1.0.0 Release IaaS
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,102 @@
|
||||
홈 서버 구축 계획
|
||||
|
||||
1. 목표
|
||||
a) 집에서 실제로 사용할 다목적 홈 서버를 구축하며, 다양한 서비스를 분산형으로(마치 실제 물리서버로 분리된 것같이) 관리하고, 이를 실제로 사용하며 정보보안에 관한 지식을 적용하고자 함.
|
||||
b) 막연하게 생각되는 기술스택 및 포토폴리오 및 정보보안기사를 구체적으로 준비 하기를 원함 + Git 등의 여러 운영 서비스에 익숙해 지기를 원함.
|
||||
c) 당장 이 계획을 시작으로 모든 과정을 문서화 하고 정리하여 스택을 쌓기를 원함.
|
||||
|
||||
2. 준비된 H/W 및 설정
|
||||
a) WTR PRO N150 (N150, 2.5gb lan 포트 x2, RAM x1, m.2 nvme x1, sata x4)
|
||||
b) 16GB DDR4 3200 x1
|
||||
c) NVME SSD 256GB x1, SATA SSD 250GB(콜드 백업 용)
|
||||
d) sata 3.5" HDD 500GB x4
|
||||
e) Domain Name 구매 예정(Cloudflare를 통한 Domain 발급과, api를 활용한 DDNS 자동화, SSL 인증서 발급 및 자동 갱신 고려, 추가로 DDoS 및 자동화된 포트 스캔등을 방지하기 위하여, 공인 ip가 아닌 DNS를 통해 들어오는 요청만 처리할 수 있도록 고려 중)
|
||||
f) 추가 라우터(iptime a1004v)
|
||||
g) 현재 홈 네트워크 구조
|
||||
WAN - easy mesh(192.168.0.x/24)
|
||||
{
|
||||
Main Gateway(Main router) - PC1(static IP)
|
||||
- PC2(static IP)
|
||||
- Sub router(static IP) - DS124(Main NAS server) (static IP)
|
||||
- printer (static IP)
|
||||
- wireless mobile devices(dynamic IP)
|
||||
}
|
||||
Main Router 기준: 국가 차단(한국 캐나다 ip만 허용), 열린 포트(443:https)
|
||||
NAS 기준(SMB, SSH, SFTP 등 모든 well-known 포트 차단, 오직 http/https 기반 접근만 허용)
|
||||
|
||||
3. 고려 사항
|
||||
a) 실제로 서버 구축시 내/외부망 막론하고 사용이 가능한가?
|
||||
b) 보안 상으로 안전한가?
|
||||
c) 준비된 H/W 성능 안에서 구현이 가능한가?
|
||||
d) 이를 통해 나의 기술 및 지식이 성장할 수 있는가?
|
||||
|
||||
5. 기본안(3 가지)
|
||||
a) 각각의 서버 HW 준비하여 전부 구축
|
||||
b) 하나의 운영체제(리눅스 예정)를 기반으로 각 서비스를 도커의 컨테이너 기술을 기반으로 격리하여 (파일 서버, 웹 서버, WAS, 프록시 서버, 미디어 서버, 사진 서버 및 도커를 통한 여러 기능들) 사용.
|
||||
c) 하이퍼 바이져를 기반으로 각 서비스를 VM 기반으로 OS별 격리하여 (현재 라우터에서 VLAN 사용이 불가능한 지금, MACVLAN을 이용하여 iptime v1004에 공인 IP를 할당해 주고, 내부에서 다시 LAN 환경을 구축 = 방화벽 등의 기능 실습 가능) 사용.
|
||||
|
||||
6. 각 안 별 장단점
|
||||
a) 기본안 1
|
||||
불가능- 금전적인 여유 X
|
||||
b) 기본안 2
|
||||
1) 장점
|
||||
A) 관리의 편리함: 모든 서비스는 하나의 OS에서 구동됨. 따라서 각각의 계정을 통한 권한 설정 및 파일 관리의 용이함(각 서버별 파일을 공유할 시, 네트워크를 활용할 필요 없이 그냥 심볼릭링크를 활용하여 도커 서비스에서 마운트하는 등) 이는 보안설정에도 이점을 줌.
|
||||
B) 이미 구성된 홈 네트워크: 이미 하나의 공인 IP를 기반으로 홈 네트워크가 구성되어 있어, 하나의 OS에 IP 설정만 해두면, 내부적으로 따로 네트워크를 사용할 필요가 없이 서비스가 가능함(도커의 bridge 기능)
|
||||
C) 하드웨어 사양이 절약됨: 하나의 OS 상에서, OS 보다 가벼운 콘테이너 기반으로 서비스를 운영하기 때문에 하드웨어 사양이 더 높아질 필요가 없음.
|
||||
D) 데이터 백업의 편리함: 모든 데이터는 OS 상의 /home 안에 저장이 가능함. 각 서버의 설정은 /home/docker 내부에 저장이 가능하며 각 유저의 설정은 /home/usr에서 관리가 가능함. 이는 백업할 데이터가 /home 하나로 일원화 될 수 있음을 이야기함. 심지어 도커의 설정도 단순히 docker-compose.yml을 통해 백업 가능.
|
||||
2) 단점
|
||||
A) 성장 가능성: 이미 모든 도커는 사용이 가능한 상태로 올라와 있으며, 이를 통해 내가 무엇인가를 구축할 필요 없이 그냥 있는 것을 설치해서 사용하면 됨. 따라서 이해하는 것이 아닌, 단순히 글을 보고 복사 붙여넣기를 하는 상황에 빠지기 쉬움.
|
||||
B) 보안 지식 습득의 불리함: 이미 홈 네트워크를 구성하며 외부로 부터 내부 서버를 공격할 때 어떤 부분을 고려하고 어떤 부분을 생각해야 하는지 어느정도 습득한 상태에서, "내부망"으로 부터의 공격에 대한 정보 지식습득에 불리함. 앞서 언급했듯, 각 서버별 통신에 네트워크를 사용하는 것이 아닌, 도커 내부의 브릿지 네트워크를 사용하므로 굳이 내부망 보안을 신경쓸 필요가 없어짐.
|
||||
c) 기본안 3
|
||||
1) 장점
|
||||
A) 실제 서버를 운영할 때 장비를 분리하는 것 처럼 모든 장비를 논리적으로 분리하여 사용할 수 있음. (방화벽, etc...)
|
||||
B) MACVLAN 기능을 적용하여 새로운 공인 ip를 적용하여 홈 서버가 제대로 구축되고 안정화 될 때 까지 격리된 네트워크를 형성해 기존의 홈 네트워크와 분리된 새로운 홈 네트워크를 사용할 수 있음 (기존의 사용 중인 home network와 격리된 환경에서 실험 가능)
|
||||
C) kail linux 등 실제 해킹 실습용 OS를 올려 해킹 공격/방어 실습이 가능
|
||||
D) 방화벽에 대한 심도 깊은 이해 가능
|
||||
E) 기본안 2의 내용도 동시에 실습 가능: 모든 서버를 전부 각각의 OS로 올리는 것이 아닌, 한 서버에서 여러 가지 역할을 하는 다목적 서버를 구성할 수 있음. (e.g. 미디어 서버 - immich, vaultwarden, jellyfin 등은 하나에 서버에서 Docker를 통해 구축/ 웹 서버 - Docker를 통해 Wordpress 등의 블로그 서비스 혹은 wiki 서비스 등을 구현 가능)
|
||||
F) 내가 각각 구현하고 올려본 서버들을 도커 컨테이너화 하여 애드온 형식으로 커스텀 서버를 내가 직접 만들어 볼 수도 있음.
|
||||
2) 단점
|
||||
A) 하드웨어 사양과 관련된 문제: 현재 PC의 스펙으로 이 모든 것들이 가능할지는 잘 모르겠음. 다만 각 서버의 기반이 될 Linux 등은 전부 CLI로 구성할 것이기에 스펙적으로 크게 모자랄지는 모르겠음.
|
||||
B) 데이터 백업의 불편함: 각 데이터를 백업하기 위해 모든 데이터를 /home만 백업하는 기본안 2와는 다르게 각각의 운영체제 및 데이터를 전부 따로 백업해야 함. - 데이터 서버를 하나 둠으로, 데이터들은 데이터 대로, 나머지는 각각의 OS단만 백업하는 형식으로 최대한 간단하게 만들 수 있을 것이라고 예상됨.
|
||||
|
||||
7. 최종 선택 및 실행 방향
|
||||
a) 최종 선택: 기본안 3
|
||||
b) 네트워크 설정: MACVLAN을 통해 새로운 공인 ip를 새로운 공유기에 할당 후, 기존의 네트워크와 별개의 새로운 "홈 서버만을 위한 네트워크 구성"과 고려 사항 확인
|
||||
1) 하이퍼 바이져 자체가 L2 스위치 기능을 적용할 수 있는지...?
|
||||
2) 하이퍼 바이져 내부에 있는 격리된 OS들은 각각의 사설 IP를 공유기(라우터)로 부터 할당 받을 수 있는지?
|
||||
3) 외부망을 통한 공격/방어 실습을 위한 세팅을 어떻게 할 수 있을지? (e.g. 외부에서 실습을 할 기기의 ip에만 모든 포트 개방 후, 칼리 리눅스로 ip로만 포트 포워딩 등.../ vlan 사용 시 더 편리하겠으나 현재 상황에서 vlan 적용 불가)
|
||||
4) VLAN 적용은 불가능하지만, VLAN의 개념과 원리 그리고 적용에 대하여 어떻게 공부할 수 있는지 확인해보기
|
||||
5) 기타 문제 사항들은 직접 경험해보며 트러블 슈팅해보기
|
||||
6) 지금 상황에서 목표 네트워크 구조
|
||||
WAN - easy mesh(192.168.0.x/24)
|
||||
{
|
||||
Main Gateway1(Main router) - PC1(static IP)
|
||||
- PC2(static IP)
|
||||
- Sub router(static IP) - DS124(Main NAS server) (static IP)
|
||||
- printer (static IP)
|
||||
- wireless mobile devices(dynamic IP)
|
||||
}
|
||||
WAN - (Sub Router/ MACVLAN 이용 공인 IP 할당) - Main Gateway2(192.168.0.x/24) - 하이퍼 바이저
|
||||
- 접속 콘솔
|
||||
7) DMZ, TwinIP, MACVLAN 차이: DMZ -> 설정되 기기로 모든 패킷 포트 포워딩/ Twin IP -> 라우터가 가지고 있는 공인 IP를 내부 클라이언트가 사용 가능/ MACVLAN -> 라우터가 가지고 있는 공인 IP가 아닌 MAC 주소를 기반으로 새로운 공인 IP를 클라이언트에 할당
|
||||
c) 하이퍼 바이저 OS 선택 및 세팅
|
||||
1) 현재 서버에 사양이 그렇게까지 좋지않고 라이센스 비용을 지불하기 어려우므로, 최대한 가볍고, 오픈 소스 프로그램 및 트러블 슈팅이 유리한 OS를 선택
|
||||
A) TrueNAS: ZFS 지원/이미 상당부분 편리한 GUI가 있음 - 무겁고/ 공부 목적에 맞지 않음
|
||||
B) Proxmox: 가볍고 오픈 소스 프로그램, 이미 상당부분 커뮤니티가 형성되어 있음.
|
||||
C) Windows server: GUI 기반, 익숙함 - 무거움, 오픈소스가 아니며, 라이센스가 필요함.
|
||||
D) 최종 Proxmox 선택
|
||||
2) 스토리지 구성
|
||||
A) 각 OS는 용량이 크지 않으므로 그냥 256GB SSD에 용량 할당하여 설치(OS용 SSD)
|
||||
B) 데이터 서버의 사용 공간을 HDD 4개를 RAID 0+ 혹은 RAID 1+(차이점에 대해 자세히 공부 필요) 혹은 RAID 5로 묶어서 사용해 보며 정리하기(파일 서버에 가용성 중요, 사실 OS 단도 RAID1을 통한 가용성을 확보하고 싶으나 HW상 SSD가 1개만 지원되므로 백업으로 갈음 - 현재는 가용성이 중요하지 않음.)
|
||||
C) 각 서버들은 필요한 데이터를 데이터 서버를 참조하여 사용(이 경우 webDAV를 통한 마운트나, smb 등의 프로토콜을 통한 읽기/sftp를 통한 업로드를 고려 중 가능한지는 확인해봐야 함)
|
||||
d) 기타 홈 서버 구축에 필요한 서버: 필요한 기본 서버들(방화벽, 데이터 서버, DB 서버, 웹 서버, 미디어 서버 등등)을 하나씩 순서대로 그리고 중요도 순서대로 선택해서 올려보기
|
||||
e) 구축된 서버를 도커를 통한 컨테이너화 해서 Git 배포 경험해보기(ex. 내가 구축한 방화벽 설정 도커)
|
||||
f) 백업
|
||||
1) 데이터가 빠진 서버 및 OS(VM) 들 각각의 용량은 그렇게 크지 않으므로, 스냅샷 및 외장 SSD를 통한 핫 백업만 진행 (백업은 실습/구축이 이뤄진 후 실제 데이터가 변경되면 변경될 때마다 증분 백업, HDD 부분은 실사용 전까지 예제만 넣을 것이므로 따로 백업 X)
|
||||
2) 홈 서버가 어느정도 완성되고 안정화 되기 전까지 중요 데이터는 기존의 NAS를 사용
|
||||
3) 이후 홈 서버가 안정화 되면, NAS를 백업용으로 사용하고 홈 서버를 기존 홈 네트워크에 가지고 들어와 집안의 서버로 활용하기(이후 MACVLAN을 이용한 다른 네트워크에는 실습용 미니 PC를 하나 더 가지고와 공부)
|
||||
4) 백업에 종류에 대해 알아보기(전체 백업, 증분 백업, 콜드 백업, 핫백업 등등)
|
||||
g) 문서화
|
||||
1) 홈 서버에 wiki 및 웹 서버등이 올라가서 그곳에 백업을 하기 전까지는 notion 등을 활용
|
||||
2) Git 등의 관리/배포 서비스 등에 대하여 아는 것이 없으므로 공부 후 이것 역시 문서화
|
||||
3) 홈 서버에 wiki 및 웹 서버등이 서비스 되기 시작하면 notion등에 올라간 문서를 홈 서버로 옮기기
|
||||
@@ -0,0 +1,814 @@
|
||||
Tags: #plan, #common
|
||||
|
||||
## Hardware information
|
||||
|
||||
### Server Hardware
|
||||
|
||||
- Server: Aoostar WTR Pro N150
|
||||
- N150 Processor (4C4T)
|
||||
- Samsung DDR4 SO-DIM Memory (31GiB)
|
||||
- Samsung NVMe SSD (1TB)
|
||||
- SATA HDD (2TB) x 4
|
||||
|
||||
### BIOS configuration
|
||||
|
||||
- Access BIOS menu with `del`
|
||||
- BIOS:Advanced:Hardware Monitor:Smart Fan Function
|
||||
- CPU Fan / Sys Fan1 / Sys Fan2
|
||||
- Fan Start Temperture: 45
|
||||
|
||||
## VM Plans
|
||||
|
||||
### Local MAC address
|
||||
|
||||
- Private Local MAC address principal
|
||||
- 0A:49:6E:4D:\[VM\]:\[Ports\]
|
||||
|
||||
### Hypervisor
|
||||
|
||||
- OS: Debian13
|
||||
- CPU: pCPU
|
||||
- Memory: 3GiB
|
||||
- This value is just margin of hypervisor. The rest of allocation of VMs.
|
||||
- KSM is activated by ksmtuned
|
||||
- MAC: C8:FF:BF:05:AA:B0, C8:FF:BF:05:AA:B1
|
||||
- Disk: 64GiB (`/`), 700 GiB (`/var/lib/libvirt`)
|
||||
|
||||
### Firewall
|
||||
|
||||
- OS: OPNsense25.7 (FreeBSD14.3)
|
||||
- CPU: 2vCPU (cputune.shares 2048)
|
||||
- Memory: 4GiB
|
||||
- MAC: 0A:49:6E:4D:00:00, 0A:49:6E:4D:00:01
|
||||
- Disk: 64GiB - qcow2
|
||||
- Services:
|
||||
- Firewall
|
||||
- IPS/IDS (CrowdSec LAPI, Suricata)
|
||||
- Kea DHCP
|
||||
- Central ACME client (automation)
|
||||
|
||||
> Do not allow web ui access from WAN, and only allow specific console user to access its web ui. Do not open ssh port at all, when you need to access its console use virsh console on the hypervisor. Because this is the center of security in this homelab.
|
||||
|
||||
### Network server
|
||||
|
||||
- OS: Debian13
|
||||
- CPU: 1vCPU (cputune.shares 512)
|
||||
- Memory: 2GiB
|
||||
- MAC: 0A:49:6E:4D:01:00
|
||||
- Disk: 32GiB - qcow2
|
||||
- Services:
|
||||
- DDNS script
|
||||
- AdGuard Home (Resolver DNS)
|
||||
- BIND9 (Authoritative DNS)
|
||||
|
||||
### Authorization server
|
||||
|
||||
- OS: Debian13
|
||||
- CPU: 2vCPU (cputune.shares 1024)
|
||||
- Memory: 4GiB
|
||||
- MAC: 0A:49:6E:4D:02:00
|
||||
- Disk: 64GiB - qcow2
|
||||
- Services:
|
||||
- Step-CA (File based)
|
||||
- Caddy-main (Reverse proxy)
|
||||
- Infrastructure services won't use caddy.
|
||||
- OPNsense
|
||||
- CrowdSec
|
||||
- AdGuard Home
|
||||
- Step-CA
|
||||
- Authellia (idP) + LLDAP (PostgreSQL)
|
||||
### Development server
|
||||
|
||||
- OS: Debian13
|
||||
- CPU: 2vCPU (cputune.shares 1024)
|
||||
- Memory: 6GiB
|
||||
- MAC: 0A:49:6E:4D:03:00
|
||||
- Disk: 256GiB - qcow2
|
||||
- Services:
|
||||
- Postgresql
|
||||
- Prometheus
|
||||
- OS: node_exporter(and telegraf)
|
||||
- VM: libvirt-exporter
|
||||
- HDD: btrfs-exporter
|
||||
- Grafana
|
||||
- Uptime kuma (SQLite)
|
||||
- Loki, Promtail
|
||||
- Code-server (File based)
|
||||
- Postfix, Dovecot, mbsync
|
||||
- These services are only uses in local mail service (@ilnmors.intenral)
|
||||
- Postfix (Split mail transper, @ilnmors.internal - directly process and @gmail.com - relayhost)
|
||||
- Dovecot (IMAP/POP3 server, Save the mail itself)
|
||||
- mbsync (Get external mail from external service to Dovecot with IMAP protocol)
|
||||
- Diun (File Provider mode + github provider mode)
|
||||
> Volume:~/data/containers/code-server/workspace/homelab:/path/of/diun:ro
|
||||
> File Provider activate and read `.container`
|
||||
> `.container` needs to have label `Label=diun.enable=true`, `Label=diun.watch_repo=true`.
|
||||
> In case of local image, use this way.
|
||||
|
||||
```yaml
|
||||
# diun.yml
|
||||
regopts:
|
||||
- name: "caddy-auth-source"
|
||||
image: "docker.io/caddy"
|
||||
```
|
||||
|
||||
```ini
|
||||
# container file
|
||||
Label=diun.enable=true
|
||||
Label=diun.watch_repo=true
|
||||
Label=diun.regopt=caddy-auth-source
|
||||
```
|
||||
|
||||
|
||||
### Application server
|
||||
|
||||
- OS Debian13
|
||||
- CPU 4vCPU (cputune.shares 2048)
|
||||
- Memory: 12GiB
|
||||
- MAC: 0A:49:6E:4D:04:00
|
||||
- Disk:
|
||||
- 256GiB - qcow2
|
||||
- 4TB - RAID10, BTRFS
|
||||
- Services (OIDC is supported):
|
||||
- OpenCloud (The fork of OwnCloud; It includes Radical, and LibreOffice. Radical will be used as only CardDAV)
|
||||
- Vikunja (CalDav and To-Do list server; PostgreSQL)
|
||||
- Gitea (Git service, and wiki; PostgreSQL)
|
||||
- Outline (Small memo note server; PostgreSQL)
|
||||
- Wiki.js (Report and book editor; PostgreSQL)
|
||||
- Immich (Photo album; PostgreSQL)
|
||||
- PeerTube (Private UCC platform; PostgreSQL)
|
||||
- Funkwhale (Music server; PostgreSQL)
|
||||
- Kavita (Web bookshelf; SQLite)
|
||||
- Audiobookshelf(SQLite)
|
||||
- Actual budget (Budget program; SQLite)
|
||||
- Paperless-ngx (Paper based information collection; OCR; PostgreSQL)
|
||||
- Miniflux (RSS management; PostgreSQL)
|
||||
- Linkwarden (Archaiving Website; PostgreSQL)
|
||||
- Ralph (IT products management; PostgreSQL)
|
||||
- Conduit (Rust matrix server; Local DB)
|
||||
- SnappyMail (Web mail service frontend with Dovecot)
|
||||
- Vaultwarden (Password manager; PostgreSQL)
|
||||
- n8n (Following goal, automation the flow; PostgreSQL)
|
||||
- Services (Foward_Auth is needed):
|
||||
- Kopia (backup)
|
||||
- Hompage
|
||||
- Define access control with yaml file via Authelia.
|
||||
```yaml
|
||||
- Admin tools:
|
||||
- group: admin
|
||||
- OPNsense
|
||||
- href: "https://opnsnese.iltnmors.internal"
|
||||
|
||||
- Services:
|
||||
- group: ["admin", "user"]
|
||||
- Gitea:
|
||||
- href:"https://gitea.iltnmors.com"
|
||||
```
|
||||
|
||||
- Services(Study):
|
||||
- Kali (Container)
|
||||
- Alpine (Container)
|
||||
|
||||
> These containers will be isolated by podman network (which has no host gateway) and podman volume. The study and practice will be conducted only in container with `podman exec -it kali bash`
|
||||
|
||||
### RDBMS and Redis
|
||||
|
||||
#### RDBMS
|
||||
|
||||
Postgresql and mariaDB will be provide database for various services on `auth`, `dev`, `app` servers. Each app can access RDBMS, Postgresql and mariaDB on `dev` server which is the central DB server with TLS.
|
||||
|
||||
#### Redis
|
||||
|
||||
Redis is the cache database, it will operate on each server where Redis is needed `dev`, and `app`, and it supports various app as one container with its own id.
|
||||
|
||||
## Matrix
|
||||
|
||||
### Network matrix
|
||||
|
||||
#### LAN
|
||||
|
||||
- Subnet: 192.168.1.0/24
|
||||
- tag: 1 (Native-untagged)
|
||||
- Static IPs:
|
||||
- 1: Gateway (opnsense)
|
||||
- 2-9: Spare IP for APs
|
||||
- 10: Hypervisor (vmm)
|
||||
- 11-12: Console
|
||||
- 20: Backup Server
|
||||
- 30: Printer
|
||||
- Dynamic IP pool
|
||||
- 100-254
|
||||
|
||||
#### VLAN10
|
||||
|
||||
- Subnet: 192.168.10.0/24
|
||||
- tag: 10
|
||||
- Static IPs:
|
||||
- 1: Gateway (opnsense)
|
||||
- 10: Hypervisor (vmm)
|
||||
- 11: Network server (net)
|
||||
- 12: Authorization server (auth)
|
||||
- 13: Development server (dev)
|
||||
- 14: Application server (app)
|
||||
|
||||
#### VPN
|
||||
|
||||
- Subnet: 10.10.10.0/24, 10.10.1.0/24
|
||||
- Static IPs:
|
||||
- 10.10.10.1: Gateway(opnsense)
|
||||
- 10.10.10.2: console
|
||||
- 10.10.10.3: phone
|
||||
- 10.10.10.4: spare
|
||||
|
||||
### UID/GID matrix
|
||||
|
||||
#### Local UID/GID
|
||||
|
||||
- Pool: 2000-2999
|
||||
- Static UID:
|
||||
- 2000: Hypervisor (vmm)
|
||||
- 2001: Network server (net)
|
||||
- 2002: Authorization server (auth)
|
||||
- 2003: Development server (dev)
|
||||
- 2004: Application server (app)
|
||||
- Static GID: 2000 (svadmins)
|
||||
|
||||
#### LDAP reservation
|
||||
|
||||
- pool: 3000 - 60000
|
||||
|
||||
#### Sub id
|
||||
|
||||
- Subuid/Subgid: 100000:65536
|
||||
|
||||
## File management
|
||||
|
||||
### File name
|
||||
|
||||
- Code files have to use `_` as a separator. (`.sh`, `.py`, etc.)
|
||||
- Normal files have to use `-` as a separator.
|
||||
|
||||
### Directory structure
|
||||
|
||||
#### Hypervisor
|
||||
|
||||
- ~/data/config/{scripts,server,services,vms}
|
||||
- ~/data/config/vms/{networks,storages,dumps}
|
||||
- /var/lib/libvirt/images
|
||||
|
||||
#### VMs
|
||||
|
||||
- ~/data/{config,containers}
|
||||
- ~/data/config/{containers,scripts,secrets,server,services}
|
||||
- ~/data/containers/apps/{certs,etc.}
|
||||
- ~/kopia
|
||||
- /etc/secrets/$UID
|
||||
|
||||
#### Application server
|
||||
|
||||
##### SSD
|
||||
|
||||
- ~/data/{config,containers}
|
||||
- ~/data/config/{containers,secrets,scripts,services}
|
||||
- ~/data/containers/app/{certs,etc.}
|
||||
- ~/kopia
|
||||
- /etc/secrets/$UID
|
||||
|
||||
##### HDD
|
||||
|
||||
- btrfs
|
||||
- ~/hdd/data/containers
|
||||
- ~/hdd/backups
|
||||
- The scrub timer systemd is required for its integrity.
|
||||
|
||||
## Certificates management
|
||||
|
||||
- CA: Step-CA (private CA)
|
||||
- DNS: BIND9 (private authoritative DNS)
|
||||
### ACME client
|
||||
|
||||
- ACME client: opnsense's `os-acme-client`
|
||||
- Automation:
|
||||
- `Upload certificate via SFTP`
|
||||
- `Run command via SSH`
|
||||
|
||||
### Caddy
|
||||
|
||||
- `caddy-dns/rfc2136`
|
||||
- `hslatman/caddy-crowdsec-bouncer/crowdsec`
|
||||
- `hslatman/caddy-crowdsec-bouncer/http`
|
||||
|
||||
|
||||
## Secret management
|
||||
|
||||
> It is necessary external KMS or secret management server (like Vault, infisical) not to leave plain data on disk. It is to hard to manage in small homelab environment. Especially, even systemd-cred uses TPM or hardware module, this makes harder to use this on rootless and vm environment. This is why compromise with perfect secret management.
|
||||
|
||||
### Secret file
|
||||
|
||||
- Files:
|
||||
- ~/data/config/secrets/.secret.yaml
|
||||
- ~/data/config/secrets/age-key.gpg
|
||||
- ~/data/config/scripts/edit_secret.sh
|
||||
- ~/data/config/scripts/extract_secret.sh
|
||||
- Directories:
|
||||
- /etc/secrets
|
||||
- Ownership: root:root
|
||||
- permission: 511
|
||||
- /etc/secrets/$UID/file
|
||||
- Ownership: $UID:root
|
||||
- Permission: 500(directory), 400(file)
|
||||
### Sequence
|
||||
|
||||
- Create `.secret.yaml`
|
||||
- Cerate `age-key`
|
||||
- Encrypt `.secret.yaml` with `sops` by `age` key
|
||||
- Modify `.secret.yaml` with `edit_secret.sh`
|
||||
- Create `podman secret` or `/etc/secrets/$UID/file` with `extract_secret.sh`
|
||||
|
||||
> Creating podman secret is always manually conducted by `extract_secret.sh`. There is no plain text of secret data in backup target, or git target.
|
||||
|
||||
```yaml
|
||||
# .secret.yaml
|
||||
# ~/data/config/secrets/.secret.yaml
|
||||
# Format of .secret.yaml
|
||||
|
||||
# app1.env:
|
||||
1SECRET: '1secret'
|
||||
2SECRET: '2secret'
|
||||
|
||||
app1.file: |
|
||||
-----TEXT-AREA-----
|
||||
contents of 3secret
|
||||
-----END-AREA-----
|
||||
|
||||
# app2.env
|
||||
3SECRET: '3secret'
|
||||
4SECRET: '4secret'
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
#### Secret scripts
|
||||
|
||||
- File:
|
||||
- ~/data/config/scripts/secrets/edit_secret.sh
|
||||
- ~/data/config/scripts/secrets/extract_secret.sh
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# edit_secret.sh /path/of/secret
|
||||
|
||||
set -e
|
||||
|
||||
KEY_PATH="$HOME/data/config/secrets"
|
||||
SECRET_FILE="$1"
|
||||
|
||||
usage() {
|
||||
echo "Usage: $0 \"/path/of/secret/file\""
|
||||
exit 1
|
||||
}
|
||||
|
||||
|
||||
if [ -z "$SECRET_FILE" -o ! -f "$SECRET_FILE" ]; then
|
||||
echo "Error: Secret file path is needed"
|
||||
usage
|
||||
fi
|
||||
|
||||
|
||||
if [ ! -f "$KEY_PATH/age-key.gpg" ]; then
|
||||
echo "Error: There is no key file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Delete password file after script
|
||||
cleanup() {
|
||||
if [ -f "/run/user/$UID/age-key" ]; then
|
||||
rm -f "/run/user/$UID/age-key"
|
||||
fi
|
||||
}
|
||||
|
||||
trap cleanup EXIT
|
||||
|
||||
|
||||
|
||||
echo -n "Enter GPG passphrase: "
|
||||
read -s GPG_PASSPHRASE
|
||||
echo
|
||||
|
||||
echo "$GPG_PASSPHRASE" | gpg --batch --yes --passphrase-fd 0 \
|
||||
--output "/run/user/$UID/age-key" \
|
||||
--decrypt "$KEY_PATH/age-key.gpg" && \
|
||||
chmod 600 "/run/user/$UID/age-key"
|
||||
|
||||
if [ -z "/run/user/$UID/age-key" ]; then
|
||||
echo "Error: Key file does not exist"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
gpgconf --kill gpg-agent
|
||||
|
||||
SOPS_AGE_KEY="$(cat "/run/user/$UID/age-key")"
|
||||
|
||||
SOPS_AGE_KEY="$SOPS_AGE_KEY" sops "$SECRET_FILE"
|
||||
```
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# extract_secret.sh /path/of/secret (-f|-e <value>)
|
||||
|
||||
set -e
|
||||
|
||||
KEY_PATH="$HOME/data/config/secrets"
|
||||
SECRET_FILE=$1
|
||||
|
||||
# shift the $2 as $1 ($1 < $2)
|
||||
shift
|
||||
|
||||
# usage() function
|
||||
usage() {
|
||||
echo "Usage: $0 \"/path/of/secret/file\" (-f|-e \"yaml section name\")" >&2
|
||||
echo "-f <type name>: Print secret file" >&2
|
||||
echo "-e <type name>: Print secret env file" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
while getopts "f:e:" opt; do
|
||||
case $opt in
|
||||
f)
|
||||
VALUE="$OPTARG"
|
||||
TYPE="FILE"
|
||||
;;
|
||||
e)
|
||||
VALUE="$OPTARG"
|
||||
TYPE="ENV"
|
||||
;;
|
||||
\?) # unknown options
|
||||
echo "Invalid option: -$OPTARG" >&2
|
||||
usage
|
||||
;;
|
||||
:) # parameter required option
|
||||
echo "Option -$OPTARG requires an argument." >&2
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Get option and move to parameters - This has no functional thing, because it only use arguments with parameters
|
||||
shift $((OPTIND - 1))
|
||||
|
||||
# Check necessary options
|
||||
if [ ! -f "$SECRET_FILE" ]; then
|
||||
echo "Error: secret file path is required" >&2
|
||||
usage
|
||||
fi
|
||||
|
||||
if [ -z "$TYPE" ]; then
|
||||
echo "Error: -f or -e option requires" >&2
|
||||
usage
|
||||
fi
|
||||
|
||||
|
||||
if [ ! -f "$KEY_PATH/age-key.gpg" ]; then
|
||||
echo "Error: There is no key file" >&2
|
||||
usage
|
||||
fi
|
||||
|
||||
# Delete password file after script
|
||||
cleanup() {
|
||||
if [ -f "/run/user/$UID/age-key" ]; then
|
||||
rm -f "/run/user/$UID/age-key"
|
||||
fi
|
||||
}
|
||||
|
||||
trap cleanup EXIT
|
||||
|
||||
echo -n "Enter GPG passphrase: " >&2
|
||||
read -s GPG_PASSPHRASE
|
||||
echo >&2
|
||||
|
||||
echo "$GPG_PASSPHRASE" | gpg --batch --yes --passphrase-fd 0 \
|
||||
--output "/run/user/$UID/age-key" \
|
||||
--decrypt "$KEY_PATH/age-key.gpg" && \
|
||||
chmod 600 "/run/user/$UID/age-key"
|
||||
|
||||
if [ ! -f "/run/user/$UID/age-key" ]; then
|
||||
echo "Error: Key file does not exist" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
gpgconf --kill gpg-agent
|
||||
|
||||
SOPS_AGE_KEY="$(cat "/run/user/$UID/age-key")"
|
||||
|
||||
if [ "$TYPE" == "FILE" ]; then
|
||||
if RESULT=$(SOPS_AGE_KEY="$SOPS_AGE_KEY" sops --decrypt --extract "[\"$VALUE\"]" --output-type binary "$SECRET_FILE") ; then
|
||||
echo -n "$RESULT"
|
||||
exit 0
|
||||
else
|
||||
echo "Error: SOPS extract error" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$TYPE" == "ENV" ]; then
|
||||
if RESULT=$(SOPS_AGE_KEY="$SOPS_AGE_KEY" sops --decrypt --extract "[\"$VALUE\"]" --output-type dotenv "$SECRET_FILE") ; then
|
||||
echo -n "$RESULT"
|
||||
exit 0
|
||||
else
|
||||
echo "Error: SOPS extract error" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
```
|
||||
|
||||
##### Secret value management
|
||||
|
||||
- Using `extract_secret.sh`
|
||||
- Inject secret value to `podman secret` or `/etc/secrets/$UID`
|
||||
|
||||
```bash
|
||||
# /etc/secrets/$UID
|
||||
# Before use sudo tee, make sure sudo doesn't need password.
|
||||
# i.e. sudo ps -ef command execute before this command.
|
||||
# Env file
|
||||
extract_secret.sh ~/data/config/secrets/.secret.yaml -e "$value" > /run/user/$UID/tmp.env \
|
||||
&& sudo mv /run/user/$UID/tmp.env /etc/secrets/$UID/"$FILE_NAME" \
|
||||
&& sudo chown $UID:root /etc/secrets/$UID/"$FILE_NAME" \
|
||||
&& sudo chmod 400 /etc/secrets/$UID/"$FILE_NAME"
|
||||
# Normal file
|
||||
extract_secret.sh ~/data/config/secrets/.secret.yaml -f "$value" > /run/user/$UID/tmp.env \
|
||||
&& sudo mv /run/user/$UID/tmp.env /etc/secrets/$UID/"$FILE_NAME" \
|
||||
&& sudo chown $UID:root /etc/secrets/$UID/"$FILE_NAME" \
|
||||
&& sudo chmod 400 /etc/secrets/$UID/"$FILE_NAME"
|
||||
|
||||
# Podman secret
|
||||
# Podman doesn't supports .env file parsing, you have to enroll all values
|
||||
extract_secret.sh ~/data/config/secrets/.secret.yaml -f "$value" | podman secret create "[$FILE_NAME|$ENV_NAME]" -
|
||||
```
|
||||
#### Use podman secret
|
||||
|
||||
```ini
|
||||
# app.container
|
||||
[Unit]
|
||||
Description=app
|
||||
|
||||
[Service]
|
||||
StartExecPre=/bin/bash -c "wait-for-it.sh ip:port -t 0"
|
||||
|
||||
[Container]
|
||||
...
|
||||
Secret=app.env,type=env,target=$ENVRIONMENT_NAME
|
||||
# or
|
||||
secret=app_data.file,target=/path/of/secret/file
|
||||
...
|
||||
```
|
||||
|
||||
> podman secret save the `secret data` as `plain text` in disk. However, it is not necessary to have full security in small homelab (practically, it is hard to realize in small environment without external secret server like infisical or vault). When the root permission or user permission compromised then it can be readable.
|
||||
|
||||
#### Change secret
|
||||
|
||||
- Edit `.secret.yaml`
|
||||
- podman container stop (systemctl)
|
||||
- `podman secret rm $target`
|
||||
- Use `extract_secret.sh`
|
||||
- Restart podman container
|
||||
|
||||
### After code-server building
|
||||
|
||||
Move all secret file on `dev` server's `code-server` container.
|
||||
|
||||
- Files:
|
||||
- .secret.yaml
|
||||
- age-key.gpg
|
||||
- edit_secret.sh
|
||||
- extract_secret.sh
|
||||
- Path: $HOME/workspace/homelab/data/common/config/{secrets,scripts} (Mapped volume in container)
|
||||
- Change the KEY_PATH as `$HOME/workspace/homelab/data/common/config/{secrets,scripts}` on scripts
|
||||
|
||||
#### Apply secrets from code-server
|
||||
|
||||
Use SFTP and SSH (or Ansible playbook), decrypt the secret values and make a file on container's `/run/user/$UID` and upload to target server's `/run/user/$UID`. Then use ssh remote command to add podman secret or mv command at the code-server container.
|
||||
|
||||
It can works on the code-server web terminal. However, if there were problems on caddy, which means not to access web console then just use ssh and podman exec.
|
||||
|
||||
## Update and upgrade policy
|
||||
|
||||
### Hypervisor
|
||||
|
||||
- Never update or upgrade hypervisor before the its stability is verified from other VMs
|
||||
|
||||
### VMs
|
||||
|
||||
- Make a qcow2 snapshot before major update or upgrade, using `virsh snapshot`
|
||||
- If there were some problems, rollback using snapshot.
|
||||
|
||||
### Containers
|
||||
|
||||
- Check the version from Diun.
|
||||
- Read caution and changes.
|
||||
- Apply the update via container file (Prepare the containerfile to make image). `systemctl --user daemon-reload` and `systemctl --user restart container`
|
||||
|
||||
## Backup policy
|
||||
|
||||
### Kopia
|
||||
|
||||
- ~/kopia: The directory of kopia configuration files.
|
||||
- ~/hdd/backups: The destination directory of each server's Kopia.
|
||||
- Don't backup the live data such as live DB data.
|
||||
- Only configuration files are backed up in hypervisor.
|
||||
|
||||
### Configuration file backup
|
||||
|
||||
- Save all configuration files in `code-server` container.
|
||||
- Path: ~/data/containers/code-server/workspace/homelab
|
||||
- Use `Gitea` container to track and manage files.
|
||||
- Apply `Ansible` on `code-server` (Following goal)
|
||||
|
||||
### opnsense
|
||||
|
||||
- `os-sftp-backup` sends its configuration towards `code-server`
|
||||
|
||||
### Application data
|
||||
|
||||
#### Common data
|
||||
|
||||
- `Kopia` backup files to app server using sftp
|
||||
- Backup target: ~/data
|
||||
- Path: ~/hdd/backups
|
||||
|
||||
#### DB data
|
||||
|
||||
Only backup `dump` db data.
|
||||
|
||||
##### Schema backup
|
||||
|
||||
```bash
|
||||
# Dev server
|
||||
podman exec postgresql sh -c 'pg_dumpall --scheme-only' > ~/data/postgresql/backups/postgresql-cluster-\[date\].dump
|
||||
```
|
||||
|
||||
##### DB data backup
|
||||
|
||||
```bash
|
||||
# VM's application data backup
|
||||
podman exec application sh -c 'pg_dump -U $DB_USER -p $DB_PW' > ~/data/containers/application/backups/application-\[date\].dump
|
||||
|
||||
# app's application data backup
|
||||
podman exec application sh -c 'pg_dump -U $DB_USER -p $DB_PW' > ~/hdd/data/containers/application/backups/application-\[date\].dump
|
||||
```
|
||||
|
||||
##### Container volume
|
||||
|
||||
```ini
|
||||
# app.container
|
||||
#...
|
||||
[Container]
|
||||
# ...
|
||||
Volume=~/data/containers/application/backups:/backups:rw
|
||||
```
|
||||
|
||||
##### Example of DB backup senario
|
||||
|
||||
```ini
|
||||
# postgres-db-backup.service
|
||||
[Unit]
|
||||
Description=PostgreSQL Database Backup
|
||||
After=postgresql.service
|
||||
Requires=postgresql.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
# %% is needed in systemd, because `%` has special meaning in systemd.
|
||||
ExecStart=/bin/sh -c 'podman exec postgresql sh -c "pg_dumpall --scheme-only" > ~/data/containers/postgresql/backups/postgresql-cluster-$(date +%%Y-%%m-%%d_%%H-%%M-%%S).dump'
|
||||
|
||||
Nice=19
|
||||
IOSchedulingClass=idle
|
||||
|
||||
# Management DB dump file
|
||||
ExecStopPost=/bin/bash -c `find "~/data/containers/postgresql/backups/" -maxdepth 1 -type f -mtime +7 -delete`
|
||||
```
|
||||
|
||||
```ini
|
||||
# postgres-db-backup.timer
|
||||
[Unit]
|
||||
Description=Run PostgreSQL backup daily at 2:30 AM
|
||||
|
||||
[Timer]
|
||||
# everyday 02:30 AM start
|
||||
OnCalendar=*-*-* 02:30:00
|
||||
# Random time to postpone the timer
|
||||
RandomizedDelaySec=15min
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
```
|
||||
|
||||
#### External backup
|
||||
|
||||
- Use `Kopia` in app server to backup files to external data server.
|
||||
|
||||
#### Verify backup
|
||||
|
||||
- Restore random directory from backup on dev server's test directory once a month (or week).
|
||||
- Check its integrity and availability.
|
||||
- If there were some problems, check the all backup data and conduct full backup immediately.
|
||||
|
||||
## Systemd
|
||||
|
||||
### `.service` file
|
||||
|
||||
- Path: ~/.config/systemd/user
|
||||
- Example of `.service`
|
||||
|
||||
```ini
|
||||
# ~/data/config/services/opnsense.service
|
||||
# ~/.config/systemd/user/opnsense.service
|
||||
[Unit]
|
||||
Description=opnsense Auto Booting
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
# Requires=x.services
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
|
||||
# Maintain status as active
|
||||
RemainAfterExit=yes
|
||||
|
||||
# Wait for other dependent services
|
||||
# ExecStartPre=%h/data/config/scripts/wait-for-it.sh -h [ip] -p [port] -t 0
|
||||
|
||||
# Run the service
|
||||
ExecStart=/usr/bin/virsh -c qemu:///system start opnsense
|
||||
|
||||
# Stop the service
|
||||
ExecStop=/usr/bin/virsh -c qemu:///system shutdown opnsense
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
```
|
||||
|
||||
### Hypervisor
|
||||
|
||||
- Adjust booting sequence of VMs via `.service`
|
||||
- Use `wait-for-it.sh` and `Requires`
|
||||
- Sequence
|
||||
- vmm
|
||||
- opnsense
|
||||
- net
|
||||
- auth
|
||||
- dev
|
||||
- app
|
||||
|
||||
### Containers
|
||||
|
||||
#### Quadlet
|
||||
|
||||
- Make the `.container` file
|
||||
- Path: ~/data/config/containers/\[app_name\]
|
||||
- Symbolic link path: ~/.config/systemd/containers
|
||||
- `systemctl --user daemon-reload` makes `.service` file automatically
|
||||
- If pod is needed, then set `.pod` file
|
||||
|
||||
```ini
|
||||
# app.container
|
||||
[Quadlet]
|
||||
# Don't make a dependencies
|
||||
DefaultDependencies=false
|
||||
|
||||
[Unit]
|
||||
# Pod=app.pod
|
||||
Description=app
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
Requires=required.service
|
||||
|
||||
[Service]
|
||||
StartExecPre=%h/data/config/scripts/wait-for-it.sh dev.ilnmors.internal:8080 --timeout=0 --strict
|
||||
|
||||
[Container]
|
||||
# pod=app-pod
|
||||
Image=localhost/app:1.0.0
|
||||
Name=app
|
||||
Port=2080:80/tcp
|
||||
Port=2443:443/tcp
|
||||
Volume=%h/data/containers/app/etc:/etc/app:rw
|
||||
Volume=%h/data/containers/app/data:/app:rw
|
||||
Secret=app.env,type=env
|
||||
Secret=app.file,type=file,target=/path/of/secret/file
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
```
|
||||
|
||||
```ini
|
||||
# app.pod
|
||||
[Quadlet]
|
||||
# Don't make a dependencies
|
||||
DefaultDependencies=false
|
||||
|
||||
[Pod]
|
||||
Name=app
|
||||
PublishPort=2080:80/tcp
|
||||
```
|
||||
@@ -0,0 +1,167 @@
|
||||
Tags: #plan, #milestone, #common
|
||||
|
||||
## Homelab Project
|
||||
|
||||
### Plans
|
||||
|
||||
- [x] Build plans
|
||||
|
||||
### Organize theory
|
||||
|
||||
- [x] Organize DNS
|
||||
- [x] Organize DHCP
|
||||
- [x] Organize PKI
|
||||
- [x] Organize TLS
|
||||
- [x] Organize SSO
|
||||
- [x] Organize Email service
|
||||
### Organize configuration
|
||||
|
||||
- [x] Organize Debian installation
|
||||
- [x] Organize Debian common configuration
|
||||
- [x] Organize iptables
|
||||
- [x] Organize podman
|
||||
- [x] Organize CrowdSec
|
||||
- [x] Organize BTRFS
|
||||
|
||||
### Hypervisor
|
||||
|
||||
- [x] Install Debian13
|
||||
- [x] Set common configuration
|
||||
- [x] Set network interfaces
|
||||
- [x] Set QEMU/KVM and Libvirt environment
|
||||
|
||||
### opnsense vm
|
||||
|
||||
- [x] Generate opnsense template
|
||||
- [x] Install opnsense
|
||||
- [x] Set interface configuration
|
||||
- [x] Set CrowdSec LAPI configuration (without TLS)
|
||||
- [x] Set KEA DHCPv4 configuration
|
||||
|
||||
### net vm
|
||||
|
||||
- [x] Generate net vm template
|
||||
- [x] Install Debian13
|
||||
- [x] Set common configuration
|
||||
- [x] Set network interfaces
|
||||
- [x] Set DDNS script
|
||||
- [x] Set BIND container
|
||||
- [x] Set AdGuard Home container
|
||||
- [x] Fix DHCP and Static IP server's `resolv.conf`, and opnsense dns
|
||||
|
||||
### auth vm
|
||||
|
||||
- [x] Generate auth vm template
|
||||
- [x] Install Debian13
|
||||
- [x] Set common configuration
|
||||
- [x] Set Step-CA container
|
||||
|
||||
### opnsense vm
|
||||
|
||||
- [x] Set ACME client in OPNsense
|
||||
- [x] Set TLS on OPNsense with ACME client
|
||||
- [x] Set TLS on CrowdSec LAPI with ACME client
|
||||
- [x] Set ACME automation
|
||||
|
||||
### net vm
|
||||
|
||||
- [x] Set TLS on AdGuard Home container with ACME client
|
||||
|
||||
### dev vm
|
||||
|
||||
- [x] Generate dev vm template
|
||||
- [x] Install Debian13
|
||||
- [x] Set common configuration
|
||||
|
||||
### app vm
|
||||
|
||||
- [x] Generate app vm template
|
||||
- [x] Install Debian13
|
||||
- [x] Set common configuration
|
||||
- [x] Set BTRFS on `$HOME/hdd`
|
||||
|
||||
### auth vm
|
||||
|
||||
- [x] Set Caddy - auth container (Main caddy)
|
||||
- [x] Caddy TLS certificates
|
||||
- [x] Caddy bouncer
|
||||
- [x] Caddy log agent
|
||||
- [x] Set crowdsec bouncer
|
||||
- [x] Set collection in LAPI (parser + scenario)
|
||||
- [x] Set collection in auth vm
|
||||
- [x] Set acquis.d/caddy-auth.yaml
|
||||
- [x] Set LLDAP container
|
||||
- [x] Set Authelia container
|
||||
- [x] Forward_Auth setting
|
||||
|
||||
### dev vm
|
||||
- [x] Set Postgresql container
|
||||
- [x] Set TLS on Postgresql with ACME client
|
||||
- [x] Set Caddy - dev container (sidecar caddy)
|
||||
- [x] Verify TLS re-encryption
|
||||
- [x] Veryfiy Forward_Auth from Caddy - auth
|
||||
- [ ] Set code-server container
|
||||
- [ ] Generate container file (with Git and Ansible)
|
||||
- [ ] Apply SSO with Authelia and Forward_Auth
|
||||
- [ ] SSH setting
|
||||
- [ ] Upload opnsense backup file via SFTP
|
||||
- [ ] Get all server's configuration file via from terminal
|
||||
|
||||
### app vm
|
||||
|
||||
- [ ] Gitea container
|
||||
- [ ] DB setting
|
||||
- [ ] OIDC apply with Authelia
|
||||
- [ ] Code and configuration file Git
|
||||
- [ ] Vaultwarden container (User secret management)
|
||||
- [ ] DB setting
|
||||
- [ ] OIDC apply with Authelia
|
||||
- [ ] TOTP setting (recovery code will be saved in .secret.yaml)
|
||||
### dev container
|
||||
|
||||
- [ ] Set Diun container
|
||||
- [ ] Set Prometheus and grafana container
|
||||
- [ ] Set Loki and promtail container
|
||||
- [ ] Set Postfix
|
||||
- [ ] Set Dovecot
|
||||
- [ ] Set Fetchmail
|
||||
- [ ] Set Mariadb conatiner (when it needs)
|
||||
- [ ] Set TLS on Mariadb with ACME client
|
||||
|
||||
### app vm
|
||||
|
||||
- [ ] Set Caddy - app container (sidecar caddy)
|
||||
- [ ] Set app service containers
|
||||
- [ ] Set all server's Kopia and Gitea (with code-server)
|
||||
- [ ] Conduct backup verification
|
||||
- [ ] `Git` all code on Gitea
|
||||
|
||||
---
|
||||
## Following goals
|
||||
|
||||
- [ ] Ansible
|
||||
|
||||
> To manage and automate this project, the tool of automation is necessary. In modern architecture, Ansible is one of most powerful tools to automate configuration. After the project will be finished, Ansible will be adopted to manage server's configurations. It supports idempotency powerfully, so from the basic configuration the dev-ops system will be applied on this project. Idempotence is very important.
|
||||
|
||||
- [ ] self inspection or mock audit
|
||||
|
||||
> Every architecture has their own vulnerability. It is because always the administrator itself is the weakest chain in the security. So, it is necessary to inspect the system based on external criteria. There's the list of criteria below.
|
||||
>
|
||||
> - ISMS-P - Korean standard
|
||||
> - ISO/IEC 27001 - International standard/Annex A
|
||||
> - NIST SP 800-53 - NIST CSF
|
||||
> - CIS Benchmark - checklist of Debian/OPNsense/RDBMS/etc
|
||||
> - OWASP Top 10
|
||||
|
||||
- [ ] documentation deeper
|
||||
|
||||
> The system itself can't prove anything. When the document that everyone can understand what it is supports the system, then the system become the most powerful weapon.
|
||||
>
|
||||
> - The code and configuration files, and Ansible playbook based on Git (private Gitea)
|
||||
> - Architecture Report based on bookstack (As-Is)
|
||||
> - Policy and Norms Report based on bookstack (To-Be)
|
||||
> - Audit Report based on bookstack (Proof of Compliance)
|
||||
|
||||
- [ ] hacking simulation for public licence
|
||||
|
||||
> Use podman network and podman volume, create `kali` and `alpin` container to train and study about hacking in `dev` server. These containers won't combined with `systemd` via `Quadlet`
|
||||
@@ -0,0 +1,101 @@
|
||||
Tags: #theory, #network, #protocol
|
||||
|
||||
## DNS (Domain Name System)
|
||||
|
||||
In the beginning of the internet, there were a few hosts on networks. It was possible to manage all hosts on network via IP address or domain name in `/etc/hosts` file in each servers. However, it is hard for people to match and remember what IP addresses means. When the internet environment became bigger and bigger, the complex of route the target server would be harder. To solve this problem, the DNS emerged as a translator between IP address and domain name. In modern internet environment, DNS has hierarchy structure from root to TLD, TLD to authoritative server for efficiency.
|
||||
|
||||
### Structure of DNS
|
||||
|
||||
#### Communication
|
||||
|
||||
- DNS: 53 tcp/udp
|
||||
|
||||
DNS communication basically uses 53/udp port. However, in the modern internet environment; which means complex environment sometimes the size of packet is above 512 bytes. In this case, DNS uses 53/tcp too. The vulnerability of DNS is that all communication is on plain data. Everyone can conduct sniffing attack towards DNS packet.
|
||||
|
||||
- DoT (DNS over TLS): 853 tcp
|
||||
|
||||
DoT was developed to encrypt DNS query. DoT uses [TLS](./02_04_tls.md) to request query. This protocol uses TLS. Moreover, because of TLS, nobody can do sniffing attack towards DoT. However, it uses specific port 853. If ISP block the 853 or analyze 853 port, the pattern of usage will be analyzed or even you cannot use DoT itself. Additionally, there is also DNS over DTLS which uses 853 udp.
|
||||
|
||||
- DoH (DNS over HTTPS): 443 tcp/udp
|
||||
|
||||
DoH is very similar with DoT. It uses TLS, and it was developed to encrypt DNS query. there is just one difference. This uses https(443 tcp/udp) instead of 853 tcp. https is standard of web protocol, so it is hard to analyze someone sends DNS request or common web packets. It means, ISP or government cannot block 443 port itself or analyze the pattern of DNS query. Since 2022, there's the new standard DNS over HTTP/3 which uses 443 udp port.
|
||||
|
||||
- DNSSEC (DNS SECurity extensions)
|
||||
|
||||
Originally, client couldn't verify integrity of the response from DNS server. If malicious attacker could get authority of cache DNS server to change their records, all clients would get affected. (i.e. pharming attack). DNSSEC is a protocol to guarantee integrity of DNS record. DNSSEC protocol adds some records in zone, RRSIG(Resource Record Signature), DNSKEY, DS, NSEC, CDNSKEY, CDS. All resolver DNS verify integrity of their records to authoritative DNS with these records. This process is similar to PKI, the chain of trust.
|
||||
|
||||
- ECH (Encrypted Client Hello)
|
||||
|
||||
Basically, client hello packet has SNI (Server Name Indication). Even though all communication under TLS is encrypted, but to start session the packet has to contain the SNI to identify server. To encrypt this information, SNI the ESNI(Encrypted SNI) was developed in 2018 based on TLS 1.3. However, ESNI just could encrypt SNI information. Now, since 2020, the new standard ECH was developed to supersede ESNI. ECH not only encrypt SNI but also encrypt all client hello process. ECH is latest protocol, and it has a lot of dependency in DNS server, service server and client. When all of them supports ECH, then user can use ECH. Because when ECH encrypts client hello data client need the target server's public key (certificate), it has to look up from encrypted DNS (DoH or DoT).
|
||||
|
||||
#### Zone
|
||||
|
||||
DNS server has zones; Forward zone and Reverse zone.
|
||||
|
||||
- Forward zone
|
||||
|
||||
Forward zone has basically information of the pair of domain and IP address. The role of this zone is change domain name to IP address. The domains are managed by IANA, TLD is already reserved. (i.e. `.com`, `.org`, etc...) For private network, `.home.arpa` or `.internal` are reserved.
|
||||
|
||||
- Reverse zone
|
||||
|
||||
Reverse zone also has basically information of the pair of IP address and domain. The role of this zone is change IP address to domain name. To change domain to IP address it uses specific domain name. \[reversed_ip_address\].in_addr.arpa (i.e. 1.168.192.in-addr.arpa)
|
||||
|
||||
#### Records
|
||||
|
||||
Each zone has their record type. If zone were a kind of DB, record would be a data of DB. There is basic records type below.
|
||||
|
||||
- SOA type
|
||||
|
||||
Information of ZONE management. Every zone has this SOA type record.
|
||||
|
||||
- NS type
|
||||
|
||||
Designate authoritative name server of domain zone
|
||||
|
||||
- A type
|
||||
|
||||
Mapping domain to IPv4 address
|
||||
|
||||
- AAAA type
|
||||
|
||||
Mapping domain to IPv6 address
|
||||
|
||||
- PTR type
|
||||
|
||||
Mapping IP address to domain
|
||||
|
||||
- CNAME type
|
||||
|
||||
Mapping domain to domain. CNAME type is kind of alias of domain. It can't have IP address value. The query acts recursively, and it gets IP address at the end.
|
||||
|
||||
#### Key
|
||||
|
||||
There is the keys to control DNS records or zone, even DNS server itself.
|
||||
|
||||
- rndc key
|
||||
|
||||
This key is to control DNS server itself. When rndc key set on DNS server, client can control DNS server with this key like, reboot server, load or unload zone. rndc key is basically generated by `rndc-confgen` command and it is defined on `rndc.conf` and `named.conf`.
|
||||
|
||||
- tsig key
|
||||
|
||||
This key is to guarantee integrity when the server syncronize zone data between other servers (usually master-slave server). It is possible update records via this key depending on the setting. Therefore, tsig key is usually used for DDNS or DNS-01 challenge. The key is generated in the DNS server, and it defined in `named.conf`.
|
||||
|
||||
### DNS Server type
|
||||
|
||||
DNS server basically separated as authoritative DNS and recursive DNS.
|
||||
|
||||
#### Authoritative DNS
|
||||
|
||||
Authoritative DNS has literally authority of domain zone. It doesn't ask recursive queries towards other DNS server in case of the query that is in its authoritative zone. It is necessary to use DNS-01 challenge (ACME protocol).
|
||||
|
||||
#### Recursive DNS
|
||||
|
||||
Recursive DNS oppositely doesn't have authority of the records in its zone. When it gets query request, it ask recursive query towards authoritative DNS. It can store the information of records (cache) and give response towards client with the cache.
|
||||
|
||||
### Split Horizon DNS
|
||||
|
||||
Split Horizon DNS means getting different IP address depending on where the client exists. For instance, if there were the domain `example.com`. This domain has its own private IP address, simultaneously own public IP address (from NAT). When client request the query `example.com` in the private network, private DNS would respond its private IP address. However, when the client request the query in the WAN network, public DNS would respond its public address. Client can access `example.com` in both case, but the IP address which client respond are different. To use this protocol, the network route will be efficient because the packet doesn't have to go out to the WAN area in private network. Basically, it is implemented internal authoritative DNS and recursive DNS. Recursive DNS decides where to send the query based on domain.
|
||||
|
||||
### DDNS (Dynamic DNS)
|
||||
|
||||
Public IP address can be changed by ISP at any time. It is hard (or expensive) to get static public IP address by ISP. However, the service (server) always guarantee their availability regardless what is their IP. DDNS is basically the protocol to change A or AAAA (or CNAME) records in DNS as server's current IP. Server keeps checking their current public IP and when it changes the server send the request to change its A or AAAA records to public authoritative DNS server with authentication with API key or tsig key.
|
||||
@@ -0,0 +1,55 @@
|
||||
Tags: #theory, #network, #protocol
|
||||
|
||||
## DHCP (Dynamic Host Configuration Protocol)
|
||||
|
||||
Before DHCP emerged, every client had to set their own static IP or using RARP(Reverse Address Resolution Protocol. They have critical problems.
|
||||
|
||||
- Static IP
|
||||
- Each host has their own IP regardless they run or not. It cause lack of IP address.
|
||||
- If administrator made configuration mistake, the network itself could stop. For instance, IP conflict, or subnet configuration error, etc.
|
||||
|
||||
- RARP
|
||||
- RARP works on L2, it makes hard to implement on hardware.
|
||||
- RARP server had to exist on every subnet(L2), it was inefficient.
|
||||
|
||||
To solve this problem, BOOTP was developed and it evolved as DHCP. DHCP works on L3, and like its name they lease IP dynamically. It allocates IP to hosts with 3 steps. Lease, renewal, and release.
|
||||
|
||||
### Lease
|
||||
|
||||
When DHCP server gets request from host to allocate IP, DHCP server choose one IP address from its subnet pool, and it responds IP/subnet and information to the hosts. It follows the process called DORA.
|
||||
|
||||
- DHCP Discover
|
||||
|
||||
The host which has no IP address broadcast discover packet including its MAC to local network. Only DHCP server responds this packet, the others discard the packets.
|
||||
|
||||
- DHCP Offer
|
||||
|
||||
When DHCP server gets discover packet, DHCP server broadcast Offer packet including host's MAC of which sends discovery packet. Only host which sends discovery packet, the others discard the packets.
|
||||
|
||||
- DHCP Request
|
||||
|
||||
When the host gets offer packet, it consider the network has DHCP server and broadcast request packet to the local network. More than one DHCP server can exist in one network, therefore host broadcast request packet.
|
||||
|
||||
- DHCP ACK
|
||||
|
||||
When DHCP server gets the Request packet, it searches for an IP address which can be allocated in its pool. When DHCP server finds an available IP address, it sends to ACK packet including IP address and subnet, and optional information(DNS, gateway, etc...) with reservation time.
|
||||
|
||||
### Renewal
|
||||
|
||||
The host allocated IP from DHCP try to renew its IP before it expires. There are two chances to renew reservation time. The first try to renew is when the reservation time remains half of them; T1. In this time, host uses unicast. If the first try failed, then it try one more time when 87.5% of the lease time has passed; T2. In this time, the host uses broadcast. All try fails, client gives up the leased IP and try the lease process again. DHCP server release the IP address from the host.
|
||||
|
||||
- DHCP Request
|
||||
|
||||
The host sends request packet to DHCP server as unicast.
|
||||
|
||||
- DHCP ACK
|
||||
|
||||
When DHCP server gets the request, it sends ACK packet to the host as unicast.
|
||||
|
||||
### Release
|
||||
|
||||
When the host doesn't use IP address anymore, DHCP server makes IP as available IP in its pool. Especially, client can send `DHCPRELEASE` to DHCP server explicitly when it doesn't need IP address.
|
||||
|
||||
### DHCP relay
|
||||
|
||||
Commonly, DHCP is located in router. Because router is the center of networks, and it takes charge of number of networks. However, DHCP server doesn't have to be located in router because of existence of DHCP relay. When router gets DHCP packets (DORA), router can relay the packets as unicast between host and DHCP server which are in different subnet.
|
||||
@@ -0,0 +1,103 @@
|
||||
Tags: #theory, #network, #security
|
||||
|
||||
## PKI(Public Key Infrastructure)
|
||||
|
||||
PKI is defined on RFC4949 and RFC5280. PKI is neither one of the protocols nor algorithms, it is a huge infrastructure itself. It has various parts which are CA(Certification Authority), RA(Registration Authority), VA(Validation Authority) and Certificates.
|
||||
|
||||
### CA (Certification Authority)
|
||||
|
||||
CA is the most important part in PKI. It is the source of certification for the secure communication. CA has various roles, some can be delegated to the RA or VA. However, CA is essential in PKI. RA or VA are optional to support CA. The purposes of CA are as follows:
|
||||
|
||||
- Managing certification policy
|
||||
- Issuing certificates
|
||||
- Verifying certificates validity
|
||||
- Managing CRL(Certification Revocation List)
|
||||
- Cross-certification between root CAs
|
||||
|
||||
CAs often operate in a hierarchy. Each CA has their own role, and they are basically PAA(Policy Approving Authority), PCA(Policy Certification Authority), and CA(Certification Authority). Normally, PAA and PCA are usually combined as root CA, and CA is an intermediate CA.
|
||||
|
||||
#### root CA
|
||||
|
||||
Root CA is simply CA of CA. It is the source of trust chain. Only a root CA can authenticate itself alongside other root CAs(cross certification) or intermediate CAs below it. Theoretically, and precisely root CA can be divided into PAA and PCA, but practically they operate as one authority, root CA.
|
||||
|
||||
#### intermediate CA
|
||||
|
||||
Intermediate CA is the authority which issues certificates for end entities we can use (i.e. web servers). Their certificate is digitally signed (the hash value is signed by root CA's private key) by root CA, so every user can verify their trust with root CA's certificate (root CA's public key). The reason for using intermediate CA is for security. If CA's private key was violated, all chain of trust will be disrupted. When only root CA exists and its private key was violated, all secure communication will be threatened. To avoid and manage these threats root CA and intermediate CA are divided.
|
||||
|
||||
#### Structure of CA
|
||||
|
||||
The basic structure of CA is very simple. It only contains private key, certificate (which is designated by X.509), and provisioner. When CA gets a request of sign on services' certificates it operates as shown below.
|
||||
|
||||
- Provisioner checks CSR (Certificate Signing Request) is valid on its policy
|
||||
- When CSR is valid, it checks the content of certificates(x.509) and its policy
|
||||
- CSR and content of certificates are valid, CA signs on the certificate with its private key (sign on hash value with CA's private key)
|
||||
|
||||
After CA's signing, every client can check its validation with CA's certificates (CA's public key)
|
||||
|
||||
### RA (Registration Authority)
|
||||
|
||||
Due to CAs many roles, all processes above are made to be inefficient when conducted. Therefore, CA delegates its registration role to RA. RA seeks to examine CSR and certificates' content. When the content of certificate and CSR are valid under its policy, RA would send request to CA to sign.
|
||||
|
||||
### VA (Validation Authority)
|
||||
|
||||
Additionally, just as RA, CA can delegate its validation role to VA. Basically, VA manages certificates' validation based on CRL (Certificate Revocation List). However, in modern internet environment, it is inefficient to manage all invalid certificates based on a list. To solve this problem, OCSP (Online Certificate Status Protocol) was developed. However, OCSP also has its limitation, the advanced way is being suggested.
|
||||
|
||||
#### CRL (Certificate Revocation List)
|
||||
|
||||
Once the certificate gets signed from CA, it is always a valid cryptograph before it expires. However, in the real world some certificates have to be revoked in case CA's key is violated or the service no longer operates, etc. So, CA needs to manage these kinds of invalid certificates which doesn't expire, such as letting clients know that their certificates are not valid anymore. This is a concept of CRL. VA releases the CRL to clients, the clients download or update the list to judge the certificates' validation.
|
||||
|
||||
#### OCSP (Online Certificate Status Protocol)
|
||||
|
||||
CRL was very effective to revoke invalid certificates but when internet is growing, a massive of invalid certificates appears it shows its limitation. Because to check certificates validation, clients need to download and update CRL from CA or VA. There are tons of invalid certificates, and CRL's size become bigger and bigger. OCSP's concept is when client request to check certificates validation, OCSP server response it. To use OCSP, clients don't need to download full CRL anymore. It is very effective to solve CRL's problem. However, it has problems too. OCSP is defined on RFC6960. OCSP's problems are below.
|
||||
|
||||
- Clients should expose its identity(IP address) to check certificates validation.
|
||||
- VA can be SPOF(Single Point of Failure).
|
||||
- It is very hard to match time syncronization
|
||||
- The more requests come to VA, the more burden is on VA.(DoS, DDoS problem)
|
||||
|
||||
#### OCSP stapling
|
||||
|
||||
OCSP's problem was that client should request to VA directly. So, when the certificate providers(like web server, db, etc.) request its validation and give the proof to client it, clients doesn't need to check the validation of certificate to VA anymore. It reduces VA's burden and time synchronization problems.
|
||||
|
||||
### Certificate
|
||||
|
||||
The form and content of certificate are standardized by X.509. X.509 format contains server's public key, domain(SAN), expired date, and sign of CA, and etc. When CA validate services or people who sent CSR(which contains information to create certificate with x.509 form), CA signs on their certificates (sign on hash value with CA's private key). Then, all clients can validate that certificates with CA's public key(CA's certificate) cryptographically. Ultimately, when clients trust CA, then they can trust services that have certificate signed by CA. However, even if the certificate were valid cryptographically, some certificates would not be valid. It is reason why CRL or OCSP is needed.
|
||||
|
||||
#### The way to issue certificate
|
||||
##### ACME
|
||||
|
||||
Basically, certificate is formed by X.509. The services which want to get certificate send request(CSR) to CA. When the services were open internet, and it should get the certificate from public CA like let's encrypt on its public domain name. Public CA always sign on public domain which is open internet, and it is the role of public CA. The protocol to automate this process is ACME. Before ACME protocol, getting a certificate was manual. The person in charge should send CSR to CA, and CA checks the CSR and return the certificate ... All process was manual. ACME protocol made this process automatically. Simply, ACME protocol checks services authority on domain with various way, and issues certificate.
|
||||
|
||||
- http-01 challenge
|
||||
|
||||
The server creates certificate file for CA. CA accesses the service server's specific directory via 80/TCP. When CA checked the file, CA issues certificate.
|
||||
|
||||
- DNS-01 challenge
|
||||
|
||||
The server sends request to authoritative DNS server to add specific record with TSIG key(API key). When the specific record successfully added on DNS server and CA can check this, CA issues certificate. This challenge can verify full authority of domain including sub domain, so CA can issue wildcard certificate.
|
||||
|
||||
- TLS_ALPN-01 challenge
|
||||
|
||||
It uses TLS handshake process on 443/TCP. ALPN(Application Layer Protocol Negotiation) is the protocol to decide what protocol server and client will use. In this challenge, CA sends some token to service server. Service server creates temporary TLS certificates using `acme-tls/1` protocol with token from CA. When CA access to server, it asks `acme-tls/1` protocol to server. The server presents the temporary certificate, then CA issues certificate.
|
||||
|
||||
##### Based on identity
|
||||
|
||||
ACME is powerful protocol to automate issuing certificate. However, it is necessary to check ownership of domain to use ACME challenge (Usually, using http or https). However, It is possible to use TLS protocol without http or https. Originally, the process was also manual way called PKIX(Public-Key Infrastructure). It is so complex and slow, and it is impossible to use ACME. To automate this process, modern CA uses JWK(JSON Web Key) and JWT(JSON Web Token). The process is below.
|
||||
|
||||
- The administrator registers system's public key as JWK at CA.
|
||||
|
||||
> JWK is a format of specification of key. It shows key and its information as JSON format.
|
||||
|
||||
- The system sends the JWT to get certificate signed by its private key to CA.
|
||||
|
||||
> JWT is a specification of what client can do after connection or of proof this request is valid. In this process, JWT substitute CSR.
|
||||
|
||||
- CA verify JWT by pre-registered JWK. When JWT is valid, it issues X.509 certificate.
|
||||
|
||||
##### Usage of X.509 Certificates
|
||||
|
||||
Regardless the way to issue certificate, either ACME or the way based on its identity, the certificate which is already issued is always valid before it is revoked. You can use X.509 certificate from the way based on its identity for https, oppositely you can use X.509 certificate from ACME for other TLS protocols (like LDAPS, DB TLS communication, etc).
|
||||
|
||||
#### X.500 and LDAP(Lightweight Directory Access Protocol)
|
||||
|
||||
In the beginning of PKI, there was plan to make a server for all certificates. It is X.500. However, the protocol to implement X.500, DAP(Directory Access Protocol) was too complex and heavy to use internet environment. To make way easy and light, LDAP(Lightweight Directory Access Protocol) was developed to store certificates. However, there are a lot more efficient way to manage certificates appeared already. LDAP could not realize X.500, but it is utilized as centralized authentication system like [SSO](./02_05_sso.md), or OS account management.
|
||||
@@ -0,0 +1,92 @@
|
||||
Tags: #theory, #network, #security, #protocol
|
||||
|
||||
## TLS/SSL
|
||||
|
||||
TLS(Transport Layer Security) is the protocol that encrypts communication with certificates under [PKI](./02_03_pki.md). Originally, the communication encryption protocol was suggested as SSL(Secure Socket Layer) in 1995 by Netscape. Before the emergence of TLS/SSL protocol, all communication in web(or database, some more protocols) used plain text, even it contained sensitive data like password. After 1996, the update of SSL protocol stopped at version 3.0, and it has its own vulnerability. The next version of SSL is TLS. Virtually, a lot of people treat SSL as the same as TLS.
|
||||
|
||||
### Encryption
|
||||
|
||||
Before talking about TLS, it is essential to understand what is encryption. Basically, encryption is to hide plain data using algorithm and key mathematically. Encryption can categorized by various criteria, but in this case focusing on key.
|
||||
|
||||
#### Symmetric key encryption
|
||||
|
||||
This way needs only one key to encrypt and decrypt. Just because using one key to encrypt and decrypt, it doesn't require complex calculation. This algorithm is fast, and easy to handle huge data. However, the key is only one so for security the key should be protected.
|
||||
|
||||
#### Asymmetric key encryption
|
||||
|
||||
This way needs two keys to encrypt and decrypt. It means the key for encryption and the key for decryption are different. This uses very complex algorithm to seperate keys. It makes this slow and hard to handle huge data. However, the keys are devided so, one key can be publically shared.
|
||||
|
||||
### Principle of TLS
|
||||
|
||||
The PKI is necessary to use TLS communication. TLS uses both of algorithm to encrypt the communication. When the communication were encrypted, the key should be existed. For this process(to generate key for encryption), server needs X.509 certificate. The certificate contains data of server's public key, domain, and extra informations. To start communication, the server and the client conduct specific process, called `TLS-handshake`. A client sends the request to start communication. The server accepts this request, and negotiates the protocol what they will use in communication. After negotiation, they start to process to generate symmetric key for communication. In this part, there are two protocol to generate symmetric key: RSA and Diffie-hellman.
|
||||
|
||||
#### RSA (Legacy way)
|
||||
|
||||
In this process server and client uses server's public key and private key. A client generates metadata (Pre-master secret) to generate symmetric key. Metadata is encrypted with server's public key and sent to the server. The server decrypts the data from a client with its private key. They uses this data to generate symmetric key. When this process ends, they share the same symmetric key to encrypt content of communication.
|
||||
|
||||
- Cons of RSA
|
||||
|
||||
Security strength of RSA depends on the server's private key. The server's private key was taken from whom have the record of past communication, all communication can be decrypted. Because session key itself (precisely, pre-master secret) is in the communication encrypted by server's public key. So, this way can't guarantee Forward Secrecy which means security of previous communication. Additionally, RSA needs high resource to encrypt and decrypt. Therefore, modern internet environment RSA way is not used frequently.
|
||||
|
||||
#### Diffie-Hellman & ECDHE(Modern standard)
|
||||
|
||||
On the other hand, TLS can use Diffie-Hellman algorithm. This process doesn't exchange any clue of session key. In this process, the server and clients generate secret values. And they make specific results from calculation with public parameters. The server's private key signs on its results just for prooving they are not altered and authenticated. When they exchange each other's results, both of them generate the same session key from results and their own secret value. All process is publically open, but except the server and client themselves, nobody can calculate the secret values and session key mathematically. After all process is done, finally the client combines all communication value between server. And it makes hash value of this, and encrypt the hash value with the session key. The client sends it to the server as finished message, and server verify this. This key is not permanent, it is temporary. There is no encryption way, so even if hacker could get the secret key of server, he wouldn't know what was the session key.
|
||||
|
||||
- Pros of DHE & ECDHE
|
||||
|
||||
On the contrary with RSA case, security strength of DHE & ECDHE doesn't depend on some specific key. Because when it generate session key, the server and client don't send any sensitive values (like pre-master secret) in this protocol. What they send to each other is open values. The server's public key is used for only signing. So, every session key can never be existed after each session; PFS (Perfect Forward Secrecy). Additionally, Elliptic curve way can provide the same strength with shorter key comparing RSA.
|
||||
|
||||
The detail of `TLS-handshake` process is below.
|
||||
|
||||
### Detail of TLS handshake
|
||||
|
||||
#### Start
|
||||
|
||||
- Client hello
|
||||
|
||||
The client sends the request to server with information including SNI (Server Name Indication), TLS version, cipher suite, and client random. SNI is the server's domain information to access. cipher suite is the protocol of encryption, and client random will be used to generate session key. Basically, the content of `client hello` isn't encrypted. It has a problem that ISP(Internet Service Provider) or government can conduct sniffing attack to the Client hello packet. To solve this problem, ECH (Encrypted Client Hello) was developed. However, ECH needs the support of DNS server, browser(host), and server itself. Today, a lot of servers and DNS servers don't support ECH, so it is hard to apply ECH in every environment.
|
||||
|
||||
- Server hello
|
||||
|
||||
When the server receives clients request, it designate how to communicate from the list of client hello. The server sends to the client with the information of what protocol, TLS version, cipher suite to use, and certificate, and server random. The way of encryption: RSA, DHE, ECDHE are including in the cipher suite.
|
||||
|
||||
#### RSA
|
||||
|
||||
When the server and client decide to use RSA way, server sends certificate including public key to the client.
|
||||
|
||||
- Client key exchange
|
||||
|
||||
The client verifies `Server hello` with server's certificate using client's trusted CA list. The certificate is valid then the client generates `Pre-master secret` and encrypts this value with server's public key to send to the server.
|
||||
|
||||
- Session key generation
|
||||
|
||||
The server recieves `pre-master secret` encrypted by its public key that only server (who knows the private key of server) can decrypt. When the server decrypts this value successfully, the server and client knows three values: `Client random`, `Server random`, and `Pre-master secret`. The server and client generate `Session key` from these values individually.
|
||||
|
||||
#### DHE or ECDHE
|
||||
|
||||
DHE and ECDHE follows the exactly same principle of Diffie-hellman algorithm. However, they have a difference of basement in mathematic. DHE is using discrete logarithm, but ECDHE is using elliptic curve discrete logrithm. This is a mathametical topic, so this is skipped. Just important thing is ECDHE is more efficient than RSA or DHE.
|
||||
|
||||
> Key length to guarantee the same level of security:
|
||||
>
|
||||
> - RSA/DHE: above 2048 bit
|
||||
> - ECDHE: above 256 bit
|
||||
|
||||
- Server key exchange
|
||||
|
||||
The server generate the pair of temporary key based on its own principal. The server sends public temporary key to the client. The public temporary key is signed by server's private key.
|
||||
|
||||
- Client key exchange
|
||||
|
||||
The client verify server's temporary public key with server's public key. Simultaneously, client also generate the pair of temporary key based on its own principal. The client sends its public temporary key to the server.
|
||||
|
||||
- Session key generation
|
||||
|
||||
Both of them have their pair of temporary key, and other's temporary public key. They generate the same `Pre-master secret` from their temporary secret key and other's temporary public key. This process has no communication, they calculate the same `Pre-master secret` from themselves. When they have `Pre-master secret`, they generate session key from pre-master secret and client random and server random.
|
||||
|
||||
#### Finish
|
||||
|
||||
After all process is done, the server and client both sends the `Finished message` encrypted by session key. When they can decrypt this messages, the session key is generated properly. They can start communication securely with session key.
|
||||
|
||||
### TLS 1.3
|
||||
|
||||
Current standard TLS 1.3 changed handshake process as 1-RTT(1 Round Trip Time) and there are no longer available to use RSA way (Legacy).
|
||||
@@ -0,0 +1,131 @@
|
||||
Tags: #theory, #network, #security, #protocol
|
||||
|
||||
## SSO (Single Sign On)
|
||||
|
||||
When someone wants to use some services, usually they have to identify themselves to the services. The service verifies who you are (authentication), and what you can do (authorization). Originally, each service has their own authentication and authorization system. However, in modern internet environment, a lot of services are organically connected to each other. It is common for one provider to operate various services. So, it is very inefficient and complex to operate authentication and authorization system like this way. To solve this problem, the concept of SSO which is to centralize all services' authentication and authorization system at once emerged.
|
||||
|
||||
### LDAP (Lightweight Directory Access Protocol)
|
||||
|
||||
To centralize authentication and authorization system, it is necessary to kind of central database of user list naturally. Fortunately, there was a very good and suitable protocol already; LDAP. LDAP was originally suggested to replace DAP (Directory Access Protocol) for X.500 in [PKI](./02_03_pki.md). Even though X.500 didn't materialized, the potentiality of LDAP was selected to materialize SSO. Because LDAP's structure which was designed for manage certificates itself allows to manage user and its authorization. In modern internet environment, many services don't usually use LDAP itself directly in SSO anymore, but it is still used as database of user and their authorization.
|
||||
|
||||
#### Structrue of LDAP
|
||||
|
||||
LLDAP will be used in this homelab. It is the easiest way to understand LDAP is comparing each part of LDAP with filesystem. Because LDAP itself is fundamentally the database which has the tree structure like filesystem.
|
||||
|
||||
##### DN (Distinguished Name)
|
||||
|
||||
DN is the unique path of some specific entry in tree. It is like an absolute path in filesystem.
|
||||
|
||||
- Example:
|
||||
- `uid=admin,ou=people,dc=ilnmors,dc=internal`
|
||||
- `uid=authelia,ou=people,dc=ilnmors,dc=internal`
|
||||
|
||||
##### Base DN (Base Distinguished Name)
|
||||
|
||||
Base DN is the root of the tree of LDAP. it is like root path of filesystem `/`. All the actions such as search or work are started from Base DN.
|
||||
|
||||
- LLDAP setting
|
||||
- Environment.="LLDAP_LDAP_BASE_DN=dc=ilnmors,dc=internal"
|
||||
- It means, the domain 'ilnmors.internal' will be the Base DN (the root of tree).
|
||||
|
||||
##### Components of DN
|
||||
|
||||
DN has components what has ordinary order. The order of these components are `cn(or uid)`, `ou`, `dc`. They are special attributes, which makes DN.
|
||||
|
||||
- cn (Common name): The name of object. It is like a file itself in filesystem.
|
||||
- ou (Organizational Unit): The name of the container which contain entries. It is like a folder in filesystem.
|
||||
- dc (domain component): The domain components, Usually, it devides the full domain `ilnmors.internal` as `dc=ilnmors`, `dc=internal`.
|
||||
|
||||
##### Object (or Entry)
|
||||
|
||||
Object is the real data item the DN defines, like file or folder itself in filesystem.
|
||||
|
||||
##### ObjectClass
|
||||
|
||||
This is the template or blueprint of object. ObjectClass defines what is this object; Either a person or a group, or an organization unit.
|
||||
|
||||
- What is this object; user (cn or uid) or group (ou).
|
||||
- What is the attribute object must have, and might have.
|
||||
- Example:
|
||||
- `ObjectClass: person`: This must have `sn` and `cn` attributes.
|
||||
- `ObjectClass: organizationalPerson`: This is inherited the `ObjectClass: person`, and it can have `mail` or `number` attributes.
|
||||
- LLDAP uses standard ObjectClass: `person`, `groupOfNames`.
|
||||
|
||||
##### Attribute
|
||||
|
||||
Attribute is the value of object, like the content of file or folder in filesystem. It is saved as the pairs of key-value. This values are following the definition of ObjectClass.
|
||||
|
||||
- Example
|
||||
- `uid=user1` has the value following `objectClass: person`, `objectClass: organizationalPerson`
|
||||
- It must have `sn`, and `cn`
|
||||
- It can have `mail`, or `number`
|
||||
- It can have special attribute `memberOf: cn=admins,ou=admin_group, ...` (The attribute that shows the group the user belongs to)
|
||||
|
||||
### IdP (Identity Provider)
|
||||
|
||||
Many of modern services, SP (Service provider), use SAML (Security Assertion Markup Language) or OIDC (OAuth 2.0/OpenID Connect) protocol to implement SSO. Originally, each service had to send request of authentication to central server (which can be LDAP or else) individually to implement SSO. It means each service should protect the sensitive data itself, and some services which have vulnerability can threaten all system. This is why many of modern services use and support SAML or OIDC protocols. The IdP is needed to use these protocols. The IdP acts as the agent of all services to substitute authentication process on their behalf. Only IdP can access the real database, and all services trust IdP's authentication in this model.
|
||||
|
||||
#### SAML (Security Assertion Markup Language)
|
||||
|
||||
SAML protocol was developed in 2001 for SSO. This protocol works on XML (eXtensible Markup Language) format which can meet complex security requirements in enterprise environments. It has been used for a long time, it makes this protocol very stable. However, XML itself is a complex and heavy format. This fact makes the protocol complex and heavy to use in common and small environment. There is the process of SAML below.
|
||||
|
||||
- Start
|
||||
|
||||
User sends the request to the service. When SP recieves the request, it redirect the request to IdP.
|
||||
|
||||
- Authentication
|
||||
|
||||
IdP asks the login information to the user and it authenticate user from database (like LDAP). When authentication process succeed, IdP generates SAML assertion including user's identity based on XML with sign to ensure integrity. IdP sends the this assertion to SP via user's browser.
|
||||
|
||||
- Finish
|
||||
|
||||
SP receives the assertion from IdP via user's browser, it verify the assertion. When it is valid, they allow login.
|
||||
|
||||
#### OIDC (OpenID Connect) and OAuth 2.0
|
||||
|
||||
OIDC was developed in 2014, it is newer than SAML. This is an authentication layer on OAuth 2.0 protocol. Basically, OAuth 2.0 is for authorization, and OIDC is for authentication. OAuth 2.0 protocol works on JSON/REST format, especially JWT (JSON Web Token) which is lighter and simpler than XML. OIDC is latest standard of SSO, it supports social login, and friendly to API. These features makes this protocol use on small and personal environment easily. There is the process of OIDC below.
|
||||
|
||||
- Start
|
||||
|
||||
User sends the requset to the service (in OIDC, RP; Relying Party). When RP receives the request, it redirects to IdP (in OIDC, OP; OpenID Provider).
|
||||
|
||||
- Authentication
|
||||
|
||||
IdP (OP) asks login information, and simultaneously asks permission to provide the information to RP. After getting information and permission, IdP generates two tokens which are for different purposes. One is an ID token (JWT) which contains user identity for authentication, the other is an access token for authorization. IdP sends these token to RP. The differences between SAML are what data format does protocol use, and whether IdP gets permission or not.
|
||||
|
||||
- Finish
|
||||
|
||||
RP recieves the tokens from IdP, and it verify the tokens. When it is valid, they allow login.
|
||||
|
||||
#### Reverse proxy
|
||||
|
||||
When application doesn't support SSO, then you can use reverse proxy as the door of SSO using `Forward Authentication`. Usually, every web packet pass through reverse proxy in modern internet environment. Therefore, reverse proxy (i.e. Caddy) can intercepts the packets and force them to SSO from IdP or OP (or LDAP itself) before they reach to the application.
|
||||
|
||||
- Header based
|
||||
|
||||
When Authelia success to authenticate someone, Caddy sends specific header which contains user information like `X-Forwarded-User: A`. Application gets this header, it automatically allows login to A. However, it is needed the ID in the application manually.
|
||||
|
||||
> If hacker can access to application without reverse proxy, it can make X header to fake application. In this homelab, all access towards application will be limited by iptables from reverse proxy or sidecar reverse proxy.
|
||||
|
||||
- LDAP based
|
||||
|
||||
LLDAP will be a light and simple LDAP server. Authelia supports OIDC OP(idP) based on external LDAP server. LLDAP server will becom the external LDAP server for Authelia
|
||||
|
||||
|
||||
---
|
||||
## Example of Authelia flow
|
||||
|
||||
### Flow
|
||||
|
||||
- Define the user and group in LLDAP server
|
||||
|
||||
#### OIDC supported app
|
||||
|
||||
- At the service, choose login way as Authelia (OP)
|
||||
- Login at the Authelia.
|
||||
- Authelia access to LLDAP server to authenticate the user based on LDAP.
|
||||
- Login succeed, Authelia generate token and Service turst OP's token, and allow login (authentication and authorization)
|
||||
|
||||
#### Non-OIDC supported app
|
||||
|
||||
- Foward_Auth function with Authelia.
|
||||
@@ -0,0 +1,242 @@
|
||||
Tags: #theory, #network, #protocol
|
||||
|
||||
## Email service
|
||||
|
||||
Email is the mail service online via the internet. ARPANET was developed in 1969, since then there has been many attempts to send messages via the internet. The mail which uses `@` character in 1971 and `SMTP(Simple Mail transfer Protocol)` was developed to standardize various ways to email.
|
||||
|
||||
### Component of Email service
|
||||
|
||||
#### Address
|
||||
|
||||
Basically, Email address has format like this. `local-parts@domain`. `local-parts` is identifier, and `domain` is service provider's domain. Following RFC 5321, `domain` doesn't distinguish it upper or lower case. `local-parts` must distinguish them, but practically they doesn't.
|
||||
|
||||
#### MUA (Mail User Agent)
|
||||
|
||||
MUA is the client of Email. The user can write Email, or read the Email which they got recieved. For instance, Outlook, Thunderbird, etc.
|
||||
|
||||
#### MTA (Mail Transfer Agent)
|
||||
|
||||
This is the essential part of Email service. MTA transpers the mail to other MTA or MDA. For instance, Postfix, sendmail, Exim, etc.
|
||||
|
||||
#### MDA (Mail Delivery Agent)
|
||||
|
||||
MDA recieves the mail from MTA, and it store the mail on receivers' mailbox. Sometimes, it is combined MTA or IMAP/POP3 servers. For instance, Dovecot LDA, Procmail, etc.
|
||||
|
||||
#### Flow of Email service
|
||||
|
||||
- User writes the mail on MUA.
|
||||
- User sends the mail from MUA to MTA using SMTP submission protocol.
|
||||
- MTA checks receiver's domain, and transfer the mail to other MTA which takes charge of that domain.
|
||||
- MTA recieves the mail and sends receiver's MDA.
|
||||
- The receiver's MUA access to the MDA such as IMAP or POP3 server. the receiver can check and read the email on their MUA.
|
||||
|
||||
## Protocols
|
||||
### SMTP (Simple Mail Transfer Protocol)
|
||||
|
||||
SMTP is standard of email transfer protocol internet defined on RFC 5321. This protocol is used when MUA sends the mail to MTA, and MTA sends the mail other MTAs. This protocol takes charge of all process of transportation of the mails.
|
||||
|
||||
#### Detail of SMTP
|
||||
|
||||
##### Start
|
||||
|
||||
- Connection
|
||||
|
||||
The client and server make the connection via SMTP port (25/tcp).
|
||||
|
||||
##### Greeting
|
||||
|
||||
- `220` code
|
||||
|
||||
The server sends `220` code to the client, they are ready.
|
||||
|
||||
- `HELO` or `EHLO`
|
||||
|
||||
The client sends `HELO` or expand version of `HELO`; `EHLO` command to server to introduce itself.
|
||||
|
||||
##### Designate sender and recipient
|
||||
|
||||
Use the command below, they designate sender and recient.
|
||||
|
||||
- `MAIL FROM:<sender@domain>`
|
||||
- `RCPT TO:<recipient@domain>`
|
||||
- If there were various recipients, use this command as much as recipients number.
|
||||
|
||||
##### Transper the mail data
|
||||
|
||||
- `DATA` and `354`
|
||||
|
||||
The client sends `DATA` command to server. After the server responds with `354` code, client sends the data including mail header (From, To, Subject), and content of mail. The end of data is `.`.
|
||||
|
||||
##### End
|
||||
|
||||
- `QUIT`
|
||||
|
||||
The client sends `QUIT` command, the connection is terminated.
|
||||
|
||||
##### Ports
|
||||
|
||||
- `25/tcp`
|
||||
|
||||
Traditional SMTP's standard port. All content using `25/tcp` is not encrypted. Because of security and SPAM problems, a lot of ISP block the `25/tcp` port of common user.
|
||||
|
||||
- `587/tcp` (Submission)
|
||||
|
||||
The standard port of SMTP for encryption. Generally MUA sends the mail to MTA with this port. It is needed to use encrypted connection via `STARTTLS`
|
||||
|
||||
- `465/tcp` (SMTPS)
|
||||
|
||||
This port used to be used for TLS/SSL for SMTP. This is not standard, so it is recommended to use `587/tcp` port for TLS/SSL of SMTP. However, even now this is generally and commonly used.
|
||||
|
||||
##### Security
|
||||
|
||||
SMTP is very old protocol, and this protocol use plain data. It is recommended to use `STARTTLS` or `SMTPS` to encrypt data for security.
|
||||
|
||||
- `SMTPS`
|
||||
|
||||
It uses TLS/SSL from the beginning of connection via `465/tcp`
|
||||
|
||||
- `STARTTLS`
|
||||
|
||||
It uses TLS/SSL after beginning of connection via `587/tcp` as plain data, and start encryption with `STARTTLS` command.
|
||||
|
||||
##### Authentication
|
||||
|
||||
It is necessary to use users' identity like name and password to prevent anyone can sends malicious mail using server. SMTP uses SASL (Simple Authentication and Secuirty Layer) machanism to authenticate its users.
|
||||
|
||||
##### Relay
|
||||
|
||||
MTA has to send the mail to the other MTA for guarantee the mail can arrive the recipitent. MTA uses `relay` function for this. Make sure to allow this function for authenticated user or trusted network to prevent malicious usage.
|
||||
|
||||
### IMAP (Internet Message Access Protocol)
|
||||
|
||||
IMAP is the protocol to read and manage the mails from remote MDA (mail server). The difference between POP3 is that IMAP can manage the mail and its mailbox remotely even without download. It is defined on RFC 3501.
|
||||
|
||||
#### Detail of IMAP
|
||||
|
||||
IMAP is the protocol to have a communication with various commands while the connection is stable. The client sends specific `tag` in front of command, and the server responds with `tag` to process the actions.
|
||||
|
||||
##### Authentication
|
||||
|
||||
- `LOGIN` or `AUTHETICATE`
|
||||
|
||||
IMAP authenticate the user with `LOGIN` command with ID and password or `AUTHENTICATE` command with SASL.
|
||||
|
||||
##### Mailbox
|
||||
|
||||
- `LIST`
|
||||
- `SELECT`
|
||||
- `CREATE`
|
||||
- `DELETE`
|
||||
- `RENAME`
|
||||
|
||||
##### Mail
|
||||
|
||||
- `FETCH`
|
||||
|
||||
IMAP can take the mail list, the mail itself, or content of the mail, even the attachment in the mail.
|
||||
|
||||
##### Statement
|
||||
|
||||
- `STORE`
|
||||
- `\Seen`
|
||||
- `\Flagged`
|
||||
- `\Answered`
|
||||
- `\Deleted`
|
||||
|
||||
IMAP can set the status flag of mail with command flags.
|
||||
|
||||
|
||||
##### Search
|
||||
|
||||
- `SEARCH`
|
||||
|
||||
IMAP can search the mail with various condition of the mail (Sender, title, contents, date, etc) from server.
|
||||
|
||||
##### Ports
|
||||
|
||||
IMAP strongly recommend to use TLS/SSL with `STARTTLS`. Even though the beginning of conversation is not encrypted, TLS/SSL is applied with the `STARTTLS` command.
|
||||
|
||||
- `143/tcp`
|
||||
|
||||
The basic IMAP port. It is mendetory to use `STARTTLS` to use IMAP with this port.
|
||||
|
||||
- `993/tcp` (IMAPS)
|
||||
|
||||
This port uses TLS/SSL in the beginning of communication. It is not a standard but it is generally and commonly use for security.
|
||||
|
||||
##### Synchronization
|
||||
|
||||
IMAP basically server's mail and mail list, so wherever you access the mail you can see the same condition and status of mailbox. When one mail is modified on one device it is applied all devices simultanaeously.
|
||||
|
||||
- `IDLE`
|
||||
|
||||
This command supports to maintain connection between server and client, when the new mail comes or the status is changed the client can get notification immediately.
|
||||
|
||||
### POP3 (Post Office Protocol version 3)
|
||||
|
||||
POP3 protocol is basically designed to download the mail on local client from remote mail server. It is defined on RFC 1939. The biggest difference between IMAP and POP3 is, POP3 basically delete the mail at the server after downloading.
|
||||
|
||||
#### Detail of POP3
|
||||
|
||||
##### Authorization
|
||||
|
||||
- `USER` and `PASS`
|
||||
|
||||
The client connect to server and it conduct authentication with `USER` and `PASS` command.
|
||||
|
||||
##### Transcation
|
||||
|
||||
- `STAT`
|
||||
- `LIST`
|
||||
- `RETR <msg_number>`
|
||||
- `DELE <msg_number>`
|
||||
- `RETR`
|
||||
|
||||
POP3 uses various commands to download or delete the mail. It checks the number of mail and size with `STAT`, downloads the mail with `RETR`, deletes the mail with `DELE`, and save the mail on client with `RETR`.
|
||||
|
||||
##### Update
|
||||
|
||||
When the client sends `QUIT` command, then server deletes the mails which have `DELE` marks from server and terminate the connection.
|
||||
|
||||
##### Ports
|
||||
|
||||
POP3 strongly recommend to use TLS/SSL with `STARTTLS`. Even though the beginning of conversation is not encrypted, TLS/SSL is applied with the `STARTTLS` command.
|
||||
|
||||
- `110/tcp`
|
||||
|
||||
The basic port of POP3. It is mendetory to use `STARTTLS` to use IMAP with this port.
|
||||
|
||||
- `995/tcp` (POP3S)
|
||||
|
||||
This port uses TLS/SSL in the beginning of communication. It is not a standard but it is generally and commonly use for security.
|
||||
|
||||
##### Simplity and locality
|
||||
|
||||
POP3 basically delete the mail from mail server, the mail is only on the local client. However, it doesn't require the complex features like IMAP, it can have simplity.
|
||||
|
||||
---
|
||||
|
||||
## local mail service in homelab
|
||||
|
||||
### SMTP server (MTA)
|
||||
|
||||
#### Postfix
|
||||
|
||||
Postfix will be used as MTA which takes charge of `@ilnmors.internal` domain. However, Postfix in this homelab will never open towards WAN environment. It works as local private MTA. The internal services (Gitea, OPNsense, Prometheus, etc) will sends the mail via `587/tcp` to Postfix. When it needs to send mail towards WAN, it will use `relayhost` function and external Email services such as Google or Naver, etc. `relayhost` makes postfix as one of a `client` not a `MTA`. It means, administrator never takes care about IP reputation or SPAM problems. WAN area's `MTA` function is delegated to public mail service providers.
|
||||
|
||||
### IMAP/POP3 server (MDA)
|
||||
|
||||
#### Dovecot
|
||||
|
||||
Dovecot will be used as IMAP server of local private MTA; Postfix. The user can use MUA (Thunderbird, Outlook, or mail application, even Roundcube webmail) to access the private mail `
|
||||
`@ilnmors.internal` via Dovecot. The user will ues `993/tcp` to access Dovecot, and Postfix store the mails on Dovecot.
|
||||
|
||||
#### mbsync
|
||||
|
||||
mbsync will be used as IMAP client of public MTA; Google or Naver. This will fetch public mail `@external-domain.com` to local Postfix from public mail service provider, and eventually the user can access the mail on Dovecot. However, it is important not to delete the mails from public mail servers with proper configuration.
|
||||
|
||||
### MUA
|
||||
|
||||
#### SnappyMail web mail
|
||||
|
||||
This will be used as MUA server on `app` server to access all mails at the same space.
|
||||
@@ -0,0 +1,657 @@
|
||||
Tags: #common, #configuration, #os
|
||||
|
||||
## Installation
|
||||
|
||||
### General setting
|
||||
|
||||
- Language: English - English
|
||||
- Location: Other > Asia > South Korea
|
||||
- Locale: United State - en_US.UTF-8
|
||||
- Keymap to use: American English
|
||||
|
||||
### Network setting
|
||||
|
||||
#### Auto Configuration
|
||||
|
||||
- Auto Configuration: Using DHCP
|
||||
|
||||
#### Manual configuration
|
||||
|
||||
> `net` server should be configured manually - It has no private DHCP server and DNS server
|
||||
|
||||
- `cancel` auto configuration is needed
|
||||
- IP address: 192.168.10.11/24
|
||||
- Gateway: 192.168.10.1
|
||||
- Name server address: 1.1.1.2
|
||||
> After AdGuard home and BIND are set, change 192.168.10.11 on `/etc/resolv.conf`
|
||||
|
||||
#### Common setting
|
||||
|
||||
- Hostname: \[vmm, net, auth, dev, app\]
|
||||
- Domain: ilnmors.internal
|
||||
|
||||
### User setting
|
||||
|
||||
- Root Password
|
||||
- User name: \[vmm, net, auth, dev, app\]
|
||||
- User account: \[vmm, net, auth, dev, app\]
|
||||
- User Password
|
||||
|
||||
### Partition setting
|
||||
|
||||
- Partitioning method: Manual
|
||||
|
||||
#### Common
|
||||
|
||||
- 512MiB - EFI system partition (Boot flag: on)
|
||||
- 1GiB - Ext4 Journaling (Mount: `/boot`)
|
||||
|
||||
#### Hypervisor
|
||||
|
||||
- 780GiB - Physical volume for LVM
|
||||
- Configure the Logical Volume Manager
|
||||
- Create volume group - vmm
|
||||
- Create logical volume - vmm - \[root, swap, data\]
|
||||
- Finish
|
||||
- vmm-root: 64GiB - Ext Journaling (Mount: `/`)
|
||||
- vmm-swap: 16GiB - SWAP area
|
||||
- vmm-libvirt: 700GiB - Ext4 Journaling (Mount: `/var/lib/libvirt`)
|
||||
|
||||
#### VM servers
|
||||
|
||||
- 32GiB(net), 64GiB(auth), 256GiB(dev,app) - Ext4 Journaling(mount: /)
|
||||
> Don't set swap partition, in qcow2 it is not effective but causes over head(CoW)
|
||||
|
||||
#### Application server
|
||||
|
||||
- Don't set HDD's partition
|
||||
- Each HDD (2TB)
|
||||
> btrfs supports RAID itself. After installing, set RAID10. These will be set with `fdisk`
|
||||
|
||||
### Debian package manager setting
|
||||
|
||||
- Scan extra installation media: no
|
||||
- Mirror country: South Korea
|
||||
- Archive mirror: deb.debian.org
|
||||
- Proxy: \[blank\]
|
||||
- Popularity-contest: no
|
||||
|
||||
### Installing packages setting
|
||||
|
||||
- \[\*\] SSH server
|
||||
- \[\*\] Standard system utilities
|
||||
|
||||
## Environment configuration
|
||||
|
||||
### Package configuration
|
||||
|
||||
#### Common packages
|
||||
|
||||
```bash
|
||||
apt update && apt upgrade
|
||||
apt install sudo iptables-persistent crowdsec acl
|
||||
```
|
||||
|
||||
- wait-for-it.sh
|
||||
- Download it [here](https://github.com/vishnubob/wait-for-it/blob/master/wait-for-it.sh)
|
||||
- ~/data/config/scripts
|
||||
#### Hypervisor
|
||||
|
||||
```bash
|
||||
apt install qemu-system-x86 ksmtuned libvirt-daemon-system virtinst virt-top ifupdown2 openvswitch-switch
|
||||
|
||||
# ksmtuned automatically adjusts ksm following its usage
|
||||
# `cat /sys/kernel/mm/ksm/pages_sharing` shows the page shared by ksmtuned, result * 4KiB is sharing volume
|
||||
```
|
||||
|
||||
#### VMs
|
||||
|
||||
```bash
|
||||
apt install podman curl jq age
|
||||
|
||||
# app
|
||||
|
||||
apt install btrfs-progs
|
||||
|
||||
# SOPS package
|
||||
curl -LO https://github.com/getsops/sops/releases/download/v3.11.0/sops-v3.11.0.linux.amd64
|
||||
|
||||
mv sops-v3.11.0.linux.amd64 /usr/local/bin/sops
|
||||
|
||||
chmod +x /usr/local/bin/sops
|
||||
# Check update this via Diun
|
||||
```
|
||||
|
||||
### User configuration
|
||||
|
||||
```bash
|
||||
usermod [user_name] -u [uid]
|
||||
|
||||
# Change default group as svadmins
|
||||
groupmod [group_name] -g 2000 -n svadmins
|
||||
chown -R [user_name]:svadmins /home/[user_name]
|
||||
chmod 770 /home/[user_name]
|
||||
usermod -aG sudo [user_name]
|
||||
|
||||
# Hypervisor
|
||||
usermod -aG kvm vmm
|
||||
usermod -aG libvirt vmm
|
||||
|
||||
# After user configuration, proceed all step as user with sudo
|
||||
```
|
||||
|
||||
### Make directory structure
|
||||
|
||||
#### Common
|
||||
|
||||
- /etc/secrets (root:root 711)
|
||||
- /etc/secrets/\$UID (\$UID:root 500, files: $UID:root 400)
|
||||
|
||||
#### Hypervisor
|
||||
|
||||
- (vmm:svadmins 700)
|
||||
- ~/data/config/{scripts,server,services,vms}
|
||||
- ~/data/config/vms/{networks,storages,dumps}
|
||||
- /var/lib/libvirt/images
|
||||
|
||||
#### VMs
|
||||
|
||||
- (\[vms\]:svadmins 700)
|
||||
- ~/data/{config,containers}
|
||||
- ~/data/config/{containers,scripts,secrets,server,services}
|
||||
- ~/data/containers/apps/{certs,etc.}
|
||||
- ~/kopia
|
||||
|
||||
#### Application server
|
||||
|
||||
##### SSD
|
||||
|
||||
- (app:svadmins 700)
|
||||
- ~/data/{config,containers}
|
||||
- ~/data/config/{containers,scripts,secrets,server,services}
|
||||
- ~/data/containers/app/{certs,etc.}
|
||||
- ~/kopia
|
||||
|
||||
##### HDD
|
||||
|
||||
RAID10 HDDs mount on this directory. Following 08_01_app_vm before you set.
|
||||
- (app:svadmins 700)
|
||||
- ~/hdd/data/containers
|
||||
- (app:svadmins 770)
|
||||
- ~/hdd/backups
|
||||
- The scrub timer systemd is required for its integrity.
|
||||
|
||||
### SSH configuration
|
||||
|
||||
|
||||
- File:
|
||||
- /etc/ssh/sshd_config
|
||||
- ~/.ssh/authorized_keys
|
||||
|
||||
```ini
|
||||
# /etc/ssh/sshd_config
|
||||
# key generation
|
||||
ssh-keygen -t ed25519 -f ~/.ssh/key_name -C "comment"
|
||||
# ...
|
||||
PermitRootLogin no
|
||||
# ...
|
||||
```
|
||||
|
||||
```bash
|
||||
# Reload sshd
|
||||
sudo systemctl restart sshd
|
||||
|
||||
# Deploy publicly key for ssh
|
||||
mkdir ~/.ssh && chmod 700 ~/.ssh
|
||||
nano ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys
|
||||
# Paste public key from the client
|
||||
```
|
||||
|
||||
> If the ICMP protocol doesn't work towards Windows, check Windows' firewall inbound `파일 및 프린터 공유(에코 요청 - ICMPv4-In)" and "파일 및 프린터 공유(에코 요청 - ICMPv6-In)` activatation
|
||||
|
||||
### Linger configuration
|
||||
|
||||
```bash
|
||||
sudo loginctl enable-linger [user_name]
|
||||
loginctl show-user $(whoami)
|
||||
# Linger=yes
|
||||
```
|
||||
|
||||
> Linger is necessary to execute services regardless their login session. If linger option were not activated, all the process would be terminated after logout.
|
||||
|
||||
### Network configuration
|
||||
|
||||
- File:
|
||||
- /etc/hosts
|
||||
- /etc/resolv.conf
|
||||
- /etc/network/interfaces
|
||||
|
||||
#### Hostname
|
||||
|
||||
```ini
|
||||
# /etc/hosts
|
||||
[ip_address] [FQDN] [hostname]
|
||||
```
|
||||
|
||||
#### DNS
|
||||
|
||||
- Only for `hypervisor` and `net` which are set manually.
|
||||
|
||||
```ini
|
||||
# /etc/resolv.conf
|
||||
# Before setting local DNS
|
||||
nameserver 1.1.1.2
|
||||
# After setting local DNS (net server)
|
||||
# nameserver 192.168.10.11
|
||||
```
|
||||
|
||||
#### Interface
|
||||
|
||||
- Only for `hypervisor` and `net` which are set manually
|
||||
|
||||
##### Hypervisor
|
||||
|
||||
```ini
|
||||
# /etc/network/interfaces
|
||||
# ifupdown2 and openvswitch-switch are required
|
||||
|
||||
source /etc/network/interfaces.d/*
|
||||
|
||||
# The loopback network interface
|
||||
auto lo
|
||||
iface lo inet loopback
|
||||
|
||||
# The openvswitch virtual bridge interfaces
|
||||
auto ovsbr0
|
||||
iface ovsbr0 inet manual
|
||||
ovs_type OVSBridge
|
||||
ovs_ports enp2s0
|
||||
|
||||
auto ovsbr1
|
||||
iface ovsbr1 inet manual
|
||||
ovs_type OVSBridge
|
||||
ovs_ports enp3s0 vlan1 vlan10
|
||||
|
||||
# The primary network interfaces
|
||||
auto enp2s0
|
||||
iface enp2s0 inet manual
|
||||
ovs_type OVSPort
|
||||
ovs_bridge ovsbr0
|
||||
|
||||
auto enp3s0
|
||||
iface enp3s0 inet manual
|
||||
ovs_type OVSPort
|
||||
ovs_bridge ovsbr1
|
||||
ovs_options vlan_mode=native-untagged tag=1 trunks=10
|
||||
|
||||
# The vlan interfaces
|
||||
auto vlan1
|
||||
iface vlan1 inet static
|
||||
ovs_type OVSIntPort
|
||||
ovs_bridge ovsbr1
|
||||
ovs_options tag=1
|
||||
address 192.168.1.10/24
|
||||
|
||||
|
||||
auto vlan10
|
||||
iface vlan10 inet static
|
||||
ovs_type OVSIntPort
|
||||
ovs_bridge ovsbr1
|
||||
ovs_options tag=10
|
||||
address 192.168.10.10/24
|
||||
gateway 192.168.10.1
|
||||
|
||||
# The primary network interfaces for IPv6
|
||||
# iface enp2s0 inet6 auto
|
||||
# iface enp3s0 inet6 auto
|
||||
# openvswitch vlan options
|
||||
# ovs_options tag=n : access mode
|
||||
# ovs_options trunk=n : trunk mode
|
||||
# ovs_options vlan_mode=native-untagged tag=n trunk=m: native untagged mode
|
||||
```
|
||||
|
||||
> Having two IP addresses in a client can cause some problems. One of them is `Asymmetric routing`. When it gets the packet via vlan10 interface from vlan1 client, the server uses vlan1 interface, because client and server are in the same subnet (L2). It can cause problem, like disconnection usually. There is the solution to solve this.
|
||||
>
|
||||
> - Use `mangle` table in iptables, you can solve this, but it is complex to use.
|
||||
> - Use `IP rule` command, it is easier to set.
|
||||
|
||||
##### net
|
||||
|
||||
```ini
|
||||
# /etc/network/interfaces
|
||||
|
||||
source /etc/network/interfaces.d/*
|
||||
|
||||
# The loopback network interface
|
||||
auto lo
|
||||
iface lo inet loopback
|
||||
|
||||
# The primary network interface
|
||||
auto enp1s0
|
||||
iface enp1s0 inet static
|
||||
address 192.168.10.11/24
|
||||
gateway 192.168.10.1
|
||||
# Don't set dns options. It will be set on /etc/resolv.conf
|
||||
```
|
||||
|
||||
### Hypervisor
|
||||
|
||||
#### VFIO configuration
|
||||
|
||||
##### IOMMU setting
|
||||
|
||||
- File: /etc/default/grub
|
||||
|
||||
```ini
|
||||
# /etc/default/grub
|
||||
GRUB_CMDLINE_LINUX_DEFAULT="quiet intel_iommu=on"
|
||||
# Intel CPU: intel_iommu=on, AMD CPU: amd_iommu=on
|
||||
```
|
||||
|
||||
##### Device setting
|
||||
|
||||
- File: /etc/modprobe.d/vfio.conf
|
||||
|
||||
```bash
|
||||
# Check device
|
||||
lspci -nn | grep -i sata
|
||||
# 04:00.0 STAT controller [0106] ... [1b21:1064]
|
||||
# PCI address: 04:00.00 / Device ID 1b21:1064
|
||||
|
||||
# IOMMU group checking
|
||||
readlink /sys/bus/pci/devices/0000\:04\:00.0/iommu_group
|
||||
# ../../../../kernel/iommu_groups/14
|
||||
|
||||
# The group of PCI device which is needed to passthrough is 14
|
||||
ls /sys/kernel/iommu_groups/devices/14
|
||||
# 04:00.00 # To passthrough device, it has to be only device in its IOMMU group
|
||||
```
|
||||
|
||||
```ini
|
||||
# /etc/modprobe.d/vfio.conf
|
||||
options vfio-pci ids=1b21:1064
|
||||
softdep ahci pre: vfio-pci
|
||||
```
|
||||
|
||||
##### Apply all configuration
|
||||
|
||||
```bash
|
||||
sudo update-grub
|
||||
sudo update-initramfs -u
|
||||
sudo reboot
|
||||
|
||||
# Check IOMMU enable message
|
||||
dmesg | grep -e DMAR -e IOMMU
|
||||
# Check the driver
|
||||
lspci -nnk -d 1b21:1064
|
||||
# Kernel driver in user: vfio-pci
|
||||
```
|
||||
|
||||
### VM servers
|
||||
|
||||
#### Serial console setting
|
||||
|
||||
```bash
|
||||
# It is necessary to connect via ssh to set serial setting
|
||||
sudo systemctl enable getty@ttyS0
|
||||
sudo systemctl start getty@ttyS0
|
||||
```
|
||||
|
||||
#### Secret management
|
||||
|
||||
##### SOPS setting
|
||||
|
||||
- File:
|
||||
- ~/data/config/secrets/age-key.gpg
|
||||
- ~/data/config/secrets/.sops.yaml
|
||||
- ~/data/config/secrets/.secret.yaml
|
||||
- /etc/secrets/\$UID (\$UID:root 500)
|
||||
|
||||
```bash
|
||||
# Generate the key for sops
|
||||
age-keygen -o ~/data/config/secrets/age-key
|
||||
|
||||
# # created: 2025-10-17T13:30:00Z
|
||||
# # public key: age1ql3z7h0cfscg......
|
||||
# AGE-SECRET-KEY-1.....
|
||||
|
||||
# Public key is printed when key generated
|
||||
|
||||
gpg --symmetric age-key && rm age-key
|
||||
> GPG password: password
|
||||
|
||||
nano ~/data/config/secrets/.sops.yaml && chmod 600 ~/data/config/secrets/.sops.yaml
|
||||
```
|
||||
|
||||
```yaml
|
||||
# ~/data/config/secrets/.sops.yaml
|
||||
creation_rules:
|
||||
- path_regex: \.secret\.yaml$
|
||||
age: [public_key]
|
||||
```
|
||||
|
||||
|
||||
##### Create Secret file
|
||||
|
||||
```yaml
|
||||
# ~/data/config/secrets/.secret.yaml
|
||||
# Format of .secret.yaml
|
||||
# app1.env:
|
||||
1SECRET: '1secret'
|
||||
2SECRET: '2secret'
|
||||
|
||||
app1.file: |
|
||||
-----TEXT-AREA-----
|
||||
contents of 3secret
|
||||
-----END-AREA-----
|
||||
|
||||
# app2.env
|
||||
3SECRET: '3secret'
|
||||
4SECRET: '4secret'
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
```bash
|
||||
sops --encrypt --in-place ~/data/config/secrets/.secret.yaml
|
||||
```
|
||||
|
||||
##### Secret scripts
|
||||
|
||||
- File:
|
||||
- ~/data/config/scripts/secrets/edit_secret.sh
|
||||
- ~/data/config/scripts/secrets/extract_secret.sh
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# edit_secret.sh /path/of/secret
|
||||
|
||||
set -e
|
||||
|
||||
KEY_PATH="$HOME/data/config/secrets"
|
||||
SECRET_FILE="$1"
|
||||
|
||||
usage() {
|
||||
echo "Usage: $0 \"/path/of/secret/file\""
|
||||
exit 1
|
||||
}
|
||||
|
||||
|
||||
if [ -z "$SECRET_FILE" -o ! -f "$SECRET_FILE" ]; then
|
||||
echo "Error: Secret file path is needed"
|
||||
usage
|
||||
fi
|
||||
|
||||
|
||||
if [ ! -f "$KEY_PATH/age-key.gpg" ]; then
|
||||
echo "Error: There is no key file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Delete password file after script
|
||||
cleanup() {
|
||||
if [ -f "/run/user/$UID/age-key" ]; then
|
||||
rm -f "/run/user/$UID/age-key"
|
||||
fi
|
||||
}
|
||||
|
||||
trap cleanup EXIT
|
||||
|
||||
|
||||
|
||||
echo -n "Enter GPG passphrase: "
|
||||
read -s GPG_PASSPHRASE
|
||||
echo
|
||||
|
||||
echo "$GPG_PASSPHRASE" | gpg --batch --yes --passphrase-fd 0 \
|
||||
--output "/run/user/$UID/age-key" \
|
||||
--decrypt "$KEY_PATH/age-key.gpg" && \
|
||||
chmod 600 "/run/user/$UID/age-key"
|
||||
|
||||
if [ -z "/run/user/$UID/age-key" ]; then
|
||||
echo "Error: Key file does not exist"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
gpgconf --kill gpg-agent
|
||||
|
||||
SOPS_AGE_KEY="$(cat "/run/user/$UID/age-key")"
|
||||
|
||||
SOPS_AGE_KEY="$SOPS_AGE_KEY" sops "$SECRET_FILE"
|
||||
```
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# extract_secret.sh /path/of/secret (-f|-e <value>)
|
||||
|
||||
set -e
|
||||
|
||||
KEY_PATH="$HOME/data/config/secrets"
|
||||
SECRET_FILE=$1
|
||||
|
||||
# shift the $2 as $1 ($1 < $2)
|
||||
shift
|
||||
|
||||
# usage() function
|
||||
usage() {
|
||||
echo "Usage: $0 \"/path/of/secret/file\" (-f|-e \"yaml section name\")" >&2
|
||||
echo "-f <type name>: Print secret file" >&2
|
||||
echo "-e <type name>: Print secret env file" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
while getopts "f:e:" opt; do
|
||||
case $opt in
|
||||
f)
|
||||
VALUE="$OPTARG"
|
||||
TYPE="FILE"
|
||||
;;
|
||||
e)
|
||||
VALUE="$OPTARG"
|
||||
TYPE="ENV"
|
||||
;;
|
||||
\?) # unknown options
|
||||
echo "Invalid option: -$OPTARG" >&2
|
||||
usage
|
||||
;;
|
||||
:) # parameter required option
|
||||
echo "Option -$OPTARG requires an argument." >&2
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Get option and move to parameters - This has no functional thing, because it only use arguments with parameters
|
||||
shift $((OPTIND - 1))
|
||||
|
||||
# Check necessary options
|
||||
if [ ! -f "$SECRET_FILE" ]; then
|
||||
echo "Error: secret file path is required" >&2
|
||||
usage
|
||||
fi
|
||||
|
||||
if [ -z "$TYPE" ]; then
|
||||
echo "Error: -f or -e option requires" >&2
|
||||
usage
|
||||
fi
|
||||
|
||||
|
||||
if [ ! -f "$KEY_PATH/age-key.gpg" ]; then
|
||||
echo "Error: There is no key file" >&2
|
||||
usage
|
||||
fi
|
||||
|
||||
# Delete password file after script
|
||||
cleanup() {
|
||||
if [ -f "/run/user/$UID/age-key" ]; then
|
||||
rm -f "/run/user/$UID/age-key"
|
||||
fi
|
||||
}
|
||||
|
||||
trap cleanup EXIT
|
||||
|
||||
echo -n "Enter GPG passphrase: " >&2
|
||||
read -s GPG_PASSPHRASE
|
||||
echo >&2
|
||||
|
||||
echo "$GPG_PASSPHRASE" | gpg --batch --yes --passphrase-fd 0 \
|
||||
--output "/run/user/$UID/age-key" \
|
||||
--decrypt "$KEY_PATH/age-key.gpg" && \
|
||||
chmod 600 "/run/user/$UID/age-key"
|
||||
|
||||
if [ ! -f "/run/user/$UID/age-key" ]; then
|
||||
echo "Error: Key file does not exist" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
gpgconf --kill gpg-agent
|
||||
|
||||
SOPS_AGE_KEY="$(cat "/run/user/$UID/age-key")"
|
||||
|
||||
if [ "$TYPE" == "FILE" ]; then
|
||||
if RESULT=$(SOPS_AGE_KEY="$SOPS_AGE_KEY" sops --decrypt --extract "[\"$VALUE\"]" --output-type binary "$SECRET_FILE") ; then
|
||||
echo -n "$RESULT"
|
||||
exit 0
|
||||
else
|
||||
echo "Error: SOPS extract error" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$TYPE" == "ENV" ]; then
|
||||
if RESULT=$(SOPS_AGE_KEY="$SOPS_AGE_KEY" sops --decrypt --extract "[\"$VALUE\"]" --output-type dotenv "$SECRET_FILE") ; then
|
||||
echo -n "$RESULT"
|
||||
exit 0
|
||||
else
|
||||
echo "Error: SOPS extract error" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
```
|
||||
|
||||
##### Secret value management
|
||||
|
||||
- Using `extract_secret.sh`
|
||||
- Inject secret value to `podman secret` or `/etc/secrets/$UID`
|
||||
|
||||
```bash
|
||||
# /etc/secrets/$UID
|
||||
# Before use sudo tee, make sure sudo doesn't need password.
|
||||
# i.e. sudo ps -ef command execute before this command.
|
||||
# Env file
|
||||
extract_secret.sh ~/data/config/secrets/.secret.yaml -e "$value" > /run/user/$UID/tmp.env \
|
||||
&& sudo mv /run/user/$UID/tmp.env /etc/secrets/$UID/"$FILE_NAME" \
|
||||
&& sudo chown $UID:root /etc/secrets/$UID/"$FILE_NAME" \
|
||||
&& sudo chmod 400 /etc/secrets/$UID/"$FILE_NAME"
|
||||
# Normal file
|
||||
extract_secret.sh ~/data/config/secrets/.secret.yaml -f "$value" > /run/user/$UID/tmp.env \
|
||||
&& sudo mv /run/user/$UID/tmp.env /etc/secrets/$UID/"$FILE_NAME" \
|
||||
&& sudo chown $UID:root /etc/secrets/$UID/"$FILE_NAME" \
|
||||
&& sudo chmod 400 /etc/secrets/$UID/"$FILE_NAME"
|
||||
|
||||
# Podman secret
|
||||
# Podman doesn't supports .env file parsing, you have to enroll all values
|
||||
extract_secret.sh ~/data/config/secrets/.secret.yaml -f "$value" | podman secret create "[$FILE_NAME|$ENV_NAME]" -
|
||||
```
|
||||
|
||||
> The reason why apply the secret value manually is its complexity. The `yaml` file can hold a lot of values in one section. It is hard to make the script to deal with a lot of exception and senario. The simple way is the best way.
|
||||
@@ -0,0 +1,273 @@
|
||||
Tags: #common, #configuration, #network, #security
|
||||
|
||||
## iptables
|
||||
|
||||
iptables is the firewall program to manage netfilter which is in the linux kernel. Basically, the iptables' settings are temporary(when you reboot the computer they disappear). So, you can use netfilter-persistent program to make settings permanent. iptables has 3 modes(tables), which are filter, nat, and mangle. And each table has their chain like input, forward or prerouting, etc. iptables' setting is temporary, when the machine reboot, all rules will be reset. After set the rules use `netfilter-persistent save` to make the rules permanently.
|
||||
|
||||
### tables
|
||||
|
||||
#### filter
|
||||
|
||||
filter is the basic and most important table in iptables. Its role is to simply judge, to ACCEPT or DROP the packets. When you use the iptables command without table option (-t), the filter table is default option. There are chians of filter table below.
|
||||
|
||||
- INPUT: check the packets in
|
||||
- OUTPUT: check the packets out
|
||||
- FORWARD: check the packets which are passing through
|
||||
|
||||
#### nat
|
||||
|
||||
nat is the table changing packets' address and port without changing contents of packets. There are chians of nat table below.
|
||||
|
||||
- PREROUTING: change the destination address or port right after packets arrived
|
||||
- POSTROUTING: change the source address or port before packets depart
|
||||
- OUTPUT: change the destination address or port of packets which produced by itself (This doesn't change the source IP; DNAT)
|
||||
|
||||
#### mangle
|
||||
|
||||
mangle is a special table to mark on the packets. It works on the every chain and it works on special purpose like asymmetric routing.
|
||||
|
||||
### grammar
|
||||
|
||||
#### Commands
|
||||
|
||||
- -A \[--append\]: create the new rules
|
||||
- -C \[--check\]: check the packets
|
||||
- -D \[--delete\]: delete the rules
|
||||
- -F \[--flush\]: delete all rules from the chain
|
||||
- -I \[--insert\]: Insert the new rules
|
||||
- -L \[--list\]: print the rules
|
||||
- -N \[--new\]: create the new chain
|
||||
- -P \[--policy\]: change the default policy
|
||||
- -R \[--replace\]: change the rules as a new rule
|
||||
- -X \[--delete-chain\]: delete chain
|
||||
- -Z \[--zero\]: reset the packet and byte counter value of all chain
|
||||
|
||||
#### match
|
||||
|
||||
- -s \[--source\]: designate source ip address or networks
|
||||
- -d \[--destination\]: designate destination ip address or networks
|
||||
- -p \[--protocol\]: match protocol(tcp/udp/icmp.. etc.)
|
||||
- --dport: designate specific protocol number (When the protocol is already defined - tcp or udp)
|
||||
- --syn: match syn packets. when starting new TCP connection, apply the rule.
|
||||
- -i \[--in-interface\]: input interface
|
||||
- -o \[--out-interface\]: output interface
|
||||
- --comment: comment(max 256byte)
|
||||
- -f \[--fragment\]
|
||||
- -t \[--table\]: designate table set(default: filter)
|
||||
- -j \[--jump\]: designate targets
|
||||
- -m \[--match\]: match with specific module
|
||||
- conntrack --ctstate: current linked connection
|
||||
|
||||
#### target
|
||||
|
||||
- ACCEPT: allow packets
|
||||
- DROP: deny packets without response (Hide server existence)
|
||||
- REJECT: deny packets with response (Show server existance)
|
||||
- LOG: log the packets on syslog
|
||||
- RETURN: stop current rules, and return to the previous chain
|
||||
|
||||
#### Command
|
||||
|
||||
```bash
|
||||
iptables -L -v -n # Print all rules in filter table
|
||||
iptables -L -v -n -t nat # Print all rules in nat table
|
||||
```
|
||||
|
||||
## netfilter-persistent
|
||||
|
||||
### Save the rules
|
||||
|
||||
```bash
|
||||
sudo netfilter-persistent save
|
||||
```
|
||||
|
||||
- File:
|
||||
- /etc/iptables/rules.v4
|
||||
- /etc/iptables/rules.v6
|
||||
|
||||
### Reload the rules (manual)
|
||||
|
||||
```bash
|
||||
# Edit the file
|
||||
# Test
|
||||
sudo bash -c 'iptables-restore --test < /etc/iptables/rules.v4'
|
||||
# If there were no message, it would have no error
|
||||
sudo netfilter-persistent start
|
||||
# or
|
||||
sudo netfilter-persistent reload
|
||||
```
|
||||
|
||||
### Rule files
|
||||
|
||||
- File: /etc/iptables/rules.v4
|
||||
#### Hypervisor (vmm)
|
||||
|
||||
```ini
|
||||
*filter
|
||||
:INPUT DROP [0:0]
|
||||
:FORWARD DROP [0:0]
|
||||
:OUTPUT ACCEPT [0:0]
|
||||
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -m comment --comment "allow established connection" -j ACCEPT
|
||||
-A INPUT -i lo -m comment --comment "allow local connection" -j ACCEPT
|
||||
-A INPUT -p icmp -m comment --comment "allow ICMP connection" -j ACCEPT
|
||||
-A INPUT -s 192.168.1.11/32 -p tcp -m tcp --dport 22 -m comment --comment "allow emergemcy LAN console ssh connection" -j ACCEPT
|
||||
-A INPUT -s 10.10.10.2/32 -p tcp -m tcp --dport 22 -m comment --comment "allow vpn console ssh connection" -j ACCEPT
|
||||
-A INPUT -s 10.10.10.3/32 -p tcp -m tcp --dport 22 -m comment --comment "allow vpn console ssh connection" -j ACCEPT
|
||||
-A INPUT -s 10.10.10.4/32 -p tcp -m tcp --dport 22 -m comment --comment "allow vpn console ssh connection" -j ACCEPT
|
||||
-A INPUT -s 192.168.10.13/32 -p tcp -m tcp --dport 22 -m comment --comment "allow code-server ssh connection" -j ACCEPT
|
||||
COMMIT
|
||||
```
|
||||
|
||||
#### net
|
||||
|
||||
```ini
|
||||
*filter
|
||||
:INPUT DROP [0:0]
|
||||
:FORWARD DROP [0:0]
|
||||
:OUTPUT ACCEPT [0:0]
|
||||
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -m comment --comment "allow established connection" -j ACCEPT
|
||||
-A INPUT -i lo -m comment --comment "allow local connection" -j ACCEPT
|
||||
-A INPUT -p icmp -m comment --comment "allow ICMP connection" -j ACCEPT
|
||||
-A INPUT -s 10.10.10.2/32 -p tcp -m tcp --dport 22 -m comment --comment "allow vpn console ssh connection" -j ACCEPT
|
||||
-A INPUT -s 10.10.10.3/32 -p tcp -m tcp --dport 22 -m comment --comment "allow vpn console ssh connection" -j ACCEPT
|
||||
-A INPUT -s 10.10.10.4/32 -p tcp -m tcp --dport 22 -m comment --comment "allow vpn console ssh connection" -j ACCEPT
|
||||
-A INPUT -s 192.168.10.1/32 -p tcp -m tcp --dport 22 -m comment --comment "allow OPNsense ssh connection for ACME update" -j ACCEPT
|
||||
-A INPUT -s 192.168.10.10/32 -p tcp -m tcp --dport 22 -m comment --comment "allow hypervisor ssh connection" -j ACCEPT
|
||||
-A INPUT -s 192.168.10.13/32 -p tcp -m tcp --dport 22 -m comment --comment "allow code-server ssh connection" -j ACCEPT
|
||||
-A INPUT -p tcp -m tcp --dport 2053 -m conntrack --ctorigdstport 53 -m comment --comment "allow tcp DNS connection which is only prerouted from 53" -j ACCEPT
|
||||
-A INPUT -p udp -m udp --dport 2053 -m conntrack --ctorigdstport 53 -m comment --comment "allow udp DNS connection which is only prerouted from 53" -j ACCEPT
|
||||
-A INPUT -p tcp -m tcp --dport 2443 -m conntrack --ctorigdstport 443 -m comment --comment "allow tcp DoH(https) connection which is only prerouted from 443" -j ACCEPT
|
||||
-A INPUT -p udp -m udp --dport 2443 -m conntrack --ctorigdstport 443 -m comment --comment "allow udp DoH(https) connection which is only prerouted from 443" -j ACCEPT
|
||||
-A INPUT -s 192.168.10.1/32 -p tcp -m tcp --dport 2253 -m comment --comment "allow opnsense tcp nsupdate connection" -j ACCEPT
|
||||
-A INPUT -s 192.168.10.1/32 -p udp -m udp --dport 2253 -m comment --comment "allow opnsense udp nsupdate connection" -j ACCEPT
|
||||
-A INPUT -s 192.168.10.12/32 -p tcp -m tcp --dport 2253 -m comment --comment "allow auth tcp nsupdate connection" -j ACCEPT
|
||||
-A INPUT -s 192.168.10.12/32 -p udp -m udp --dport 2253 -m comment --comment "allow auth udp nsupdate connection" -j ACCEPT
|
||||
-A INPUT -s 192.168.10.13/32 -p tcp -m tcp --dport 2253 -m comment --comment "allow dev tcp nsupdate connection" -j ACCEPT
|
||||
-A INPUT -s 192.168.10.13/32 -p udp -m udp --dport 2253 -m comment --comment "allow dev udp nsupdate connection" -j ACCEPT
|
||||
-A INPUT -s 192.168.10.14/32 -p tcp -m tcp --dport 2253 -m comment --comment "allow app tcp nsupdate connection" -j ACCEPT
|
||||
-A INPUT -s 192.168.10.14/32 -p udp -m udp --dport 2253 -m comment --comment "allow app udp nsupdate connection" -j ACCEPT
|
||||
COMMIT
|
||||
*nat
|
||||
:PREROUTING ACCEPT [0:0]
|
||||
:INPUT ACCEPT [0:0]
|
||||
:OUTPUT ACCEPT [0:0]
|
||||
:POSTROUTING ACCEPT [0:0]
|
||||
-A PREROUTING -p tcp -m tcp --dport 53 -m comment --comment "allow and preroute tcp DNS connection 53 to 2053" -j REDIRECT --to-ports 2053
|
||||
-A PREROUTING -p udp -m udp --dport 53 -m comment --comment "allow and preroute udp DNS connection 53 to 2053" -j REDIRECT --to-ports 2053
|
||||
-A PREROUTING -p tcp -m tcp --dport 443 -m comment --comment "allow and preroute tcp DoH(https) connection 443 to 2443" -j REDIRECT --to-ports 2443
|
||||
-A PREROUTING -p udp -m udp --dport 443 -m comment --comment "allow and preroute udp DoH(https) connection 443 to 2443" -j REDIRECT --to-ports 2443
|
||||
-A OUTPUT -d 127.0.0.1/32 -p tcp -m tcp --dport 53 -m comment --comment "NAT local tcp DNS connection 53 to 2053" -j REDIRECT --to-ports 2053
|
||||
-A OUTPUT -d 127.0.0.1/32 -p udp -m udp --dport 53 -m comment --comment "NAT local udp DNS connection 53 to 2053" -j REDIRECT --to-ports 2053
|
||||
-A OUTPUT -d 192.168.10.11/32 -p tcp -m tcp --dport 53 -m comment --comment "NAT local tcp DNS connection 53 to 2053" -j REDIRECT --to-ports 2053
|
||||
-A OUTPUT -d 192.168.10.11/32 -p udp -m udp --dport 53 -m comment --comment "NAT local udp DNS connection 53 to 2053" -j REDIRECT --to-ports 2053
|
||||
-A OUTPUT -d 127.0.0.1/32 -p tcp -m tcp --dport 443 -m comment --comment "NAT local tcp DoH(https) connection 443 to 2443" -j REDIRECT --to-ports 2443
|
||||
-A OUTPUT -d 127.0.0.1/32 -p udp -m udp --dport 443 -m comment --comment "NAT local udp DoH(https) connection 443 to 2443" -j REDIRECT --to-ports 2443
|
||||
-A OUTPUT -d 192.168.10.11/32 -p tcp -m tcp --dport 443 -m comment --comment "NAT local tcp DoH(https) connection 443 to 2443" -j REDIRECT --to-ports 2443
|
||||
-A OUTPUT -d 192.168.10.11/32 -p udp -m udp --dport 443 -m comment --comment "NAT local udp DoH(https) connection 443 to 2443" -j REDIRECT --to-ports 2443
|
||||
COMMIT
|
||||
```
|
||||
|
||||
#### auth
|
||||
|
||||
```ini
|
||||
*filter
|
||||
:INPUT DROP [0:0]
|
||||
:FORWARD DROP [0:0]
|
||||
:OUTPUT ACCEPT [204:15800]
|
||||
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -m comment --comment "allow established connection" -j ACCEPT
|
||||
-A INPUT -i lo -m comment --comment "allow local connection" -j ACCEPT
|
||||
-A INPUT -p icmp -m comment --comment "allow ICMP connection" -j ACCEPT
|
||||
-A INPUT -s 10.10.10.2/32 -p tcp -m tcp --dport 22 -m comment --comment "allow vpn console ssh connection" -j ACCEPT
|
||||
-A INPUT -s 10.10.10.3/32 -p tcp -m tcp --dport 22 -m comment --comment "allow vpn console ssh connection" -j ACCEPT
|
||||
-A INPUT -s 10.10.10.4/32 -p tcp -m tcp --dport 22 -m comment --comment "allow vpn console ssh connection" -j ACCEPT
|
||||
-A INPUT -s 192.168.10.1/32 -p tcp -m tcp --dport 22 -m comment --comment "allow OPNsense ssh connection for ACME update" -j ACCEPT
|
||||
-A INPUT -s 192.168.10.10/32 -p tcp -m tcp --dport 22 -m comment --comment "allow hypervisor ssh connection" -j ACCEPT
|
||||
-A INPUT -s 192.168.10.13/32 -p tcp -m tcp --dport 22 -m comment --comment "allow code-server ssh connection" -j ACCEPT
|
||||
-A INPUT -s 192.168.10.1/32 -p tcp -m tcp --dport 9000 -m comment --comment "allow opnsense step-ca connection" -j ACCEPT
|
||||
-A INPUT -s 192.168.10.13/32 -p tcp -m tcp --dport 9000 -m comment --comment "allow dev step-ca connection" -j ACCEPT
|
||||
-A INPUT -s 192.168.10.14/32 -p tcp -m tcp --dport 9000 -m comment --comment "allow app step-ca connetcion" -j ACCEPT
|
||||
-A INPUT -p tcp -m tcp --dport 2080 -m conntrack --ctorigdstport 80 -m comment --comment "allow tcp http connection which is only from 80" -j ACCEPT
|
||||
-A INPUT -p tcp -m tcp --dport 2443 -m conntrack --ctorigdstport 443 -m comment --comment "allow tcp https connection which is only from 443" -j ACCEPT
|
||||
COMMIT
|
||||
*nat
|
||||
:PREROUTING ACCEPT [0:0]
|
||||
:INPUT ACCEPT [0:0]
|
||||
:OUTPUT ACCEPT [0:0]
|
||||
:POSTROUTING ACCEPT [0:0]
|
||||
-A PREROUTING -p tcp -m tcp --dport 80 -m comment --comment "allow and preroute tcp http connection 80 to 2080" -j REDIRECT --to-ports 2080
|
||||
-A PREROUTING -p tcp -m tcp --dport 443 -m comment --comment "allow and preroute tcp https connection 443 to 2443" -j REDIRECT --to-ports 2443
|
||||
-A OUTPUT -d 127.0.0.1/32 -p tcp -m tcp --dport 80 -m comment --comment "NAT local tcp http connection 80 to 2080" -j REDIRECT --to-ports 2080
|
||||
-A OUTPUT -d 192.168.10.12/32 -p tcp -m tcp --dport 80 -m comment --comment "NAT local tcp http connection 80 to 2080" -j REDIRECT --to-ports 2080
|
||||
-A OUTPUT -d 127.0.0.1/32 -p tcp -m tcp --dport 443 -m comment --comment "NAT local tcp https connection 443 to 2443" -j REDIRECT --to-ports 2443
|
||||
-A OUTPUT -d 192.168.10.12/32 -p tcp -m tcp --dport 443 -m comment --comment "NAT local tcp https connection 443 to 2443" -j REDIRECT --to-ports 2443
|
||||
-A OUTPUT -d 127.0.0.1/32 -p tcp -m tcp --dport 636 -m comment --comment "NAT local tcp ldaps connection 636 to 6360" -j REDIRECT --to-ports 6360
|
||||
-A OUTPUT -d 192.168.10.12/32 -p tcp -m tcp --dport 636 -m comment --comment "NAT local tcp ldaps connection 636 to 6360" -j REDIRECT --to-ports 6360
|
||||
COMMIT
|
||||
```
|
||||
|
||||
#### dev
|
||||
|
||||
```ini
|
||||
*filter
|
||||
:INPUT DROP [0:0]
|
||||
:FORWARD DROP [0:0]
|
||||
:OUTPUT ACCEPT [0:0]
|
||||
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -m comment --comment "allow established connection" -j ACCEPT
|
||||
-A INPUT -i lo -m comment --comment "allow local connection" -j ACCEPT
|
||||
-A INPUT -p icmp -m comment --comment "allow ICMP connection" -j ACCEPT
|
||||
-A INPUT -s 10.10.10.2/32 -p tcp -m tcp --dport 22 -m comment --comment "allow vpn console ssh connection" -j ACCEPT
|
||||
-A INPUT -s 10.10.10.3/32 -p tcp -m tcp --dport 22 -m comment --comment "allow vpn console ssh connection" -j ACCEPT
|
||||
-A INPUT -s 10.10.10.4/32 -p tcp -m tcp --dport 22 -m comment --comment "allow vpn console ssh connection" -j ACCEPT
|
||||
-A INPUT -s 192.168.10.1/32 -p tcp -m tcp --dport 22 -m comment --comment "allow OPNsense ssh connection for ACME update" -j ACCEPT
|
||||
-A INPUT -s 192.168.10.10/32 -p tcp -m tcp --dport 22 -m comment --comment "allow hypervisor ssh connection" -j ACCEPT
|
||||
-A INPUT -s 192.168.10.12/32 -p tcp -m tcp --dport 5432 -m comment --comment "allow auth postgresql connection" -j ACCEPT
|
||||
-A INPUT -s 192.168.10.14/32 -p tcp -m tcp --dport 5432 -m comment --comment "allow app postgresql connection" -j ACCEPT
|
||||
-A INPUT -s 192.168.10.12/32 -p tcp -m tcp --dport 2080 -m conntrack --ctorigdstport 80 -m comment --comment "allow tcp http connection which is only from 80 and main caddy" -j ACCEPT
|
||||
-A INPUT -s 192.168.10.12/32 -p tcp -m tcp --dport 2443 -m conntrack --ctorigdstport 443 -m comment --comment "allow tcp https connection which is only from 443 and main caddy" -j ACCEPT
|
||||
COMMIT
|
||||
*nat
|
||||
:PREROUTING ACCEPT [0:0]
|
||||
:INPUT ACCEPT [0:0]
|
||||
:OUTPUT ACCEPT [0:0]
|
||||
:POSTROUTING ACCEPT [0:0]
|
||||
-A PREROUTING -p tcp -m tcp --dport 80 -m comment --comment "allow and preroute tcp http connection 80 to 2080" -j REDIRECT --to-ports 2080
|
||||
-A PREROUTING -p tcp -m tcp --dport 443 -m comment --comment "allow and preroute tcp https connection 443 to 2443" -j REDIRECT --to-ports 2443
|
||||
-A OUTPUT -d 127.0.0.1/32 -p tcp -m tcp --dport 80 -m comment --comment "NAT local tcp http connection 80 to 2080" -j REDIRECT --to-ports 2080
|
||||
-A OUTPUT -d 192.168.10.13/32 -p tcp -m tcp --dport 80 -m comment --comment "NAT local tcp http connection 80 to 2080" -j REDIRECT --to-ports 2080
|
||||
-A OUTPUT -d 127.0.0.1/32 -p tcp -m tcp --dport 443 -m comment --comment "NAT local tcp https connection 443 to 2443" -j REDIRECT --to-ports 2443
|
||||
-A OUTPUT -d 192.168.10.13/32 -p tcp -m tcp --dport 443 -m comment --comment "NAT local tcp https connection 443 to 2443" -j REDIRECT --to-ports 2443
|
||||
COMMIT
|
||||
```
|
||||
|
||||
#### app
|
||||
|
||||
```ini
|
||||
*filter
|
||||
:INPUT DROP [0:0]
|
||||
:FORWARD DROP [0:0]
|
||||
:OUTPUT ACCEPT [0:0]
|
||||
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -m comment --comment "allow established connection" -j ACCEPT
|
||||
-A INPUT -i lo -m comment --comment "allow local connection" -j ACCEPT
|
||||
-A INPUT -p icmp -m comment --comment "allow ICMP connection" -j ACCEPT
|
||||
-A INPUT -s 10.10.10.2/32 -p tcp -m tcp --dport 22 -m comment --comment "allow vpn console ssh connection" -j ACCEPT
|
||||
-A INPUT -s 10.10.10.3/32 -p tcp -m tcp --dport 22 -m comment --comment "allow vpn console ssh connection" -j ACCEPT
|
||||
-A INPUT -s 10.10.10.4/32 -p tcp -m tcp --dport 22 -m comment --comment "allow vpn console ssh connection" -j ACCEPT
|
||||
-A INPUT -s 192.168.10.1/32 -p tcp -m tcp --dport 22 -m comment --comment "allow OPNsense ssh connection for ACME update" -j ACCEPT
|
||||
-A INPUT -s 192.168.10.10/32 -p tcp -m tcp --dport 22 -m comment --comment "allow hypervisor ssh connection" -j ACCEPT
|
||||
-A INPUT -s 192.168.10.13/32 -p tcp -m tcp --dport 22 -m comment --comment "allow code-server ssh connection" -j ACCEPT
|
||||
-A INPUT -p tcp -m tcp --dport 2080 -m conntrack --ctorigdstport 80 -m comment --comment "allow tcp http connection which is only from 80" -j ACCEPT
|
||||
-A INPUT -p tcp -m tcp --dport 2443 -m conntrack --ctorigdstport 443 -m comment --comment "allow tcp https connection which is only from 443" -j ACCEPT
|
||||
COMMIT
|
||||
*nat
|
||||
:PREROUTING ACCEPT [0:0]
|
||||
:INPUT ACCEPT [0:0]
|
||||
:OUTPUT ACCEPT [0:0]
|
||||
:POSTROUTING ACCEPT [0:0]
|
||||
-A PREROUTING -p tcp -m tcp --dport 80 -m comment --comment "allow and preroute tcp http connection 80 to 2080" -j REDIRECT --to-ports 2080
|
||||
-A PREROUTING -p tcp -m tcp --dport 443 -m comment --comment "allow and preroute tcp https connection 443 to 2443" -j REDIRECT --to-ports 2443
|
||||
-A OUTPUT -d 127.0.0.1/32 -p tcp -m tcp --dport 80 -m comment --comment "NAT local tcp http connection 80 to 2080" -j REDIRECT --to-ports 2080
|
||||
-A OUTPUT -d 192.168.10.14/32 -p tcp -m tcp --dport 80 -m comment --comment "NAT local tcp http connection 80 to 2080" -j REDIRECT --to-ports 2080
|
||||
-A OUTPUT -d 127.0.0.1/32 -p tcp -m tcp --dport 443 -m comment --comment "NAT local tcp https connection 443 to 2443" -j REDIRECT --to-ports 2443
|
||||
-A OUTPUT -d 192.168.10.14/32 -p tcp -m tcp --dport 443 -m comment --comment "NAT local tcp https connection 443 to 2443" -j REDIRECT --to-ports 2443
|
||||
COMMIT
|
||||
```
|
||||
@@ -0,0 +1,306 @@
|
||||
systTags: #common, #configuration, #virtualization, #container, #os
|
||||
|
||||
## podman
|
||||
|
||||
The container is one of virtual technology to run applications independently regardless of what the host system is. One of them, Docker uses daemon(dockerd) and docker socket(root authority), so all container can access hosts' root authority. It causes danger that hackers can take down root authority via docker containers. Additionally, docker daemon system makes it hard to combine each container to systemd, because all containers are in charge of dockerd.
|
||||
|
||||
Podman is a new technology to solve docker's problems. It implements daemonless, and rootless container environment. It reduces the danger of the hacker getting root authority via containers, and it is easy to combine each container to systemd.
|
||||
|
||||
### Configuration
|
||||
|
||||
- File: /etc/containers/registries.conf
|
||||
|
||||
```bash
|
||||
# Check the linger
|
||||
loginctl show-user $(whoami)
|
||||
# Linger=yes
|
||||
```
|
||||
|
||||
```ini
|
||||
# /etc/containers/registries.conf
|
||||
unqualified-search-registries = ["docker.io"]
|
||||
```
|
||||
|
||||
### Networking
|
||||
|
||||
Podman uses a specific IP address and domain names to communicate with its host system using pasta. Pasta is a new default network mode in podman 4.0 which can allow communication with the host system directly. Therefore, it doesn't need a bridge or network host. `169.254.1.2` is 'link-local address' to communicate with host's system. The pasta conduct the SNAT. Therefore, even though the packet's original source IP were container's IP, when the packet passes the pasta its source IP changes as 127.0.0.1 or its own IP (localhost).
|
||||
|
||||
- `/etc/hosts` in container
|
||||
- 169.254.1.2 host.containers.internal host.docker.internal
|
||||
|
||||
When you use the command below, you can add domain to the line.
|
||||
|
||||
- --host-name mydomain.internal:host-gateway
|
||||
|
||||
Additionally, rootless podman containers cannot bind host's privileged ports number(=<1024). Therefore, if container needed to use these ports, you would have to use iptables' nat table. The example of iptables' usage is [here](./03_02_iptables.md).
|
||||
|
||||
#### Bridge mode
|
||||
|
||||
Bridge mode create a separated virtual IP network from host's network. This mode supports simple DNS function between containers belonging to the same network. This mode basically uses SNAT(Source NAT) mutually; both inbound and outbond. It is because basically podman runs as rootless. So, the container can't distinguish where the packets come from, except from the container belonging to the same podman network. At the same time, client also can't distinguish where the packets come from. Because every packet seems like come from host server. It makes inspection hard for both of client and containers.
|
||||
|
||||
### ID mapping
|
||||
|
||||
Podman basically mapped container's root to host's executing user. Despite the root, podman uses subuid, subgid system. They are set on `/etc/subuid`, `/etc/subgid`.
|
||||
|
||||
- host(1000:1000) < container(0:0)
|
||||
- host(100999:100999) < container(1000:1000) : subuid, subgid
|
||||
|
||||
When podman runs and executes commands with -u option, --userns=keep-id option, -uid, -gid option, it can adjust mapping.
|
||||
|
||||
- -u uid:gid: excute container with hosts's uid (bring the host's UID/GID towards container's `/etc/passwd` directly.)
|
||||
- --userns=keep-id: mapping all container's file permission to host's file permission.
|
||||
- --cap-add=DAC_READ_SEARCH option: without root authority, container can access every file regardless permission.
|
||||
|
||||
#### Mapping error
|
||||
|
||||
Some container doesn't execute a container with root permission. They execute the container with their specific uid (i.e. UID:53 - BIND). In this case, when the container runs with `-u` option or `--userns=keep-id` option can make mapping error very frequently.
|
||||
|
||||
- `-u` option
|
||||
|
||||
When the container runs with `-u` option, the entrypoint can't work properly in many cases because they were already set that runs entrypoint as specific uid. So, if `-u` option were set, then it would cause permission error.
|
||||
|
||||
- `--userns=keep-id` option
|
||||
|
||||
This option makes container's directory/file UID as the same as the host's directory/file UID. So, it turns off the UID/GID mapping itself. When some directory which has root authorization mapped with hosts' file it occurs UID mapping error.
|
||||
|
||||
#### Permission management
|
||||
|
||||
Use ACL packages, to give additional permission of directory. It can give the extra permission to subuid or host's uid.
|
||||
|
||||
```bash
|
||||
# u:[subuid]:rwx
|
||||
sudo getfacl /path/of/podman_directory
|
||||
# `-d` option is to set permission for file or directory which are created automatically
|
||||
# `-R` option is to set permission for file or directory which already existed
|
||||
sudo setfacl -d -m u:[subuid]:rwx /path/of/podman_directory
|
||||
sudo setfacl -d -m u:[hostuid]:rwx /path/of/podman_directory
|
||||
```
|
||||
|
||||
### Usage of podman
|
||||
|
||||
#### Containerfile and build
|
||||
|
||||
Containerfile's format is compatible with dockerfile. Here is the example below. Containerfile can be built as podman image with `podman build` command.
|
||||
|
||||
```containerfile
|
||||
FROM caddy:2.10.2-builder-alpine AS builder
|
||||
|
||||
RUN xcaddy build \
|
||||
--with github.com/caddy-dns/rfc2136 \
|
||||
--with github.com/hslatman/caddy-crowdsec-bouncer/crowdsec \
|
||||
--with github.com/hslatman/caddy-crowdsec-bouncer/http \
|
||||
|
||||
FROM caddy:2.10.2
|
||||
|
||||
COPY --from=builder /usr/bin/caddy /usr/bin/caddy
|
||||
```
|
||||
|
||||
```bash
|
||||
# Build container file as podman image
|
||||
podman build -t caddy:2.10.2-main -f /path/of/containterfile-caddy-main . && podman image prune -f
|
||||
# Delete source images
|
||||
podman rmi caddy:2.10.2-builder-alpine
|
||||
podman rmi caddy:2.10.2
|
||||
```
|
||||
|
||||
#### Podman images
|
||||
|
||||
- `podman images`: Print list of all local podman images
|
||||
- `podman image pull [image_name]`: Download podman images from repository
|
||||
- `podman pull` is the same command
|
||||
- `podman image prune`: Remove unused and untagged images
|
||||
- `podman image rm [images]`: Remove local podman image
|
||||
- `podman rmi` is the same command
|
||||
|
||||
#### Run and exec
|
||||
|
||||
podman ps \[--all\]: it shows podman container lists
|
||||
- podman run
|
||||
- --name: container's name
|
||||
- --restart: restart mode - unless-stopped
|
||||
- --add-host: additional host domain name on 169.254.1.2
|
||||
- --cap-add: add some specific privileges without root authority
|
||||
- -p host_ports:container_port
|
||||
- -v host_path:container_path:permission(rw, ro, and when you use SELinux, you can use Z or z)
|
||||
- -e environment_value
|
||||
- -d: run background
|
||||
- image_name
|
||||
- podman exec -it \[container_name\] \[command\]
|
||||
|
||||
#### Pod
|
||||
|
||||
Pod makes each container which are in the same pod share some specific resources. The network(IP address), storage volumes. However, each container has their own file system, process, and resource limits. So, this is very useful to use various containers which has close relationship like application and Redis(cache db).
|
||||
|
||||
- podman pod create
|
||||
- --name: pod's name
|
||||
- -p: host_ports:pod_ports
|
||||
- podman run
|
||||
- ...
|
||||
- --pod pod's name
|
||||
> Don't use `-p` option. Pod already has `-p` option.
|
||||
|
||||
#### Container and file management
|
||||
|
||||
#### container
|
||||
|
||||
- check pure container
|
||||
|
||||
```bash
|
||||
podman run --rm -it --entrypoint sh [image_name] --args
|
||||
# or
|
||||
podman run --rm -it [image_name] sh --args
|
||||
```
|
||||
|
||||
#### file management
|
||||
|
||||
- Using `podman exec` to manage file
|
||||
- Use ACL package `setfacl`
|
||||
- `--cap-add=DAC_READ_SEARCH` option allows to read all file without permission to backup (for kopia)
|
||||
|
||||
### Quadlet and systemd
|
||||
|
||||
#### Register the secret on podman secret
|
||||
|
||||
- Using `edit_secret.sh` and `extract_secret.sh`
|
||||
- Inject secret value to `podman secret` or `/etc/secrets/$UID`
|
||||
|
||||
```bash
|
||||
# /etc/secrets/$UID
|
||||
# Before use sudo tee, make sure sudo doesn't need password.
|
||||
# i.e. sudo ps -ef command execute before this command.
|
||||
# Env file
|
||||
extract_secret.sh ~/data/config/secrets/.secret.yaml -e "$value" > /run/user/$UID/tmp.env \
|
||||
&& sudo mv /run/user/$UID/tmp.env /etc/secrets/$UID/"$FILE_NAME" \
|
||||
&& sudo chown $UID:root /etc/secrets/$UID/"$FILE_NAME" \
|
||||
&& sudo chmod 400 /etc/secrets/$UID/"$FILE_NAME"
|
||||
# Normal file
|
||||
extract_secret.sh ~/data/config/secrets/.secret.yaml -f "$value" > /run/user/$UID/tmp.env \
|
||||
&& sudo mv /run/user/$UID/tmp.env /etc/secrets/$UID/"$FILE_NAME" \
|
||||
&& sudo chown $UID:root /etc/secrets/$UID/"$FILE_NAME" \
|
||||
&& sudo chmod 400 /etc/secrets/$UID/"$FILE_NAME"
|
||||
|
||||
# Podman secret
|
||||
# Podman doesn't supports .env file parsing, you have to enroll all values
|
||||
extract_secret.sh ~/data/config/secrets/.secret.yaml -f "$value" | podman secret create "[$FILE_NAME|$ENV_NAME]" -
|
||||
```
|
||||
|
||||
> ` podman secret inspect --showsecret --format '{{.SecretData}}' $secret_name` shows the content of secret
|
||||
|
||||
|
||||
#### Define quadlet file
|
||||
|
||||
- File:
|
||||
- ~/data/config/containers/app/app.container
|
||||
- ~/data/config/containers/app/app.pod
|
||||
|
||||
Quadlet is to define of specification as `.quadlet` or `.container`. Quadlet uses these file to make `.service` file to combine container to systemd. Here is the example of `.container` file below.
|
||||
|
||||
```ini
|
||||
# app.container
|
||||
[Quadlet]
|
||||
# Don't make a dependencies
|
||||
DefaultDependencies=false
|
||||
|
||||
[Unit]
|
||||
Description=app
|
||||
After=a.service
|
||||
Wants=a.service
|
||||
Requires=a.service
|
||||
|
||||
[Service]
|
||||
ExecStartPre=%h/data/config/scripts/wait-for-it.sh -h 192.168.10.1 -p 8080 -t 20
|
||||
|
||||
[Container]
|
||||
# Pod=app.pod
|
||||
Image=localhost/app:1.0.0
|
||||
|
||||
ContainerName=app
|
||||
|
||||
PublishPort=2080:80/tcp
|
||||
PublishPort=2443:443/tcp
|
||||
|
||||
AddHost=app.service.internal:host-gateway
|
||||
|
||||
Volume=%h/data/containers/app:/home/app:rw
|
||||
|
||||
Environment="ENV1=ENV1"
|
||||
|
||||
Secret=ENV_NAME,type=env
|
||||
Secret=app.file,target=/path/of/secret/file/name
|
||||
|
||||
# podman run [options] [image] example --config exconfig
|
||||
Exec=example --config exconfig
|
||||
|
||||
# If you want to change Entrypoint itself, use
|
||||
Entrypoint=sh -c 'command'
|
||||
|
||||
# For Diun
|
||||
Label=diun.enable=true
|
||||
# For Diun to track repository new version
|
||||
Label=diun.watch_repo=true
|
||||
# For Diun, and it needs `diun.yml` configuration
|
||||
Label=diun.regopt=container-source
|
||||
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
```
|
||||
|
||||
```ini
|
||||
# app.pod
|
||||
[Quadlet]
|
||||
# Don't make a dependencies
|
||||
DefaultDependencies=false
|
||||
[Pod]
|
||||
Name=app
|
||||
|
||||
PublishPort=2080:80/tcp
|
||||
```
|
||||
#### Create systemd `.service` file
|
||||
|
||||
```bash
|
||||
# linger has to be activated
|
||||
mkdir -p ~/.config/containers/systemd
|
||||
|
||||
ln -s ~/data/config/containers/app/app.container ~/.config/containers/systemd/app.container
|
||||
|
||||
# This command makes ~/.config/systemd/user/my-app.service
|
||||
systemctl --user daemon-reload
|
||||
```
|
||||
|
||||
#### Enable and start service
|
||||
|
||||
```bash
|
||||
systemctl --user enable app.service
|
||||
systemctl --user start app.service
|
||||
```
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Following goal
|
||||
|
||||
### Health check
|
||||
|
||||
```ini
|
||||
# i.e. caddy
|
||||
# Podman [Container] section
|
||||
|
||||
[Container]
|
||||
# Health check configuration
|
||||
# Health check command
|
||||
HealthcheckCommand=curl -f http://localhost/ || exit 1
|
||||
# Health check interval
|
||||
HealthcheckInterval=30s
|
||||
# the time to wait for health check
|
||||
HealthcheckTimeout=5s
|
||||
# the number to try to health check
|
||||
HealthcheckRetries=3
|
||||
# the time to wait to start first health check
|
||||
HealthcheckStartPeriod=15s
|
||||
|
||||
# override.conf [Service] section
|
||||
[Service]
|
||||
# Restart, if it is not healthy
|
||||
Restart=on-failure
|
||||
```
|
||||
@@ -0,0 +1,321 @@
|
||||
Tags: #common, #configuration, #network, #security
|
||||
|
||||
## CrowdSec
|
||||
|
||||
CrowdSec is the free, open-source IPS(Intrusion Prevention System). It has a distributed architecture. When an agent detects malicious IPs from log, it reports the information to LAPI. This information is shared to CrowdSec's central server anonymously and is spread across the world. Additionally, bouncer receives a blacklist from LAPI and when they approach the server, the bouncer blocks them.
|
||||
|
||||
|
||||
### Collection
|
||||
|
||||
#### Parser
|
||||
|
||||
It is how to organize the raw logs to the parsed log that scenario can understand. It is works on agent, and parsed log is transferred to LAPI to decide.
|
||||
#### Scenario
|
||||
|
||||
It is how to analyze the malicious attack from the parsed log from the Agent. The LAPI decides what to do for malicious attack, and transfers the result to Bouncer to block.
|
||||
|
||||
### Agent
|
||||
|
||||
Agent is the detector in each server. They analyze the logs. When they find malicious approaches, or abnormal and harmful actions, they report the information to LAPI(Local API). It analizes the log following `Parsers`.
|
||||
|
||||
### LAPI
|
||||
|
||||
LAPI server is a local central collector and reporter of malicious attack information. It decides what to apply for traffic following `Scenarios`. In this homelab it is located in OPNsense, because the center of gateway of home network is Firewall. When agent reports threats to the LAPI server, LAPI decides whether or not to block them, and reports to the central CrowdSec server. This information will be spread to all CrowdSec users in the world.
|
||||
|
||||
### Bouncer
|
||||
|
||||
When LAPI decides to block some IPs, they create a blacklist and give it to bouncer. Bouncer blocks and bans some IPs depending on LAPI's blacklist. `Caddy-auth (L7)` and `OPNsense (L4)` will be bouncer to ban. The most important thing is LAPI just decide what to ban, and Bouncer conducts ban practically.
|
||||
|
||||
## CrowdSec in OPNsense
|
||||
|
||||
OPNsense supports CrowdSec with community plugin. This is not a basic function so when you want to use it in OPNsense you should install the community plugin.
|
||||
|
||||
### Installation
|
||||
|
||||
- System:Firmware:Plugins
|
||||
- \[\*\] Show community plugins
|
||||
- `os-crowdsec`
|
||||
|
||||
### General configuration
|
||||
|
||||
Services:CrowdSec:Settings
|
||||
- \[\*\] Enable Log Processor (IDS)
|
||||
- \[\*\] Enable LAPI
|
||||
- \[\*\] Enable Remediation Component (IPS)
|
||||
- \[ \] Manual LAPI configuration
|
||||
- LAPI listen address: \[opnsense IP: 192.168.10.1\]
|
||||
- LAPI listen port: 8080
|
||||
- \[\*\] Create blocklist rules
|
||||
- `Apply`
|
||||
|
||||
> Enable Remediation Component (IPS) option means, Bouncer will be integrated with OPNsense's firewall rules
|
||||
|
||||
> Set LAPI configuration manually, `Manual LAPI configuration` is needed.
|
||||
|
||||
### Machines configuration
|
||||
|
||||
#### OPNsense console
|
||||
|
||||
```sh
|
||||
# 8) shell
|
||||
cscli machines add [server_name] -a -f -
|
||||
# ---
|
||||
# Machine 'server_name' successfully added to the local API.
|
||||
# url: http://192.168.10.1:8080
|
||||
# login: [server_name]
|
||||
# password: (API key)
|
||||
# ---
|
||||
```
|
||||
|
||||
#### Each server
|
||||
|
||||
```ini
|
||||
# /etc/crowdsec/local_api_credentials.yaml
|
||||
url: http://192.168.10.1:8080
|
||||
login: [server_name]
|
||||
password: (API key)
|
||||
# /etc/crowdsec/acquis.d/sshd.yaml
|
||||
---
|
||||
source: journalctl
|
||||
journalctl_filter:
|
||||
- "_SYSTEMD_UNIT=ssh.service" labels:
|
||||
type: sshd
|
||||
# origin value is syslog
|
||||
---
|
||||
|
||||
```
|
||||
|
||||
```bash
|
||||
sudo systemctl restart crowdsec
|
||||
```
|
||||
|
||||
#### OPNsense web UI
|
||||
|
||||
- Services:CrowdSec:Machines
|
||||
- checking the lists of server
|
||||
- Main CLI commands of CrowdSec
|
||||
|
||||
```sh
|
||||
# View active decisions(Ban list)
|
||||
cscli decisions list
|
||||
|
||||
# View alerts
|
||||
cscli alerts list
|
||||
|
||||
# Check connected machines(agents)
|
||||
cscli machines list
|
||||
```
|
||||
|
||||
---
|
||||
## TLS on crowdsec communication
|
||||
|
||||
TLS can be applied in CrowdSec communication when internal PKI are set (BIND, Step-CA, ACME-Client in OPNsense). CrowdSec communication can contain sensitive information such as API key, it is recommended to set TLS.
|
||||
|
||||
### General configuration
|
||||
|
||||
- Services:CrowdSec:Settings
|
||||
- \[\*\] Manual LAPI configuration
|
||||
|
||||
### DNS setting
|
||||
|
||||
### Add new domain in BIND
|
||||
|
||||
Following [here](../06_network/06_03_net_bind.md).
|
||||
- net server
|
||||
- file: ~/data/containers/bind/lib/db.ilnmors.internal
|
||||
|
||||
```text
|
||||
...
|
||||
crowdsec IN CNAME opnsense.ilnmors.internal.
|
||||
...
|
||||
```
|
||||
|
||||
### ACME setting
|
||||
|
||||
#### Use opnsense certificates
|
||||
|
||||
- Services:ACME Client:Certificates - Certificates
|
||||
- Alt name: crowdsec.ilnmors.internal
|
||||
|
||||
#### Certificate and key file
|
||||
|
||||
- opnsense
|
||||
- file:
|
||||
- /var/etc/acme-client/cert-home/\[ramdom_string\]/opnsense.ilnmors.internal/
|
||||
- fullchain.cer
|
||||
- opnsense.ilnmors.internal.key
|
||||
> There is `opnsense.ilnmors.internal.cer` file. However, when client verify the certificate, it verify the intermediate CA's certificate and root CA's certificate both. Therefore, in this case, use fullchain.cer
|
||||
- /usr/local/etc/ssl/cert.pem
|
||||
|
||||
### Add TLS setting in LAPI configuration
|
||||
|
||||
- opnsense
|
||||
- file:
|
||||
- /usr/local/etc/crowdsec/config.yaml
|
||||
- /usr/local/etc/crowdsec/local_api_credentials.yaml
|
||||
- /usr/local/etc/crowdsec/bouncer/crowdsec-firewall-bouncer.yaml
|
||||
|
||||
```yaml
|
||||
# config.yaml
|
||||
api:
|
||||
client:
|
||||
# ...client configurations
|
||||
server:
|
||||
enable: true
|
||||
listen_uri: 192.168.10.1:8080 # actual IP address is required. (Do not use FQDN in here, the service listener is binded on network interface)
|
||||
# ... server configurations
|
||||
tls:
|
||||
cert_file: /var/etc/acme-client/cert-home/[random_string]/opnsense.ilnmors.internal/fullchain.cer
|
||||
key_file: /var/etc/acme-client/cert-home/[ramdom_string]/opnsense.ilnmors.internal/opnsense.ilnmors.internal.key
|
||||
# random string is generated by opnsense itself. In real environment, check it first.
|
||||
```
|
||||
|
||||
```yaml
|
||||
# local_api_credentials.yaml
|
||||
...
|
||||
url: https://crowdsec.ilnmors.internal:8080/
|
||||
```
|
||||
|
||||
```yaml
|
||||
# crowdsec-firewall-bouncer.yaml
|
||||
...
|
||||
api_url: https://crowdsec.ilnmors.internal:8080/
|
||||
...
|
||||
```
|
||||
|
||||
```sh
|
||||
service crowdsec restart
|
||||
```
|
||||
|
||||
#### CrowdSec LAPI restart setting
|
||||
|
||||
- opnsense
|
||||
- file: /usr/local/etc/cron.d/crowdsec
|
||||
|
||||
```text
|
||||
#minute hour mday month wday who command
|
||||
0 3 * * * root /usr/local/libexec/crowdsec/upgrade-hub
|
||||
30 3 * * * root /usr/sbin/service crowdsec reload # Add this line to reload every day.
|
||||
```
|
||||
|
||||
### Each server configuration
|
||||
|
||||
#### Server's certificate trust
|
||||
|
||||
- each server
|
||||
- file: /usr/local/share/ca-certificates/root_ca.crt
|
||||
|
||||
```bash
|
||||
sudo update-ca-certificates
|
||||
```
|
||||
|
||||
#### CrowdSec Agent setting
|
||||
|
||||
```ini
|
||||
# /etc/crowdsec/local_api_credentials.yaml
|
||||
url: https://crowdsec.ilnmors.internal:8080
|
||||
login: [server_name]
|
||||
password: (API key)
|
||||
```
|
||||
|
||||
```bash
|
||||
sudo systemctl restart crowdsec
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Crowdsec in Caddy-auth
|
||||
|
||||
Caddy supports bouncer. Also, it can be work as agent via auth server where Caddy-auth is located.
|
||||
|
||||
### Bouncer configuration
|
||||
|
||||
> Caddy has to contain local CA's root_ca.crt (Step-CA). - containerfile already includes root_ca.crt in container when it was built
|
||||
|
||||
#### OPNsense console
|
||||
|
||||
```sh
|
||||
cscli bouncer add caddy-auth
|
||||
|
||||
> API key for 'caddy-auth':
|
||||
|
||||
> Secret_value
|
||||
|
||||
> Please keep this key since you will not be able to retrieve it!
|
||||
>
|
||||
>
|
||||
cscli collections install crowdsecurity/caddy
|
||||
```
|
||||
|
||||
#### Caddyfile
|
||||
|
||||
```ini
|
||||
# ...
|
||||
# Crowdsec bouncer setting
|
||||
{
|
||||
crowdsec {
|
||||
# CrowdSec LAPI
|
||||
api_url https://crowdsec.ilnmors.internal:8080
|
||||
api_key "{env.CADDY_CROWDSEC_KEY}"
|
||||
}
|
||||
}
|
||||
# ...
|
||||
```
|
||||
|
||||
- `podman exec caddy-auth caddy reload --config /etc/caddy/Caddyfile`
|
||||
### Agent configuration
|
||||
|
||||
#### auth sv
|
||||
|
||||
- File:
|
||||
- /etc/crowdsec/acquis.yaml
|
||||
- ~/data/container/caddy_auth/data/access.log
|
||||
|
||||
```yaml
|
||||
# /etc/crowdsec/acquis.d/caddy-auth.yaml
|
||||
filenames:
|
||||
- /var/log/caddy.log
|
||||
labels:
|
||||
type: caddy
|
||||
```
|
||||
|
||||
```ini
|
||||
# Caddyfile
|
||||
# ...
|
||||
(crowdsec_log) {
|
||||
log {
|
||||
output file /data/access.log {
|
||||
roll_size 10mb
|
||||
roll_keep 5
|
||||
}
|
||||
}
|
||||
}
|
||||
# ...
|
||||
caddy.ilnmors.com {
|
||||
import crowdsec_log
|
||||
route {
|
||||
crowdsec
|
||||
root * /usr/share/caddy
|
||||
file_server
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
podman exec caddy-auth caddy reload --config /etc/caddy/Caddyfile
|
||||
sudo mkdir /etc/crowdsec/acquis.d
|
||||
sudo nano /etc/crowdsec/acquis.d/caddy-auth.yaml
|
||||
ln -s /home/auth/data/containers/caddy-auth/data/access.log /var/log/caddy.log
|
||||
# install collection(senario + parser) crowdsecurity/caddy
|
||||
sudo cscli collections install crowdsecurity/caddy
|
||||
sudo systemctl restart crowdsec
|
||||
sudo cscli metrics
|
||||
```
|
||||
|
||||
---
|
||||
## PLAN
|
||||
|
||||
- distributed bouncer
|
||||
- [x] Caddy bouncer
|
||||
|
||||
- [ ] dash board
|
||||
@@ -0,0 +1,107 @@
|
||||
Tags: #os, #configuration, #virtualization, #container, #database, #cache
|
||||
|
||||
## Redis
|
||||
|
||||
Redis is the cache database. It doesn't use SQL, it is just key-value data storage.
|
||||
|
||||
### Redis in Pod
|
||||
|
||||
Redis is combined with master services in the pod. It allows master services and Redis can communication in localhost. It doesn't need TLS, password, or even ACL itself.
|
||||
|
||||
There is the list of services which use Redis in Pod.
|
||||
#### Preperation
|
||||
|
||||
##### Create directory for container
|
||||
|
||||
```bash
|
||||
mkdir -p ~/data/containers/app/redis
|
||||
setfacl -m d:g::0 ~/data/containers/app/redis
|
||||
setfacl -m d:o::0 ~/data/containers/app/redis
|
||||
setfacl -m u:
|
||||
$UID:rwx ~/data/containers/app/redis
|
||||
setfacl -m u:100998:rwx ~/data/containers/app/redis
|
||||
setfacl -d -m u:$UID:rwx ~/data/containers/app/redis
|
||||
setfacl -d -m u:100998:rwx ~/data/containers/app/redis
|
||||
```
|
||||
|
||||
>Redis container executes as 999:999(redis:redis) permission in container. It is mapped host's 100998. Therefore, directories have to have ACL via `setfacl`
|
||||
|
||||
### Podman Image
|
||||
|
||||
```bash
|
||||
podman pull redis:8.2.2 # Do not use latest version to management
|
||||
```
|
||||
|
||||
### Quadlet
|
||||
|
||||
- File:
|
||||
- ~/data/config/containers/app/app.pod
|
||||
- ~/data/config/containers/app/app-redis.container
|
||||
- ~/data/config/containers/app/app.container
|
||||
|
||||
```ini
|
||||
# ~/data/config/containers/app/app.pod
|
||||
[Quadlet]
|
||||
DefaultDependencies=false
|
||||
|
||||
[Pod]
|
||||
PodName=app
|
||||
|
||||
# web port
|
||||
PublishPort=9080:9000/tcp
|
||||
# LDAP port
|
||||
#PublishPort=[set_port]:3389
|
||||
# Prometheus Port
|
||||
#PublishPort=[set_port]:9300
|
||||
```
|
||||
|
||||
```ini
|
||||
# ~/data/config/containers/app/app-redis.container
|
||||
|
||||
[Quadlet]
|
||||
DefaultDependencies=false
|
||||
|
||||
[Unit]
|
||||
Description=app - redis
|
||||
Before=app.service
|
||||
|
||||
[Container]
|
||||
Pod=app.pod
|
||||
|
||||
Image=redis:8.2.2
|
||||
|
||||
ContainerName=app-redis
|
||||
|
||||
# Port 6379
|
||||
|
||||
Volume=%h/data/containers/app/redis:/data:rw
|
||||
|
||||
Environment="TZ=Asia/Seoul"
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
```
|
||||
|
||||
```ini
|
||||
# ~/data/config/containers/app/app.container
|
||||
[Quadlet]
|
||||
DefaultDependencies=false
|
||||
|
||||
[Unit]
|
||||
Description=app
|
||||
|
||||
After=app-redis.service
|
||||
Requires=app-redis.service
|
||||
|
||||
[Container]
|
||||
Pod=app.pod
|
||||
|
||||
Image=app:version
|
||||
|
||||
Volume=%h/data/containers/app/app:/data:rw
|
||||
|
||||
Environment="TZ=Asia/Seoul"
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
```
|
||||
@@ -0,0 +1,130 @@
|
||||
Tags: #common, #configuration, #os, #filesystem
|
||||
|
||||
## BTRFS usage
|
||||
|
||||
### Setting
|
||||
|
||||
```bash
|
||||
# All hdd needs partition, but it has no filesystem. To make a partition use `fdisk`.
|
||||
sudo fdisk "$DIVICE_PATH"
|
||||
> n # create the new parition
|
||||
> 1 # Partition number
|
||||
> Default # First Sector
|
||||
> Default # Last Sectort
|
||||
> w # write the new partition
|
||||
|
||||
# check btrfs-progs package
|
||||
sudo apt list --installed | grep btrfs-progs
|
||||
# btrfs-progs/stable,now 6.14-1 amd64 [installed]
|
||||
|
||||
|
||||
sudo mkfs.btrfs -d raid10 -m raid10 -L hdd /dev/sda1 /dev/sdb1 /dev/sdc1 /dev/sdd1
|
||||
# These are partition device files
|
||||
```
|
||||
|
||||
### Snapshot
|
||||
|
||||
Usually, the Read-Only snapshot is used.
|
||||
|
||||
```bash
|
||||
# Create snapshot_subvolume
|
||||
sudo btrfs subvolume create /home/app/hdd/data
|
||||
# Create snapshot_directory
|
||||
mkdir /home/app/hdd/.snapshot
|
||||
# Create snapshot
|
||||
sudo btrfs subvolume snapshot -r /home/app/hdd/data /home/app/hdd/.snapshot/data_[date]
|
||||
|
||||
# Rollback (file)
|
||||
cp /home/app/hdd/.snapshot/data_[date]/file /home/app/hdd/data/
|
||||
|
||||
# Roleback (volume)
|
||||
# Current subvolume move
|
||||
mv /home/app/hdd/data /home/app/hdd/data_fail
|
||||
sudo btrfs subvolume snapshot /home/app/hdd/.snapshot/data_[date] /home/app/hdd/data
|
||||
# If the data successfully recovered
|
||||
sudo btrfs subvolume delete /home/app/hdd/data_fail
|
||||
```
|
||||
|
||||
### replace HDD to new system
|
||||
|
||||
btrfs has its own volume manangement data and filesystem in hdd, as metadata. It has no dependency on specific OS or hardware but it can work on every linux system which supports btrfs.
|
||||
|
||||
```bash
|
||||
# unmount filesystem
|
||||
sudo umount /home/app/hdd
|
||||
|
||||
# turn off the system, and remove all hdd
|
||||
# add all disk to new hardware(server), and turn on
|
||||
|
||||
# scan btrfs
|
||||
sudo btrfs device scan
|
||||
sudo btrfs filesystem show
|
||||
|
||||
# mount
|
||||
sudo nano /etc/fstab
|
||||
# LABEL=hdd /home/app/hdd btrfs defaults,compress=zstd,autodefrag 0 0
|
||||
sudo mount -a
|
||||
```
|
||||
|
||||
### add extra HDD
|
||||
|
||||
```bash
|
||||
# Add HDD and check the device file
|
||||
lsblk
|
||||
|
||||
# Add hdd to btrfs RAID
|
||||
sudo btrfs device add /dev/xxx /dev/xxy /home/app/hdd
|
||||
|
||||
# Expand the volume
|
||||
sudo btrfs balance start /home/app/hdd
|
||||
```
|
||||
|
||||
### change HDD
|
||||
|
||||
#### btrfs replace
|
||||
|
||||
When sata slot is enough to connect new HDD and old HDD, you can use this way.
|
||||
|
||||
```bash
|
||||
# check devid of old HDD
|
||||
sudo btrfs device stats /home/app/hdd # you can check IO error
|
||||
sudo btrfs filesystem show /home/app/hdd
|
||||
|
||||
# check the new disk's path
|
||||
lsblk
|
||||
# /dev/xxx
|
||||
|
||||
# Replace
|
||||
sudo btrfs replace start [old HDD\'s devid] /dev/xxx /home/app/hdd
|
||||
|
||||
# check
|
||||
sudo btrfs replace status /home/app/hdd
|
||||
```
|
||||
|
||||
#### btrfs device add and delete
|
||||
|
||||
When sata slot is not enough to connect new HDD and old HDD simultaneously, you can use this way.
|
||||
|
||||
```bash
|
||||
# Check the HDD which will change
|
||||
sudo btrfs device stats /home/app/hdd # you can check IO error
|
||||
sudo btrfs filesystem show /home/app/hdd # check disk's devid
|
||||
|
||||
# turn off the system and change the broken HDD to the new HDD
|
||||
# Make sure the hardware supports Hot-Swap, if it didn't support, you would have to turn off your system fully when you are changing the HDD
|
||||
|
||||
# if the system couldn't mount automatically, mount it manually
|
||||
# sudo mount -o degraded /dev/xxx /home/app/hdd # /dev/xxx is one of ordinary HDD
|
||||
|
||||
lsblk
|
||||
# /dev/xxy # new HDD device file path
|
||||
|
||||
# Add new HDD
|
||||
sudo btrfs device add /dev/xxy /home/app/hdd
|
||||
|
||||
# Delete broken HDD
|
||||
sudo btrfs device delete missing /home/app/hdd
|
||||
|
||||
# To major balance
|
||||
sudo btrfs balance start /home/app/hdd
|
||||
```
|
||||
@@ -0,0 +1,277 @@
|
||||
Tags: #os, #hypervisor, #configuration, #virtualization
|
||||
|
||||
## Hypervisor configuration
|
||||
|
||||
### Debian installation and configuration
|
||||
|
||||
- Following [debian configuration](../03_common/03_01_debian_configuration).
|
||||
- Following [iptables configuration](../03_common/03_02_iptables.md).
|
||||
- Following [crowdsec](../03_common/03_04_crowdsec.md) (After OPNsense LAPI configuration).
|
||||
|
||||
### QEMU/KVM
|
||||
|
||||
#### KVM
|
||||
|
||||
KVM is a virtualization engine to share and to allocate physical resource to VM. It works on Linux kernel.
|
||||
|
||||
#### QEMU
|
||||
|
||||
QEMU is an emulator. It works with KVM frequently, and it emulates hardware or other OS on hypervisor.
|
||||
|
||||
### Libvirtd
|
||||
|
||||
Libvirtd is a daemon to use libvirt API and command to manage virtualization engines and emulators including QEMU/KVM. It supports not only QEMU/KVM but also XEN, VMWare or etc.
|
||||
|
||||
#### Libvirtd configuration
|
||||
|
||||
##### LIBVIRT_DEFAULT_URI setting
|
||||
|
||||
Basically, KVM and libvirtd require root permission to access low-level device and configuration to implement virtual machines. However, it is possible to use libvirtd command without root permission; `sudo`. When the common user has its group as `kvm`, and `libvirt`, and export environment variable `export LIBVIRT_DEFAULT_URI='qemu:///system'` on `~/.bashrc`.
|
||||
|
||||
```bash
|
||||
# ~/.bashrc
|
||||
# If the file doesn't exist, execute the commands below
|
||||
# sudo cp /etc/skel/.bashrc /home/vmm
|
||||
# sudo cp /etc/skel/.profile /home/vmm
|
||||
# sudo chown vmm:svadmins .bashrc .profile
|
||||
|
||||
# add the line below in .bashrc
|
||||
echo "export LIBVIRT_DEFAULT_URI='qemu:///system'" >> ~/.bashrc
|
||||
source ~/.bashrc
|
||||
```
|
||||
##### Directory
|
||||
|
||||
- ~/data/config/{scripts,server,services,vms}
|
||||
- ~/data/config/vms/{networks,storages}
|
||||
|
||||
#### virsh
|
||||
|
||||
##### VM management
|
||||
|
||||
- virsh list \[--all\]: Print vm list
|
||||
- virsh start \[vm_name\]: Start vm
|
||||
- virsh shutdown \[vm_name\]: Send ACPI signal to vm (shutdown)
|
||||
- virsh destroy \[vm_name\]: Stop vm forcefully
|
||||
- virsh reboot \[vm_name\]: Reboot vm
|
||||
- virsh autostart \[--disable\]: Register vm autostart
|
||||
- qemu-img create -f \[format\] /path \[volume\]: Create the virtual disk file
|
||||
|
||||
##### VM configuration
|
||||
|
||||
- virsh edit \[vm_name\]: Open vm template file (xml) and edit
|
||||
- virsh dumpxml \[vm_name\] > \[file\].xml: Backup vm template file
|
||||
- virsh define \[file\].xml: Create vm from backup file
|
||||
- virsh undefine \[vm_name\] \[--nvram\]: Remove vm
|
||||
> When vm is set as uefi, `--nvram` option is required.
|
||||
- virsh domrename \[old_vm_name\] \[new_vm_name\]: Rename vm
|
||||
|
||||
##### VM access
|
||||
|
||||
- virsh console \[vm_name\]: Access vm via serial console
|
||||
|
||||
##### Virtual network
|
||||
|
||||
- virsh net-define \[file\].xml: Create virtual network from xml file
|
||||
- virsh net-start \[network_name\]: Start virtual network
|
||||
- virsh net-autostart \[network_name\]: Register virtual network auto start
|
||||
##### Snapshot
|
||||
|
||||
- virsh snapshot-create-as --domain \[vm_name\] --name \[snapshot_name\] --description \[description\] --disk-only --atomic (for qcow2 format)
|
||||
- virsh snapshot-list \[vm_name\]
|
||||
|
||||
##### VM pool management
|
||||
|
||||
- virsh pool-define ~/data/config/vms/storages/vm-images.xml
|
||||
- virsh pool-start vm-images
|
||||
- virsh pool-autostart vm-images
|
||||
- virsh pool-refresh vm-images - After put the images in the pool
|
||||
- virsh vol-list vm-images
|
||||
#### virt-install
|
||||
|
||||
virt-install command helps to define vm template file with various options.
|
||||
|
||||
```bash
|
||||
virt-install \
|
||||
[--import] \ # without booting images, boot with disk. [--cdrom|--location] is not available
|
||||
--boot uefi \ # activate secure booting
|
||||
--name vm_name \
|
||||
--os-variant [os] \ # set optimized setting for each OS
|
||||
--vcpu [num] \
|
||||
--memory [num] \ # memory size(unit: MiB)
|
||||
--location [path] \ # or cdrom, add booting images, location is for serial booting
|
||||
# --disk path=[path],format=[raw|qcow2],discard=unmap \ # discard=unmap: ssd emulation
|
||||
# --disk vol=vm-images/my-existing-disk.qcow2 \ # To use pool's volume
|
||||
--disk pool=vms-images,size=[num],format=qcow2,discard=unmap \ # Create qcow2 image with pool configuration
|
||||
--network network=[network_name],model=virtio,mac=[mac_address] \
|
||||
--graphics none \
|
||||
--console pty,target_type=serial \
|
||||
--extra-args "console=ttyS0,115200" # Define the console configuration
|
||||
```
|
||||
|
||||
#### virsh network configuration
|
||||
|
||||
Libvirt's network is defined and saved as a XML. There is setting below. It can be compatible with host's `open vswitch` interfaces.
|
||||
|
||||
- Make xml files
|
||||
- ~/data/config/vms/networks/ovs-wan-net.xml
|
||||
- ~/data/config/vms/networks/ovs-lan-net.xml
|
||||
- `chmod 600 ~/data/config/vms/networks/*`
|
||||
|
||||
```xml
|
||||
<!-- WAN network for OVS -->
|
||||
<!-- ovs-wan-net.xml -->
|
||||
<network>
|
||||
<name>ovs-wan-net</name>
|
||||
<forward mode='bridge'/>
|
||||
<bridge name='ovsbr0'/>
|
||||
<virtualport type='openvswitch'/>
|
||||
</network>
|
||||
```
|
||||
|
||||
```xml
|
||||
<!-- LAN network for OVS -->
|
||||
<!-- ovs-lan-net.xml -->
|
||||
<network>
|
||||
<name>ovs-lan-net</name>
|
||||
<forward mode='bridge'/>
|
||||
<bridge name='ovsbr1'/>
|
||||
<virtualport type='openvswitch'/>
|
||||
<portgroup name='vlan-trunk'>
|
||||
<vlan trunk='yes'>
|
||||
<tag id='1' nativeMode='untagged'/>
|
||||
<tag id='10'/>
|
||||
</vlan>
|
||||
</portgroup>
|
||||
<portgroup name='vlan10-access'>
|
||||
<vlan>
|
||||
<tag id='10'/>
|
||||
</vlan>
|
||||
</portgroup>
|
||||
</network>
|
||||
```
|
||||
|
||||
- Define XML files
|
||||
|
||||
```bash
|
||||
virsh net-define ~/data/config/vms/networks/ovs-wan-net.xml
|
||||
virsh net-define ~/data/config/vms/networks/ovs-lan-net.xml
|
||||
virsh net-start ovs-wan-net
|
||||
virsh net-start ovs-lan-net
|
||||
virsh net-autostart ovs-wan-net
|
||||
virsh net-autostart ovs-lan-net
|
||||
```
|
||||
|
||||
#### virsh storage pool configuration
|
||||
|
||||
- Make a storage pool file
|
||||
- ~/data/config/vms/storages/vm-images.xml
|
||||
- `chmod 600 ~/data/config/vms/storages/*`
|
||||
|
||||
```xml
|
||||
<!-- Storage pool define -->
|
||||
<!-- vm-images.xml -->
|
||||
<pool type='dir'>
|
||||
<name>vm-images</name>
|
||||
<target>
|
||||
<path>/var/lib/libvirt/images</path>
|
||||
</target>
|
||||
</pool>
|
||||
```
|
||||
|
||||
- Define XML files
|
||||
|
||||
```bash
|
||||
virsh pool-define ~/data/config/vms/storages/vm-images.xml
|
||||
virsh pool-start vm-images
|
||||
virsh pool-autostart vm-images
|
||||
```
|
||||
|
||||
#### ACL setting
|
||||
|
||||
```bash
|
||||
# To manage qcow2 file without non-root user
|
||||
sudo setfacl -R -m u:vmm:rwx /var/lib/libvirt/images
|
||||
sudo setfacl -d -m u:vmm:rwx /var/lib/libvirt/images
|
||||
```
|
||||
|
||||
### Backup configuration
|
||||
|
||||
```bash
|
||||
cp /etc/network/interfaces /etc/default/grub /etc/modprobe.d/vfio.conf ~/data/config/server
|
||||
|
||||
sudo cp /etc/iptables/rules.v4 ~/data/config/server
|
||||
```
|
||||
|
||||
### Systemd
|
||||
|
||||
#### Linger configuration
|
||||
|
||||
```bash
|
||||
# Check linger configuration
|
||||
loginctl show-user vmm
|
||||
# Linger=yes
|
||||
# It is necessary to use the services after session is done
|
||||
```
|
||||
|
||||
- opnsense.service
|
||||
|
||||
```ini
|
||||
# ~/data/config/services/opnsense.service
|
||||
# ~/.config/systemd/user/opnsense.service
|
||||
[Unit]
|
||||
Description=opnsense Auto Booting
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
|
||||
# Maintain status as active
|
||||
RemainAfterExit=yes
|
||||
|
||||
# Run the service
|
||||
ExecStart=/usr/bin/virsh -c qemu:///system start opnsense
|
||||
|
||||
# Stop the service
|
||||
ExecStop=/usr/bin/virsh -c qemu:///system shutdown opnsense
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
```
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.config/systemd/user
|
||||
chmod -R 700 ~/.config
|
||||
ln -s ~/data/config/services/opnsense.service ~/.config/systemd/user/opnsense.service
|
||||
|
||||
systemctl --user daemon-reload
|
||||
```
|
||||
|
||||
- net.service
|
||||
|
||||
```ini
|
||||
# ~/data/config/services/net.service
|
||||
# ~/.config/systemd/user/net.service
|
||||
[Unit]
|
||||
Description=net Auto Booting
|
||||
After=opnsense.service
|
||||
Requires=opnsense.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
|
||||
# Maintain status as active
|
||||
RemainAfterExit=yes
|
||||
|
||||
# 8080 is CrowdSec API
|
||||
ExecStartPre=%h/data/config/scripts/wait-for-it.sh -h 192.168.10.1 -p 8080 -t 20
|
||||
|
||||
# Run the service
|
||||
ExecStart=/usr/bin/virsh -c qemu:///system start net
|
||||
|
||||
# Stop the service
|
||||
ExecStop=/usr/bin/virsh -c qemu:///system shutdown net
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
```
|
||||
@@ -0,0 +1,130 @@
|
||||
Tags: #os, #firewall, #configuration, #network, #virtualization
|
||||
|
||||
|
||||
## Preparation
|
||||
|
||||
### Create VM template
|
||||
|
||||
- ~/data/config/scripts/opnsense.sh
|
||||
|
||||
```bash
|
||||
virt-install \
|
||||
--import \ # Start without CD-ROM or location - This is for Serial installation.
|
||||
--boot uefi \ # Supports secure booting
|
||||
--name opnsense \ # VM name
|
||||
--os-variant freebsd14.2 \ # Choose kind of OS
|
||||
--vcpus 2 \ # Set number of vcpu (It means core of pcpu)
|
||||
--memory 4096 \ # Set the memory volume (Default: MiB)
|
||||
--disk path=/var/lib/libvirt/images/OPNs
|
||||
ense-25.7-serial-amd64.img,format=raw \ # Installation image file, format raw
|
||||
--disk pool=vm-images,size=72,format=qcow2,discard=unmap \ # The target disk to install opnsense, it is on SSD; discard=unmap option will emulate SSD.
|
||||
--network network=ovs-wan-net,model=virtio,mac=0A:49:6E:4D:00:00 \ # Use pre-designated network and specific MAC address
|
||||
--network network=ovs-lan-net,portgroup=vlan-trunk,model=virtio,mac=0A:49:6E:4D:00:01 \
|
||||
--graphics none \ # Don't use any graphic
|
||||
--console pty,target_type=serial # Use console serial
|
||||
# After enter this command, then the console start automatically
|
||||
# Remove all annotation before you make the sh file.
|
||||
```
|
||||
|
||||
### OPNsense installation
|
||||
|
||||
```sh
|
||||
# Interface setting
|
||||
Press any key to start the manual interface assignment: [enter]
|
||||
LAGGs: N
|
||||
VLANs: N
|
||||
Enter the WAN interface name or 'a' for auto-detection: vtnet0
|
||||
Enter the LAN interface name or 'a' for auto-detection: vtnet1
|
||||
Enter the Optional interface 1 name or 'a' for auto-detection: [blank]
|
||||
Do you want to proceed? : Y
|
||||
|
||||
# Login as an installer account
|
||||
login: installer
|
||||
password: opnsense
|
||||
|
||||
# installing
|
||||
Continue with default keymap
|
||||
Install (ZFS)
|
||||
stripe
|
||||
[*] vtbd1: yes
|
||||
Complete Install: Halt Now
|
||||
```
|
||||
|
||||
### Modify VM template
|
||||
|
||||
- virsh edit
|
||||
|
||||
```bash
|
||||
virsh edit opnsense
|
||||
```
|
||||
|
||||
- Edit template file
|
||||
|
||||
```xml
|
||||
<!-- opnsense -->
|
||||
...
|
||||
</vcpu>
|
||||
<cputune>
|
||||
<shares>2048</shares>
|
||||
</cputune>
|
||||
<!-- cpu priority - 1024: default/2048: high/512: low -->
|
||||
|
||||
<!--
|
||||
<disk>
|
||||
...
|
||||
<source file='/var/lib/libvirt/images/opnsense.img'/>
|
||||
...
|
||||
</disk>
|
||||
# Remove booting disk
|
||||
-->
|
||||
...
|
||||
</console>
|
||||
<channel type='unix'>
|
||||
<target type='virtio' name='org.qemu.guest_agent.0'/>
|
||||
</channel>
|
||||
<!-- Define QEMU-Guest-Agent channel -->
|
||||
```
|
||||
|
||||
- Save template file
|
||||
|
||||
```bash
|
||||
virsh dumpxml opnsense > ~/data/config/vms/dumps/opnsense.xml
|
||||
```
|
||||
|
||||
- opnsense.service
|
||||
|
||||
```ini
|
||||
# ~/data/config/services/opnsense.service
|
||||
# ~/.config/systemd/user/opnsense.service
|
||||
[Unit]
|
||||
Description=opnsense Auto Booting
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
|
||||
# Maintain status as active
|
||||
RemainAfterExit=yes
|
||||
|
||||
# Run the service
|
||||
ExecStart=/usr/bin/virsh -c qemu:///system start opnsense
|
||||
|
||||
# Stop the service
|
||||
ExecStop=/usr/bin/virsh -c qemu:///system shutdown opnsense
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
```
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.config/systemd/user
|
||||
|
||||
chmod -R 700 ~/.config
|
||||
|
||||
ln -s ~/data/config/services/opnsense.service ~/.config/systemd/user/opnsense.service
|
||||
|
||||
systemctl --user daemon-reload
|
||||
systemctl --user enable opnsense.service
|
||||
systemctl --user start opnsense.service
|
||||
```
|
||||
@@ -0,0 +1,91 @@
|
||||
Tags: #os, #firewall, #configuration, #network
|
||||
|
||||
## Web UI
|
||||
|
||||
### Access
|
||||
|
||||
- Browser: https://192.168.1.1
|
||||
- Login with root
|
||||
|
||||
### Wizard setting
|
||||
|
||||
- Hostname: opnsense
|
||||
- Domain: ilnmors.internal
|
||||
- Timezone: Asia/Seoul
|
||||
> - System:Settings:General - Time Zone
|
||||
- DNS Server: 1.1.1.2
|
||||
> Temporary DNS server, when Adguard home and BIND is building, the change it.
|
||||
> - System:Setting:General - DNS server
|
||||
- \[ \] Override DNS
|
||||
- \[ \] Enable Resolver
|
||||
|
||||
- WAN
|
||||
- type dhcp
|
||||
- \[\*\]Block RFC1918 Private Networks
|
||||
- \[\*\]Block bogon networks
|
||||
- LAN
|
||||
- \[ \] Configure DHCP server
|
||||
- Change root password
|
||||
|
||||
### User setting
|
||||
|
||||
- System:Access:Users - \[+\]
|
||||
- Name: opnsense
|
||||
- Full name: opnsense management account
|
||||
- E-mail: opnsense@ilnmors.internal
|
||||
- Group: admins
|
||||
|
||||
> When console access is needed, root account is necessary. Only root account can access console program. However, when using WebUI it is better to use separated account for security.
|
||||
|
||||
### system update and pkg
|
||||
|
||||
#### Updates
|
||||
|
||||
- System:Firmware:Updates
|
||||
|
||||
#### Plugins
|
||||
|
||||
- System:Firmware:Plugins
|
||||
- \[\*\] Community plugins
|
||||
- os-crowdsec
|
||||
- os-acme-client
|
||||
- os-qemu-guest-agent
|
||||
>after install and restart plugin check with CLI `virsh qemu-agent-command opnsense '{"execute":"guest-get-osinfo"}'` on hypervisor
|
||||
- os-telegraf
|
||||
- os-sftp-backup
|
||||
|
||||
|
||||
## Disable unused basic function
|
||||
|
||||
In this homelab project, Kea DHCP and AdGuard home, BIND will be used as DHCP server and DNS server. Therefore, it is necessary to disable Dnsmasq, ISC DHCP and Unbound which are used as basic function of DHCP and DNS in OPNsense.
|
||||
|
||||
- Services:Dnsmasq DNS&DHCP:General
|
||||
- \[ \] Enable
|
||||
- `Apply`
|
||||
- Services:ISC DHCPv4
|
||||
- \[ \] Enable
|
||||
- `Apply`
|
||||
- Services:ISC DHCPv6
|
||||
- \[ \] Enable
|
||||
- `Apply`
|
||||
- Services:Unbound DNS:General
|
||||
- \[ \] Enable
|
||||
- `Apply`
|
||||
## Backup
|
||||
|
||||
### ZFS Snapshot
|
||||
|
||||
Before major updating, it is important to make a ZFS snapshot.
|
||||
|
||||
- System:Snapshots - \[+\]
|
||||
- Name: \[Date\]
|
||||
- `Save`
|
||||
|
||||
### Setting backup
|
||||
|
||||
OPNsense is managed with configuration file. You can download the configuration file as XML format.
|
||||
|
||||
- System:Configuration:Backups
|
||||
- Download: `Download configuration`
|
||||
- Restore: Select file > `Restore configuration`
|
||||
- sftp: URL / SSH private key
|
||||
@@ -0,0 +1,159 @@
|
||||
Tags: #os, #firewall, #configuration, #network
|
||||
|
||||
## Interface configuration
|
||||
|
||||
### VLAN setting
|
||||
|
||||
- Interfaces:Devices:VLAN - \[+\]
|
||||
|
||||
| Device | Parent | Tag | Description |
|
||||
| :--------: | :----: | :-: | :---------: |
|
||||
| vlan0.1.10 | vtnet1 | 10 | Server |
|
||||
|
||||
> The device name is reserved in Web UI; Name of VLAN device must start with `vlan0` or `qinq0`. In this project, name of vlan device would be `vlan0.[interface_num].[tag_num]`
|
||||
|
||||
- `Apply`
|
||||
|
||||
### VPN setting
|
||||
|
||||
- VPN:WireGuard
|
||||
- \[\*\] Enable WireGuard
|
||||
- VPN:WireGuard:Instance - \[+\]
|
||||
|
||||
| Name | Listen Port | Tunnel Address |
|
||||
| :-------: | :---------: | :------------: |
|
||||
| WG_SERVER | 11290 | 10.10.10.1/24 |
|
||||
| WG_USER | 11291 | 10.10.1.1/24 |
|
||||
|
||||
- VPN:WireGuard:PeerGenerate
|
||||
|
||||
| Instance | End point | Name | Address | Pre-shared Key | Allowed IP | Keepalive interval | DNS |
|
||||
| :-------: | :---------------: | :-----: | :-----------: | :------------: | :------------: | :----------------: | :-----------: |
|
||||
| WG_SERVER | ilnmors.com:11290 | console | 10.10.10.2/32 | Generate | 192.168.0.0/16 | 25 | 192.168.10.11 |
|
||||
| WG_SERVER | ilnmors.com:11290 | phone | 10.10.10.3/32 | Generate | 192.168.0.0/16 | 25 | 192.168.10.11 |
|
||||
| WG_SERVER | ilnmors.com:11290 | spare | 10.10.10.4/32 | Generate | 192.168.0.0/16 | 25 | 192.168.10.11 |
|
||||
|
||||
> Set `PersistentKeepalive = 25` on peer, to avoid NAT timeout.
|
||||
|
||||
> Press `Store and generate next` button after generate each row.
|
||||
|
||||
> Before building net server (Private DNS), use 1.1.1.2 instead of 192.168.10.11.
|
||||
|
||||
|
||||
```ini
|
||||
# console
|
||||
[Interface]
|
||||
PrivateKey = 2ACJCZV7Zg4fxCHTxfbmAggX/x06Nt05CC6gJvcrokI=
|
||||
Address = 10.10.10.2/32
|
||||
DNS = 192.168.10.11
|
||||
|
||||
[Peer]
|
||||
PublicKey = jqDJKe8pZSK8GXwBnrjBJiflYvJDB7GfgogLsSSdnA4=
|
||||
PresharedKey = OCx3mohOp2Uipxda7ZJs+78Mjh3Lbf0UfvGZB4SULk4=
|
||||
Endpoint = ilnmors.com:11290
|
||||
AllowedIPs = 192.168.0.0/16
|
||||
PersistentKeepalive = 25
|
||||
|
||||
# phone
|
||||
[Interface]
|
||||
PrivateKey = oIhpQn7yTEU3wH+eOVJuzcgo4t05MBqv+OmD1KC/Z38=
|
||||
Address = 10.10.10.3/32
|
||||
DNS = 192.168.10.11
|
||||
|
||||
[Peer]
|
||||
PublicKey = jqDJKe8pZSK8GXwBnrjBJiflYvJDB7GfgogLsSSdnA4=
|
||||
PresharedKey = pqA5OtJy7lZHD+PzzqVEYa/iMHOOCNagAzCfTLoaDmw=
|
||||
Endpoint = ilnmors.com:11290
|
||||
AllowedIPs = 192.168.0.0/16
|
||||
PersistentKeepalive = 25
|
||||
|
||||
# spare
|
||||
[Interface]
|
||||
PrivateKey = EIHZRuI5IDG3h8mC3ez4I1duYuVbe5UwgWnVxbg9uFk=
|
||||
Address = 10.10.10.4/32
|
||||
DNS = 192.168.10.11
|
||||
|
||||
[Peer]
|
||||
PublicKey = jqDJKe8pZSK8GXwBnrjBJiflYvJDB7GfgogLsSSdnA4=
|
||||
PresharedKey = Xuf/G0HJ2S/zO0C4zUjjW2rD1b51yUYNhZaByEgfM6I=
|
||||
Endpoint = ilnmors.com:11290
|
||||
AllowedIPs = 192.168.0.0/16
|
||||
PersistentKeepalive = 25
|
||||
```
|
||||
|
||||
- `Apply`
|
||||
|
||||
### Interface assignment
|
||||
|
||||
- Interface:Assignments
|
||||
- vlan0.1.10 - VLAN10 - `Add`
|
||||
- wg0 - WG_SERVER - `Add`
|
||||
- wg1 - WG_USER - `Add`
|
||||
- `Save`
|
||||
|
||||
### Assigned interface setting
|
||||
|
||||
- Interface:WAN
|
||||
- \[\*\] Enable
|
||||
- \[\*\] Block private
|
||||
- \[\*\] Block bogon
|
||||
- \[\*\] IPv4 DHCP
|
||||
- `Save`
|
||||
- Interface:LAN
|
||||
- \[\*\] Enable
|
||||
- IPv4 Static (IPv6 Configuration Type: None)
|
||||
- 192.168.1.1/24
|
||||
- `Save`
|
||||
- Interface:VLAN10
|
||||
- \[\*\] Enable
|
||||
- IPv4 Static
|
||||
- 192.168.10.1/24
|
||||
- Interface:WG_SERVER
|
||||
- \[\*\] Enable
|
||||
- `Save`
|
||||
- Interface:WG_USER
|
||||
- \[\*\] Enable
|
||||
- `Save`
|
||||
- `Apply change`
|
||||
|
||||
### Web UI itself settings
|
||||
|
||||
- System:Settings:Administration
|
||||
- Web GUI
|
||||
- Listen Interfaces: LAN, VLAN10
|
||||
- `Save`
|
||||
- Firewall:Settings:Advanced
|
||||
- \[\*\] Disable anti-lockout
|
||||
- `Save`
|
||||
|
||||
## Aliases and group configuration
|
||||
|
||||
### Aliases setting
|
||||
|
||||
- Firewall:Aliases - \[+\]
|
||||
|
||||
| Name | Type | Content | Description |
|
||||
| :------------: | :-----: | :----------------------------------------------------------------------------------------------------: | :-----------------------------: |
|
||||
| ports_vpn | port(s) | 11290,11291 | ports udp vpn |
|
||||
| ports_web | port(s) | 80,443 | ports tcp web |
|
||||
| ports_dhcp4 | port(s) | 67,68 | ports udp dhcp4 |
|
||||
| ports_dns | port(s) | 53,443 | ports tcp/udp dns including DoH |
|
||||
| ports_crowdsec | port(s) | 8080 | ports tcp crowdsec |
|
||||
| hosts_console | host(s) | 192.168.1.11,10.10.10.2,10.10.10.3,10.10.10.4 | hosts console |
|
||||
| hosts_server | host(s) | 192.168.10.10,192.168.10.11,192.168.10.12,192.168.10.13,192.168.10.14,10.10.10.2,10.10.10.3,10.10.10.4 | hosts servers |
|
||||
| hosts_net | host(s) | 192.168.10.11 | hosts net |
|
||||
| hosts_auth | host(s) | 192.168.10.12 | hosts auth |
|
||||
|
||||
|
||||
- `Apply`
|
||||
|
||||
### Group setting
|
||||
|
||||
- Firewall:Groups - \[+\]
|
||||
|
||||
| Name | Members | Description |
|
||||
| :----: | :---------------: | :----------: |
|
||||
| SERVER | VLAN10, WG_SERVER | Server group |
|
||||
| USER | LAN, WG_USER | User group |
|
||||
|
||||
- `Apply`
|
||||
@@ -0,0 +1,79 @@
|
||||
Tags: #os, #firewall, #configuration, #network, #security
|
||||
|
||||
## OPNsense rules
|
||||
|
||||
### NAT
|
||||
|
||||
- Firewall:NAT:Outbound
|
||||
- Mode: Automatic outbound NAT rule generation (no manual rules can be used)
|
||||
- Firewall:NAT:Port Forward - \[+\]
|
||||
|
||||
| Interface | TCP/IP version | Proto | Destination | Destination port | Redirect target IP | Redirect target port | Description |
|
||||
| :-------: | :------------: | :---: | :---------: | :--------------: | :----------------: | :------------------: | :-----------------------------------------------: |
|
||||
| WAN | IPv4 | TCP | WAN address | ports_web | hosts_auth | ports_web | allow wan clients to access web services from WAN |
|
||||
|
||||
> Set after building main reverse proxy, all web packets from WAN are going to the reverse proxy.
|
||||
|
||||
> Filter rule association's `Add associated filter rule` option will automatically generate the `Pass` rule on WAN interface for Port Forwarding rule.
|
||||
|
||||
### Firewall rules
|
||||
|
||||
OPNsense has prioirty in order to rule's squence. The upper rule is prior than below one. Moreover, when `Quick` option is enabled, OPNsense doesn't check the rule below. It means OPNsense just applies the rule what it is first matched (First match). Reversely, when `Quick` option is disabled, OPNsense checks all rules to apply the packet (Last match).
|
||||
|
||||
#### WAN interface
|
||||
|
||||
- Firewall:Rules:WAN - \[+\]
|
||||
|
||||
| Action | Quick | Interface | Direction | TCP/IP version | Proto | Source | Destination | Destination port | Description |
|
||||
| :----: | :---: | :-------: | :-------: | :------------: | :---: | :----: | :---------: | :--------------: | :------------------------------------------------------------------------: |
|
||||
| Pass | * | WAN | IN | IPv4 | UDP | * | WAN address | ports_vpn | allow vpn clients from WAN |
|
||||
| Pass | * | WAN | IN | IPv4 | TCP | * | WAN address | ports_web | allow wan clients to access web services from WAN(Automatically generated) |
|
||||
|
||||
- `Apply changes`
|
||||
|
||||
#### SERVER interface
|
||||
|
||||
- Firewall:Rules:SERVER - \[+\]
|
||||
|
||||
| Action | Quick | Interface | Direction | TCP/IP version | Proto | Srouce | Destination | Destination port | Description |
|
||||
| :----: | :---: | :-------: | :-------: | :------------: | :---: | :-----------: | :-----------: | :--------------: | :--------------------------------------------------------------------: |
|
||||
| Pass | * | SERVER | IN | IPv4 | UDP | * | * | ports_dhcpv4 | allow server clients to access DHCPv4 server from SERVER net |
|
||||
| Pass | * | SERVER | IN | IPv4 | ICMP | SERVER net | This Firewall | - | allow server clients to access This Firewall with ICMP from SERVER net |
|
||||
| Block | * | SERVER | IN | IPv4 | * | !hosts_server | * | * | block undesignated server clients to access any from SERVER net |
|
||||
| Pass | * | SERVER | IN | IPv4 | UDP | SERVER net | This Firewall | NTP | allow server clients to access NTP from SERVER net |
|
||||
| Pass | * | SERVER | IN | IPv4 | TCP | SERVER net | This Firewall | ports_crowdsec | allow server clients to access CrowdSec LAPI from SERVER net |
|
||||
| Pass | * | SERVER | IN | IPv4 | TCP | hosts_console | This Firewall | ports_web | allow console to access This Firewall web GUI from SERVER net |
|
||||
| Pass | * | SERVER | IN | IPv4 | * | hosts_console | USER net | * | allow console to access USER net from SERVER net |
|
||||
| Block | * | SERVER | IN | IPv4 | TCP | SERVER net | This Firewall | * | block server clients to access This Firewall from SERVER net |
|
||||
| Block | * | SERVER | IN | IPv4 | * | SERVER net | USER net | * | block server clients to access USER net from SERVER net |
|
||||
| Pass | - | SERVER | IN | IPv4 | * | SERVER net | * | * | allow server clients to access WAN from SERVER net |
|
||||
|
||||
- `Apply changes`
|
||||
|
||||
> When you add the new server client, you must edit alias `hosts_server`
|
||||
|
||||
#### USER interface
|
||||
|
||||
- Firewall:Rules:USER - \[+\]
|
||||
|
||||
| Action | Quick | Interface | Direction | TCP/IP version | Proto | Srouce | Destination | Destination port | Description |
|
||||
| :----: | :---: | :-------: | :-------: | :------------: | :-----: | :-----------: | :-----------: | :--------------: | :----------------------------------------------------------------: |
|
||||
| Pass | * | USER | IN | IPv4 | UDP | * | * | ports_dhcpv4 | allow user clients to access DHCPv4 server from USER net |
|
||||
| Pass | * | USER | IN | IPv4 | ICMP | USER net | This Firewall | - | allow user clients to access This Firewall with ICMP from USER net |
|
||||
| Pass | * | USER | IN | IPv4 | TCP/UDP | USER net | hosts_net | ports_dns | allow user clients to access DNS server from USER net |
|
||||
| Pass | * | USER | IN | IPv4 | TCP | USER net | hosts_auth | ports_web | allow user clients to access reverse proxy from USER net |
|
||||
| Pass | * | USER | IN | IPv4 | TCP | hosts_console | This Firewall | ports_web | allow console to access This Firewall web GUI from USER net |
|
||||
| Pass | * | USER | IN | IPv4 | * | hosts_console | SERVER net | * | allow console to access SERVER net from USER net |
|
||||
| Block | * | USER | IN | IPv4 | * | USER net | This Firewall | * | block user clients to access This Firewall from USER net |
|
||||
| Block | * | USER | IN | IPv4 | * | USER net | SERVER net | * | block user clients to access SERVER net from USER net |
|
||||
| Pass | - | USER | IN | IPv4 | * | USER net | * | * | allow user clients to access WAN from USER net |
|
||||
|
||||
- `Apply changes`
|
||||
|
||||
#### LAN interface
|
||||
|
||||
OPNsense automatically generates all pass rules on LAN interface. The USER group will be used instead of LAN interface, all rules will be disabled after USER interface rules set.
|
||||
|
||||
- Firewall:Rules:LAN
|
||||
- Disable all auto generated rules
|
||||
- `Apply changes`
|
||||
@@ -0,0 +1,69 @@
|
||||
sTags: #os, #firewall, #configuration, #network, #security
|
||||
|
||||
## Suricata
|
||||
|
||||
Suricata is IPS(Intrusion Prevent System)/IDS(Intrusion Detection System) to supersede snort in 2010 from OISF for NSM(Network Security Monitoring). This program supports multi-thread and check the packet and even the programs (such as exe or shell script) in it based on rules. When the suricata find the packet matched with rule it has, it decides `pass; with alert`, `block` the packet.
|
||||
|
||||
### General setting in OPNsense
|
||||
|
||||
#### Enable Suricata
|
||||
|
||||
- Services:Intrusion Detection:Administration - Settings
|
||||
- \[\*\] Enabled
|
||||
- \[\*\] IPS mode
|
||||
- \[\*\] Promiscuous mode (Scan all traffic even L2 traffics)
|
||||
- Interfaces: Select ALL
|
||||
- Pattern matcher: Hyperscan (Intel's opensource regex matching library)
|
||||
|
||||
#### Rule set download and update
|
||||
|
||||
- Services:Intrusion Detection:Administration - Download
|
||||
|
||||
- Select the rule set below
|
||||
- ET open/botcc: List of bot net C&C server
|
||||
- ET open/compromised: List of known zombie PC
|
||||
- ET open/drop: List of certain malicious traffic
|
||||
- ET open/dshield: List of current activated malicious IP
|
||||
- ET open/emerging-attack_response: List of response after attack success
|
||||
- ET open/emerging-coinminer: List of coinminer malicious code
|
||||
- ET open/emerging-current_event: List of latest attack pattern
|
||||
- ET open/emerging-dns: List of malicious DNS query
|
||||
- ET open/emerging-exploit: List of attack towards software vulnerability
|
||||
- ET open/emerging-exploit_kit: List of automatic hacking tool
|
||||
- ET open/emerging-ja3: List of malicious programs'(tools, or browsers) finger print
|
||||
- ET open/emerging-malware: List of malware C&C server
|
||||
- ET open/emerging-mobile_malware: List of mobile malware C&C server
|
||||
- ET open/emerging-phishing: List of communication regard phishing
|
||||
- ET open/emerging-policy: List of non-standard or malicious TLS version or http traffics
|
||||
- ET open/emerging-scan: List of port scan or network scan traffic
|
||||
- ET open/emerging-shellcode: List of malicious attack shell script
|
||||
- ET open/emerging-sql: List of sql injection traffic
|
||||
- ET open/emerging-user_agent: List of malicious bot user-agent
|
||||
- ET open/emerging-web_client: List of attack towards web browser
|
||||
- ET open/emerging-web_server: List of attack towards web server
|
||||
- ET open/emerging-web_specific_apps: List of attack towards specific web application(like word press)
|
||||
- ET open/emerging-CS_c2: List of Cobalt Strike hacking tool
|
||||
- `Enable selected`
|
||||
- `Download & Update Rules`
|
||||
|
||||
> When you want to delete rule set, then select target rule and click `Disable selected` and `Download & Update Rules`
|
||||
|
||||
- Services:Intrusion Detection:Schedule
|
||||
- \[\*\] Enable update
|
||||
|
||||
#### Policy of rule set
|
||||
|
||||
Suricata rule set in OPNsense has basically `Alert` policy. Therefore, it is necessary to set rules as `Drop` manually. Except `ET open/emerging-ja3` and `ET open/emerging-policy`, it is good for security to drop all rule set. The reason why `ja3` and `policy` remain as `Alert` is they are not extremely dangerous, and they could drop normal packets frequently.
|
||||
|
||||
- Services:Intrusion Detection:Policy - Policies - \[+\]
|
||||
- Ruleset: All ruleset except `ja3` and `policy`
|
||||
- Action: Alert, Drop
|
||||
- New action: Drop
|
||||
- `Save`
|
||||
- `Apply`
|
||||
|
||||
### Check the log
|
||||
|
||||
- Services:Intrusion Detection:Administration - Alerts
|
||||
|
||||
If suricata made the wrong action like block the normal packets or pass malicious packet, you can tune the ruleset. At the Alerts section, next to log there's Info column. When you click the pencil icon on info column, then you can change that rule's action in specific environment; source IP. It is a suppress.
|
||||
@@ -0,0 +1,133 @@
|
||||
Tags: #os, #firewall, #configuration, #network, #security
|
||||
|
||||
## ACME client in OPNsense
|
||||
|
||||
ACME client needs private CA(Step-CA) and BIND to issue private certificates for TLS.
|
||||
|
||||
### Plugin package
|
||||
|
||||
- os-acme-client
|
||||
|
||||
### DNS records
|
||||
|
||||
- Add new domain in BIND, Following [here](../06_network/06_03_net_bind.md).
|
||||
- net server
|
||||
- file: ~/data/containers/bind/lib/db.ilnmors.internal
|
||||
```text
|
||||
...
|
||||
opnsense IN A 192.168.10.1
|
||||
...
|
||||
```
|
||||
|
||||
### Trust setting (CA)
|
||||
|
||||
- Add root CA's crt
|
||||
- System:Settings:Trust:Authorities - \[+\]
|
||||
- Method: Import an existing Certificate Authority
|
||||
- Description: step-ca.ilnmors.internal
|
||||
- Certificate Data: Content of root_ca.crt of Step-CA
|
||||
- `Save`
|
||||
|
||||
### ACME Client setting
|
||||
|
||||
#### ACME account and server setting
|
||||
|
||||
- Services:ACME Client:Settings
|
||||
- \[\*\] Enable Plugin
|
||||
- \[\*\] Auto Renewal
|
||||
- `Apply`
|
||||
|
||||
- Services:ACME Client:Accounts - \[+\]
|
||||
- Name: acme.ilnmors.internal
|
||||
> It is only referred to OPNsense itself. It is not the provisioner name. It doesn't support special characters such as `@` or `!`.
|
||||
- Description: acme
|
||||
- Custom CA URL: https://step-ca.ilnmors.internal:9000/acme/acme@ilnmors.internal/directory
|
||||
|
||||
> `[CA_URL]:[port]/acme/[provisioner_name]/directory`
|
||||
|
||||
- `Save`
|
||||
- `Register account` and check Status column `OK (registered)`
|
||||
|
||||
#### ACME challenge
|
||||
|
||||
There is private authoritative DNS server in this homelab, therefore DNS-01 challenge will be used.
|
||||
|
||||
- Services:ACME Client:Challenge Types - \[+\]
|
||||
- Name: ilnmors.internal-dns-01-challenge
|
||||
- Description: step-ca.ilnmors.internal dns-01 challenge
|
||||
- Challenge Type: DNS-01
|
||||
- DNS service: nsupdate (RFC 2136)
|
||||
- DNS Sleep Time: 10
|
||||
> If this option weren't set, ACME client send query towards public DNS. It is necessary to set this option to use private authoritative DNS server. After set value(second) ACME client send the query towards private authoritative DNS.
|
||||
|
||||
- Server (FQDN): bind.ilnmors.internal 2253
|
||||
> DNS server domain and port. It uses \[space\] as separator.
|
||||
|
||||
- Secret Key:
|
||||
```text
|
||||
key "acme-key" {
|
||||
algorithm hmac-sha256;
|
||||
secret "secret value";
|
||||
};
|
||||
```
|
||||
> About key, following [here](../06_network/06_03_net_bind.md)
|
||||
|
||||
- Zone: ilnmors.internal
|
||||
|
||||
#### Certificates
|
||||
|
||||
- Services:ACME Client:Certificates - \[+\]
|
||||
- Common name: opnsense.ilnmors.internal
|
||||
- Description: opnsense
|
||||
- Alt Names: crowdsec.ilnmors.internal
|
||||
- ACME account: acme.ilnmors.internal
|
||||
- Challenge Type: ilnmors.internal-dns-01-challenge
|
||||
- \[\*\] Auto Renewal
|
||||
- `Save`
|
||||
- Click `Issue or renew certificate` and check Last ACME status `OK`
|
||||
|
||||
#### Automations
|
||||
|
||||
##### Web UI restart automation configuration
|
||||
|
||||
- Services:ACME Client:Automations - \[\+\]
|
||||
- Name: opnsense-auto-restart-web-ui
|
||||
- Description: restart opnsense web ui
|
||||
- Run Command: Restart OPNsense web UI
|
||||
|
||||
> Web UI account `opnsense` doesn't use `sh`, it makes hard ACME client run the command via sh. Just use crontab to reload the crowdsec service everyday in `/usr/local/etc/cron.d/crowdsec`.
|
||||
|
||||
##### Example of remote automations
|
||||
|
||||
OPNsense's ACME client can upload certificates to other server or run specific command when the certificates is issued or renewed. Here is the example to upload the certificate towards other server via sftp.
|
||||
|
||||
- Services:ACME Client:Automations - \[+\]
|
||||
- Name: test_acme
|
||||
- Description: certificate upload test
|
||||
- Run Command: Upload certificate via SFTP
|
||||
- SFTP Host: net.ilnmors.internal
|
||||
- Host Key: \[blank\]
|
||||
> When it is not set, it automatically registers host key in `known_hosts`.
|
||||
- Username: net
|
||||
- Identity Type: ed25519
|
||||
- Remote Path: /home/net/certificate_test
|
||||
- `Show Identity` and copy the key value
|
||||
```text
|
||||
ssh-ed25519 [key_value] root@opnsense.ilnmors.internal
|
||||
```
|
||||
- Add this key on target server's ~/.ssh/authorized_keys
|
||||
- `Test Connection`
|
||||
- `Save`
|
||||
|
||||
- Services:ACME Client:Certificates - \[Edit Certificates\]
|
||||
- Automations: test_acme
|
||||
|
||||
|
||||
### OPNsense Web UI certificate setting
|
||||
|
||||
- System:Settings:Administration
|
||||
- SSL certificate: opnsense.ilnmors.internal (ACME Client)
|
||||
|
||||
### Crowdsec TLS setting
|
||||
|
||||
Following [here](../03_common/03_04_crowdsec.md)
|
||||
@@ -0,0 +1,40 @@
|
||||
Tags: #os, #firewall, #configuration, #network
|
||||
|
||||
## Kea DHCP in OPNsense
|
||||
|
||||
Kea DHCP is the next generation DHCP server of ISC(Internet Systems Consortium) to supersede ISC DHCP. Kea has more flexibility in configuration, and higher performance, and modern API than ISC DHCP. OPNsense also select Kea as the future DHCP server than ISC DHCP. Basically, Kea supports container(Docker or podman) but it doesn't use docker.io repository but cloudsmith.io repository. Moreover, Kea DHCP is separated into 3 module kea ddns, kea dhcp 4, kea dhcp 6 as containers. This fact makes network environment (especially when the rootless podman network and DHCP relay are considered.) complex. Therefore, even though Kea in OPNsense doesn't support DDNS module, in this home lab Kea DHCP will be located in OPNsense.
|
||||
|
||||
### Kea DHCP setting
|
||||
|
||||
- Services:Kea DHCP:Kea DHCPv4 - Settings
|
||||
- \[\*\] Enabled
|
||||
- Interfaces: LAN, VLAN10
|
||||
- \[ \] Firewall rules (Manually created)
|
||||
- Services:Kea DHCP:Kea DHCPv4 - Subnets - \[+\]
|
||||
- \[ \] Match client-id (To match based on MAC)
|
||||
- \[ \] Auto collect option data (To designate optional data manually)
|
||||
|
||||
| Subnet | Description | Pool | Routers(gateway) | DNS | Domain name |
|
||||
| :-------------: | :---------: | :---------------------------: | :--------------: | :-----------: | :--------------: |
|
||||
| 192.168.1.0/24 | LAN | 192.168.1.100-192.168.1.254 | 192.168.1.1 | 192.168.10.11 | ilnmors.internal |
|
||||
| 192.168.10.0/24 | VLAN10 | 192.168.10.254-192.168.10.254 | 192.168.10.1 | 192.168.10.11 | ilnmors.internal |
|
||||
|
||||
> Before building net server (Private DNS), use 1.1.1.2 instead of 192.168.10.11.
|
||||
|
||||
> The reason why VLAN10's pool has only 192.168.1.254 is VLAN10 allows only reservation IP.
|
||||
|
||||
- Services:Kea DHCP:KeaDHCPv4 - Reservation
|
||||
- Delete all rows.
|
||||
- Import csv file: kea_dhcp_v4_reservation.csv
|
||||
|
||||
```csv
|
||||
ip_address,hw_address,hostname,description,option_data,
|
||||
192.168.1.11,D8:E2:DF:FF:1B:D5,surface,console eth,
|
||||
192.168.1.30,38:CA:84:94:5E:06,printer,printer,
|
||||
192.168.10.12,0A:49:6E:4D:02:00,auth,auth,
|
||||
192.168.10.13,0A:49:6E:4D:03:00,dev,dev,
|
||||
192.168.10.14,0A:49:6E:4D:04:00,app,app,
|
||||
```
|
||||
- `Apply`
|
||||
|
||||
> vmm(Hypervisor), opnsense(firewall), net(Net server which has DNS in it) should use static IP instead of DHCP reservation. Because these servers are fundamental servers for building network and DHCP. It is very stable for DNS, hypervisor, firewall themselves to use static IP.
|
||||
@@ -0,0 +1,107 @@
|
||||
Tags: #os, #configuration, #network, #virtualization
|
||||
|
||||
## Preparation
|
||||
|
||||
### Create VM template
|
||||
|
||||
- ~/data/config/scripts/net.sh
|
||||
|
||||
```bash
|
||||
virt-install \
|
||||
--boot uefi \
|
||||
--name net \
|
||||
--os-variant debian13 \
|
||||
--vcpus 1 \
|
||||
--memory 2048 \
|
||||
--location /var/lib/libvirt/images/debian-13.0.0-amd64-netinst.iso \ # For serial installing, use `--location` instead of `--cdrom`
|
||||
--disk pool=vm-images,size=34,format=qcow2,discard=unmap \
|
||||
--network network=ovs-lan-net,portgroup=vlan10-access,model=virtio,mac=0A:49:6E:4D:01:00 \ # Use designated ovs port group
|
||||
--graphics none \
|
||||
--console pty,target_type=serial \
|
||||
--extra-args "console=ttyS0,115200"
|
||||
# After enter this command, then the console starts automatically
|
||||
# Remove all annotation before you make the sh file.
|
||||
```
|
||||
|
||||
### Debian installation
|
||||
|
||||
- Following [here](../03_common/03_01_debian_configuration.md) to install Debian.
|
||||
- Debian installer supports serial mode regardless getty@ttyS0 service is enabled or not.
|
||||
- Following [here](../03_common/03_02_iptables.md) to set iptables.
|
||||
- Following [here](../03_common/03_04_crowdsec.md) to set CrowdSec
|
||||
|
||||
#### Serial console setting
|
||||
|
||||
After installation, use `ctrl + ]` to exit console. Before setting getty@ttyS0, you can't use serial console to access VM. Therefore, use IP address set on installation, and connect net server via ssh first, following the step to enable the getty.
|
||||
|
||||
### Modify VM template settings
|
||||
|
||||
After getty setting, shutdown net vm with `shutdown` in VM or `sudo virsh shutdown net` in hypervisor to turn off vm first.
|
||||
|
||||
```bash
|
||||
virsh edit net
|
||||
```
|
||||
|
||||
```xml
|
||||
<!-- net -->
|
||||
...
|
||||
</vcpu>
|
||||
<cputune>
|
||||
<shares>512</shares>
|
||||
</cputune>
|
||||
<!-- cpu priority - 1024: default/2048: high/512: low -->
|
||||
|
||||
<!--
|
||||
<disk type='file' device='cdrom'>
|
||||
...
|
||||
</disk>
|
||||
# Remove booting disk
|
||||
-->
|
||||
```
|
||||
|
||||
```bash
|
||||
virsh dumpxml net > ~/data/config/vms/dumps/net.xml
|
||||
# Start net server with console
|
||||
```
|
||||
|
||||
### Common setting
|
||||
|
||||
- net.service
|
||||
|
||||
```ini
|
||||
# ~/data/config/services/net.service
|
||||
# ~/.config/systemd/user/net.service
|
||||
[Unit]
|
||||
Description=net Auto Booting
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
Requires=opnsense.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
|
||||
# Maintain status as active
|
||||
RemainAfterExit=yes
|
||||
|
||||
# CrowdSec should be set
|
||||
ExecStartPre=%h/data/config/scripts/wait-for-it.sh 192.168.10.1:8080 -t 0
|
||||
|
||||
ExecStartPre=/bin/bash -c "sleep 15"
|
||||
|
||||
# Run the service
|
||||
ExecStart=/usr/bin/virsh -c qemu:///system start net
|
||||
|
||||
# Stop the service
|
||||
ExecStop=/usr/bin/virsh -c qemu:///system shutdown net
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
```
|
||||
|
||||
```bash
|
||||
ln -s ~/data/config/services/net.service ~/.config/systemd/user/net.service
|
||||
|
||||
systemctl --user daemon-reload
|
||||
systemctl --user enable net.service
|
||||
systemctl --user start net.service
|
||||
```
|
||||
@@ -0,0 +1,387 @@
|
||||
Tags: #os, #configuration, #network
|
||||
|
||||
## DDNS
|
||||
|
||||
### Secret management
|
||||
|
||||
- File:
|
||||
- ~/data/config/secrets/.secret.yaml
|
||||
- /etc/secrets/2001/ddns.env
|
||||
|
||||
- Edit `.secret.yaml` with `edit_secret.sh`
|
||||
|
||||
```yaml
|
||||
# ~/data/config/secrets/.secret.yaml
|
||||
# DDNS
|
||||
DDNS:
|
||||
ZONE_ID: 'encrypted value'
|
||||
API_KEY: 'encrypted value'
|
||||
```
|
||||
|
||||
```bash
|
||||
extract_secret.sh ~/data/config/secrets/.secret.yaml -e "DDNS" > /run/user/$UID/tmp.env && sudo mv /run/user/$UID/tmp.env /etc/secrets/$UID/ddns.env && sudo chown $UID:root /etc/secrets/$UID/ddns.env && sudo chmod 400 /etc/secrets/$UID/ddns.env
|
||||
```
|
||||
|
||||
### ddns.sh
|
||||
|
||||
- File: ~/data/config/scripts/ddns/ddns.sh
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# ~/data/config/scripts/ddns.sh
|
||||
|
||||
# Designate directory
|
||||
# DIRECTORY="$HOME/data/config/scripts"
|
||||
|
||||
# Information
|
||||
DOMAIN=""
|
||||
TTL=180
|
||||
C_TTL=86400
|
||||
PROXIED="false"
|
||||
DELETE_FLAG="false"
|
||||
CURRENT_IP=""
|
||||
|
||||
# These will be injected by systemd
|
||||
# ZONE_ID='.secret'
|
||||
# API_KEY='.secret'
|
||||
|
||||
# usage() function
|
||||
usage() {
|
||||
echo "Usage: $0 -d \"domain\" [-t \"ttl\"] [-p] [-r] [-c]"
|
||||
echo "-d <domain>: Specify the domain to update"
|
||||
echo "-t <ttl>: Specify the TTL(Time to live)"
|
||||
echo "-p: Specify the cloudflare proxy to use"
|
||||
echo "-r: Delete the DNS record"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# getopts to get arguments
|
||||
while getopts "d:t:pr" opt; do
|
||||
case $opt in
|
||||
d)
|
||||
DOMAIN="$OPTARG"
|
||||
;;
|
||||
t)
|
||||
TTL="$OPTARG"
|
||||
;;
|
||||
p)
|
||||
PROXIED="true"
|
||||
;;
|
||||
r)
|
||||
DELETE_FLAG="true"
|
||||
;;
|
||||
\?) # unknown options
|
||||
echo "Invalid option: -$OPTARG" >&2
|
||||
usage
|
||||
;;
|
||||
:) # parameter required option
|
||||
echo "Option -$OPTARG requires an argument." >&2
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Get option and move to parameters - This has no functional thing, because it only use arguments with parameters
|
||||
shift $((OPTIND - 1))
|
||||
|
||||
# Check necessary options
|
||||
if [ -z "$DOMAIN" ]; then
|
||||
echo "Error: -d option is required" >&2
|
||||
usage
|
||||
fi
|
||||
|
||||
if ! [[ "$TTL" =~ ^[0-9]+$ ]] || [ "$TTL" -le 0 ]; then
|
||||
echo "Error: -t option (ttl) requires a number above 0." >&2
|
||||
usage
|
||||
fi
|
||||
|
||||
# log() function
|
||||
log()
|
||||
{
|
||||
local text="$1"
|
||||
echo -e "$(date "+%Y-%m-%d %H:%M:%S"): [ddns] $text"
|
||||
}
|
||||
|
||||
# Make log directory
|
||||
# if [ ! -d "$DIRECTORY/log" ]; then
|
||||
# mkdir "$DIRECTORY/log"
|
||||
# fi
|
||||
|
||||
# Check and create log file
|
||||
# LOG_FILE="$DIRECTORY/log/ddns_$(date "+%Y-%m-%d").log"
|
||||
# if [ ! -f "$LOG_FILE" ]; then
|
||||
# log "Notice: log file is created"
|
||||
# fi
|
||||
|
||||
# Check package
|
||||
if ! command -v curl &> /dev/null; then
|
||||
log "Error: curl package is needed"
|
||||
exit
|
||||
fi
|
||||
if ! command -v jq &> /dev/null; then
|
||||
log "Error: jq package is needed"
|
||||
exit
|
||||
fi
|
||||
|
||||
# API options
|
||||
URL="https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records"
|
||||
CONTENT_TYPE="Content-Type: application/json"
|
||||
AUTHORIZATION="Authorization: Bearer $API_KEY"
|
||||
|
||||
# Current IP check
|
||||
CURRENT_IP=$(curl -sf "https://ifconfig.me") ||\
|
||||
CURRENT_IP=$(curl -sf "https://ifconfig.kr") ||\
|
||||
CURRENT_IP=$(curl -sf "https://api.ipify.org")
|
||||
if [ "$CURRENT_IP" == "" ]; then
|
||||
log "Error: Can't get an IP"
|
||||
exit
|
||||
fi
|
||||
|
||||
# DNS functions
|
||||
|
||||
# get_dns_record() function
|
||||
get_dns_record()
|
||||
{
|
||||
local type="$1"
|
||||
local name="$2"
|
||||
|
||||
local response="$(
|
||||
curl -s "$URL?type=$type&name=$name"\
|
||||
-H "$CONTENT_TYPE"\
|
||||
-H "$AUTHORIZATION")"
|
||||
if [ "$(echo "$response" | jq -r '.success')" == "false" ]; then
|
||||
log "Error: Can't get dns record\"Reason: $response"
|
||||
exit
|
||||
else
|
||||
echo "$response"
|
||||
fi
|
||||
}
|
||||
|
||||
# create_dns_record() function
|
||||
create_dns_record()
|
||||
{
|
||||
local type="$1"
|
||||
local name="$2"
|
||||
local ttl="$3"
|
||||
local comment="$4"
|
||||
local content="$5"
|
||||
local response="$(
|
||||
curl -s "$URL"\
|
||||
-X POST\
|
||||
-H "$CONTENT_TYPE"\
|
||||
-H "$AUTHORIZATION"\
|
||||
-d "{
|
||||
\"name\": \"$name\",
|
||||
\"ttl\": $ttl,
|
||||
\"type\": \"$type\",
|
||||
\"comment\": \"$comment\",
|
||||
\"content\": \"$content\",
|
||||
\"proxied\": $PROXIED
|
||||
}")"
|
||||
if [ "$(echo "$response" | jq -r '.success')" == "false" ]; then
|
||||
log "Error: Can't create dns record\"Reason: $response"
|
||||
exit
|
||||
else
|
||||
echo "$response"
|
||||
fi
|
||||
}
|
||||
|
||||
# update_dns_record() function
|
||||
update_dns_record()
|
||||
{
|
||||
local type="$1"
|
||||
local name="$2"
|
||||
local ttl="$3"
|
||||
local comment="$4"
|
||||
local content="$5"
|
||||
local id="$6"
|
||||
local response=$(
|
||||
curl -s "$URL/$id"\
|
||||
-X PUT\
|
||||
-H "$CONTENT_TYPE"\
|
||||
-H "$AUTHORIZATION"\
|
||||
-d "{
|
||||
\"name\": \"$name\",
|
||||
\"ttl\": $ttl,
|
||||
\"type\": \"$type\",
|
||||
\"comment\": \"$comment\",
|
||||
\"content\": \"$content\",
|
||||
\"proxied\": $PROXIED
|
||||
}")
|
||||
if [ "$(echo "$response" | jq -r '.success')" == "false" ]; then
|
||||
log "Error: Can't update dns record\"Reason: $response"
|
||||
exit
|
||||
else
|
||||
echo "$response"
|
||||
fi
|
||||
}
|
||||
|
||||
# delete_dns_record() function
|
||||
delete_dns_record()
|
||||
{
|
||||
local type="$1"
|
||||
local id="$2"
|
||||
|
||||
local response=$(
|
||||
curl -s "$URL/$id"\
|
||||
-X DELETE\
|
||||
-H "$CONTENT_TYPE"\
|
||||
-H "$AUTHORIZATION"
|
||||
)
|
||||
if [ "$(echo "$response" | jq -r '.success')" == "false" ]; then
|
||||
log "Error: Can't delete dns record\"Reason: $response"
|
||||
exit
|
||||
else
|
||||
echo "$response"
|
||||
fi
|
||||
}
|
||||
|
||||
# Get DNS A, and CNAME record
|
||||
A_DNS_RECORD=$(get_dns_record "A" "$DOMAIN")
|
||||
S_DNS_RECORD=$(get_dns_record "cname" "*.$DOMAIN")
|
||||
W_DNS_RECORD=$(get_dns_record "cname" "www.$DOMAIN")
|
||||
|
||||
# Delete DNS record with Delete flag
|
||||
if [ "$DELETE_FLAG" == "true" ]; then
|
||||
FLAG="false"
|
||||
if [ "$(echo $A_DNS_RECORD | jq -r '.result | length')" -eq 1 ]; then
|
||||
A_DNS_ID="$(echo $A_DNS_RECORD | jq -r '.result[0].id')"
|
||||
delete_dns_record "A" "$A_DNS_ID"
|
||||
log "Delete: root DNS record is deleted"
|
||||
FLAG="true"
|
||||
fi
|
||||
if [ "$(echo $S_DNS_RECORD | jq -r '.result | length')" -eq 1 ]; then
|
||||
S_DNS_ID="$(echo $S_DNS_RECORD | jq -r '.result[0].id')"
|
||||
delete_dns_record "cname" "$S_DNS_ID"
|
||||
log "Delete: sub DNS record is deleted"
|
||||
FLAG="true"
|
||||
fi
|
||||
if [ "$(echo $W_DNS_RECORD | jq -r '.result | length')" -eq 1 ]; then
|
||||
W_DNS_ID="$(echo $W_DNS_RECORD | jq -r '.result[0].id')"
|
||||
delete_dns_record "cname" "$W_DNS_ID"
|
||||
log "Delete: www DNS record is deleted"
|
||||
FLAG="true"
|
||||
fi
|
||||
if [ "$FLAG" == "false" ]; then
|
||||
log "Notice: Nothing is Deleted. There are no DNS records"
|
||||
fi
|
||||
exit
|
||||
fi
|
||||
|
||||
# Create or update DNS A record
|
||||
if [ "$(echo $A_DNS_RECORD | jq -r '.result | length')" -eq 1 ]; then # root DNS record exist
|
||||
A_DNS_ID="$(echo $A_DNS_RECORD | jq -r '.result[0].id')"
|
||||
A_DNS_CONTENT="$(echo $A_DNS_RECORD | jq -r '.result[0].content')"
|
||||
A_DNS_TTL="$(echo $A_DNS_RECORD | jq -r '.result[0].ttl')"
|
||||
A_DNS_PROXIED="$(echo $A_DNS_RECORD | jq -r '.result[0].proxied')"
|
||||
if [ "$A_DNS_CONTENT" != $CURRENT_IP -o "$A_DNS_TTL" != "$TTL" -o "$A_DNS_PROXIED" != "$PROXIED" ]; then
|
||||
update_dns_record "A" "$DOMAIN" "$TTL" "$(date "+%Y-%m-%d %H:%M:%S"): root domain from ddns.sh" "$CURRENT_IP" "$A_DNS_ID"
|
||||
log "Update: Root DNS record is successfully changed\nDomain: $DOMAIN\nIP: $A_DNS_CONTENT to $CURRENT_IP\nTTL: $A_DNS_TTL to $TTL\nproxied: $A_DNS_PROXIED to $PROXIED"
|
||||
else
|
||||
log "Notice: Root DNS record is not changed\nDomain: $DOMAIN\nIP: $CURRENT_IP\nTTL: $TTL\nproxied: $PROXIED"
|
||||
fi
|
||||
else # root DNS record does not exist
|
||||
create_dns_record "A" "$DOMAIN" "$TTL" "$(date "+%Y-%m-%d %H:%M:%S"): root domain from ddns.sh" "$CURRENT_IP"
|
||||
log "Create: Root DNS record is successfully created\nDomain: $DOMAIN\nIP: $CURRENT_IP\nTTL: $TTL\nproxied: $PROXIED"
|
||||
fi
|
||||
|
||||
# Create or update DNS CNAME records
|
||||
if [ "$(echo $S_DNS_RECORD | jq -r '.result | length')" -eq 1 ]; then # sub DNS record exist
|
||||
S_DNS_ID="$(echo $S_DNS_RECORD | jq -r '.result[0].id')"
|
||||
S_DNS_CONTENT="$(echo $S_DNS_RECORD | jq -r '.result[0].content')"
|
||||
S_DNS_TTL="$(echo $S_DNS_RECORD | jq -r '.result[0].ttl')"
|
||||
S_DNS_PROXIED="$(echo $S_DNS_RECORD | jq -r '.result[0].proxied')"
|
||||
if [ "$S_DNS_CONTENT" != "$DOMAIN" -o "$S_DNS_TTL" != "$C_TTL" -o "$S_DNS_PROXIED" != "$PROXIED" ]; then
|
||||
update_dns_record "cname" "*.$DOMAIN" "$C_TTL" "$(date "+%Y-%m-%d %H:%M:%S"): sub domain from ddns.sh" "$DOMAIN" "$S_DNS_ID"
|
||||
log "Update: Sub DNS record is successfully changed\nDomain: $S_DNS_CONTENT to *.$DOMAIN\ncname: $DOMAIN \nTTL: $S_DNS_TTL to $C_TTL\nproxied: $S_DNS_PROXIED to $PROXIED"
|
||||
else
|
||||
log "Notice: Sub DNS record is not changed\nDomain: *.$DOMAIN\ncname: $DOMAIN\nTTL: $C_TTL\nproxied: $PROXIED"
|
||||
fi
|
||||
else # sub DNS record does not exist
|
||||
create_dns_record "cname" "*.$DOMAIN" "$C_TTL" "$(date "+%Y-%m-%d %H:%M:%S"): sub domain from ddns.sh" "$DOMAIN"
|
||||
log "Create: Sub DNS record is successfully created\nDomain: *.$DOMAIN\ncname: $DOMAIN\nTTL: $C_TTL\nproxied: $PROXIED"
|
||||
fi
|
||||
|
||||
if [ "$(echo $W_DNS_RECORD | jq -r '.result | length')" -eq 1 ]; then # www DNS record exist
|
||||
W_DNS_ID="$(echo $W_DNS_RECORD | jq -r '.result[0].id')"
|
||||
W_DNS_CONTENT="$(echo $W_DNS_RECORD | jq -r '.result[0].content')"
|
||||
W_DNS_TTL="$(echo $W_DNS_RECORD | jq -r '.result[0].ttl')"
|
||||
W_DNS_PROXIED="$(echo $W_DNS_RECORD | jq -r '.result[0].proxied')"
|
||||
if [ "$W_DNS_CONTENT" != "$DOMAIN" -o "$W_DNS_TTL" != "$C_TTL" -o "$W_DNS_PROXIED" != "$PROXIED" ]; then
|
||||
update_dns_record "cname" "www.$DOMAIN" "$C_TTL" "$(date "+%Y-%m-%d %H:%M:%S"): www domain from ddns.sh" "$DOMAIN" "$W_DNS_ID"
|
||||
log "Update: www DNS record is successfully changed\nDomain: $W_DNS_CONTENT to www.$DOMAIN\ncname: $DOMAIN\nTTL: $W_DNS_TTL to $C_TTL\nproxied: $W_DNS_PROXIED to $PROXIED"
|
||||
else
|
||||
log "Notice: www DNS record is not changed\nDomain: www.$DOMAIN\ncname: $DOMAIN\nTTL: $C_TTL\nproxied: $PROXIED"
|
||||
fi
|
||||
else # www DNS record does not exist
|
||||
create_dns_record "cname" "www.$DOMAIN" "$C_TTL" "$(date "+%Y-%m-%d %H:%M:%S"): www domain from ddns.sh" "$DOMAIN"
|
||||
log "Create: www DNS record is successfully created\nDomain: www.$DOMAIN\ncname: $DOMAIN\nTTL: $C_TTL\nproxied: $PROXIED"
|
||||
fi
|
||||
|
||||
# Remove old backup file (7days before)
|
||||
# find "$DIRECTORY/log" -maxdepth 1 -type f -mtime +7 -delete
|
||||
```
|
||||
|
||||
### Systemd
|
||||
|
||||
- File:
|
||||
- ~/data/config/services/ddns/ddns.service
|
||||
- ~/data/config/services/ddns/ddns.timer
|
||||
- /etc/secrets/2001/ddns.env
|
||||
|
||||
```ini
|
||||
# ~/data/config/services/ddns/ddns.service
|
||||
# ~/.config/systemd/user/ddns.service
|
||||
[Unit]
|
||||
Description=DDNS Update Service
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
|
||||
# logging
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
|
||||
# EnvironmentFile
|
||||
EnvironmentFile=/etc/secrets/%U/ddns.env
|
||||
|
||||
# Run the script
|
||||
ExecStart=/bin/bash -c '%h/data/config/scripts/ddns/ddns.sh -d "ilnmors.com"'
|
||||
|
||||
```
|
||||
|
||||
```ini
|
||||
# ~/data/config/services/ddns/ddns.timer
|
||||
# ~/.config/systemd/user/ddns.timer
|
||||
[Unit]
|
||||
Description=Run DDNS update service every 5 minutes
|
||||
|
||||
[Timer]
|
||||
# Execute service after 1 min on booting
|
||||
OnBootSec=1min
|
||||
|
||||
# Execute service every 5mins
|
||||
OnUnitActiveSec=5min
|
||||
|
||||
# When timer is activated, Service also starts.
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
```
|
||||
|
||||
```bash
|
||||
# Register service
|
||||
mkdir -p ~/.config/systemd/user && chmod -R 700 ~/.config
|
||||
|
||||
ln -s ~/data/config/services/ddns/ddns.service ~/.config/systemd/user/ddns.service
|
||||
|
||||
ln -s ~/data/config/services/ddns/ddns.timer ~/.config/systemd/user/ddns.timer
|
||||
|
||||
systemctl --user daemon-reload
|
||||
|
||||
# Start timer and enable
|
||||
systemctl --user enable --now ddns.timer
|
||||
```
|
||||
@@ -0,0 +1,338 @@
|
||||
Tags: #os, #configuration, #network, #virtualization, #container
|
||||
|
||||
## BIND9
|
||||
|
||||
BIND9 is an open source authoritative DNS software of ISC. It can work as a personal authoritative in private network environment.
|
||||
|
||||
### Secret management
|
||||
|
||||
- File:
|
||||
- ~/data/config/secrets/.secret.yaml
|
||||
|
||||
- Edit `.secret.yaml` with `edit_secret.sh`
|
||||
|
||||
#### Generate TSIG key as ACME key
|
||||
|
||||
```bash
|
||||
podman run --rm --entrypoint /bin/sh internetsystemsconsortium/bind9:9.20 -c "tsig-keygen -a hmac-sha256 acme-key"
|
||||
# Paste and add on .secret.yaml
|
||||
# key "acme-key" {
|
||||
# algorithm hmac-sha256;
|
||||
# secret "your_base64_tsig_secret_here";
|
||||
# };'
|
||||
```
|
||||
|
||||
```yaml
|
||||
# ~/data/config/secrets/.secret.yaml
|
||||
BIND9_ACME_KEY: |
|
||||
key "acme-key" {
|
||||
algorithm hmac-sha256;
|
||||
secret "your_base64_tsig_secret_here";
|
||||
};
|
||||
```
|
||||
|
||||
```bash
|
||||
# Copy the secret value from .secret.yaml
|
||||
|
||||
# Podman secret
|
||||
extract_secret.sh ~/data/config/secrets/.secret.yaml -f "BIND9_ACME_KEY" | podman secret create "acme-key" -
|
||||
```
|
||||
|
||||
|
||||
### Preparation
|
||||
|
||||
#### iptables and firewall rules
|
||||
|
||||
- Set iptables first, following [here](../03_common/03_02_iptables.md).
|
||||
- Set firewall rules first, following [here](Latest/05_firewall/05_04_opnsense_rules.md).
|
||||
|
||||
#### Create directory for container
|
||||
|
||||
```bash
|
||||
mkdir -p ~/data/containers/bind
|
||||
chmod 700 ~/data/containers/bind
|
||||
sudo setfacl -m d:g::0 ~/data/containers/bind
|
||||
sudo setfacl -m d:o::0 ~/data/containers/bind
|
||||
sudo setfacl -m u:net:rwx ~/data/containers/bind
|
||||
sudo setfacl -m u:100052:rwx ~/data/containers/bind
|
||||
sudo setfacl -d -m u:net:rwx ~/data/containers/bind
|
||||
sudo setfacl -d -m u:100052:rwx ~/data/containers/bind
|
||||
mkdir -p ~/data/containers/bind/{cache,etc,lib,log}
|
||||
nano ~/data/containers/bind/{etc,lib}/configuration_files
|
||||
```
|
||||
|
||||
> BIND9 container executes as 53:53(bind:bind) permission in container. It is mapped host's 100052. Therefore, directories have to have ACL via `setfacl`
|
||||
|
||||
|
||||
### Podman Image
|
||||
|
||||
```bash
|
||||
podman pull internetsystemsconsortium/bind9:9.20 # Do not use latest version to management
|
||||
```
|
||||
|
||||
### Configuration files
|
||||
|
||||
> If `named.conf` file didn't exist in `/etc/bind` or it had error in it, the container would be terminated without any logs. Before starting BIND container, you should makes all configuration file already.
|
||||
|
||||
#### named.conf
|
||||
|
||||
- file: ~/data/containers/bind/etc/named.conf
|
||||
|
||||
```ini
|
||||
include "/run/secrets/keys/acme-key";
|
||||
include "/run/secrets/keys/ddns-key";
|
||||
|
||||
options {
|
||||
directory "/var/cache/bind";
|
||||
|
||||
listen-on { any; };
|
||||
listen-on-v6 { ::1; };
|
||||
|
||||
// Authoritative DNS setting
|
||||
allow-recursion { none; };
|
||||
allow-transfer { none; };
|
||||
allow-update { none; };
|
||||
|
||||
dnssec-validation no;
|
||||
|
||||
check-names master warn;
|
||||
};
|
||||
|
||||
zone "ilnmors.internal." {
|
||||
type primary;
|
||||
file "/var/lib/bind/db.ilnmors.internal";
|
||||
notify yes;
|
||||
// ACME-01 challenge policy. It allows only subdomain's TXT record update.
|
||||
update-policy {
|
||||
grant acme-key subdomain ilnmors.internal. TXT;
|
||||
grant ddns-key subdomain ilnmors.internal. A AAAA DHCID;
|
||||
};
|
||||
};
|
||||
|
||||
zone "1.168.192.in-addr.arpa" {
|
||||
type primary;
|
||||
file "/var/lib/bind/db.1.168.192.in-addr.arpa";
|
||||
notify yes;
|
||||
update-policy {
|
||||
grant ddns-key subdomain 1.168.192.in-addr.arpa PTR DHCID;
|
||||
};
|
||||
};
|
||||
|
||||
zone "10.168.192.in-addr.arpa" {
|
||||
type primary;
|
||||
file "/var/lib/bind/db.10.168.192.in-addr.arpa";
|
||||
notify yes;
|
||||
update-policy {
|
||||
grant ddns-key subdomain 10.168.192.in-addr.arpa PTR DHCID;
|
||||
};
|
||||
};
|
||||
|
||||
zone "ilnmors.com." {
|
||||
//split horizon dns
|
||||
type primary;
|
||||
file "/var/lib/bind/db.ilnmors.com";
|
||||
notify yes;
|
||||
};
|
||||
|
||||
logging {
|
||||
channel default_log {
|
||||
stderr;
|
||||
severity info;
|
||||
};
|
||||
category default { default_log; };
|
||||
category config { default_log; };
|
||||
category queries { default_log; };
|
||||
};
|
||||
```
|
||||
|
||||
>If ddns function were required, generate tsig key for ddns and give update policy for each zone like below.
|
||||
>
|
||||
> - include "/etc/bind/ddns-key";
|
||||
> - update-policy { grant ddns-key subdomain \[zone_domain\] ANY A AAAA TXT; };
|
||||
|
||||
- Verify the named.conf with the command `named-checkconf`
|
||||
|
||||
#### Zone files
|
||||
|
||||
> When you add the record, you should use `.` at the end of the domain. i.e. mydomain.internal.
|
||||
|
||||
- file:
|
||||
- ~/data/containers/bind/lib/db.ilnmors.internal
|
||||
|
||||
```ini
|
||||
$TTL 86400
|
||||
|
||||
@ IN SOA bind.ilnmors.internal. mail.ilnmors.internal. (
|
||||
2025113001 ; serial
|
||||
3600 ; refresh (1 hour)
|
||||
1800 ; retry (30 minutes)
|
||||
604800 ; expire (1 week)
|
||||
86400 ; minimum (1 day)
|
||||
)
|
||||
IN NS bind.ilnmors.internal.
|
||||
bind IN A 192.168.10.11
|
||||
opnsense IN A 192.168.10.1
|
||||
vmm IN A 192.168.10.10
|
||||
net IN A 192.168.10.11
|
||||
crowdsec IN CNAME opnsense.ilnmors.internal.
|
||||
adguard IN CNAME net.ilnmors.internal.
|
||||
step-ca IN CNAME auth.ilnmors.internal.
|
||||
caddy IN CNAME auth.ilnmors.internal.
|
||||
ldap IN CNAME auth.ilnmors.internal.
|
||||
authelia IN CNAME auth.ilnmors.internal.
|
||||
code-server IN CNAME dev.ilnmors.internal.
|
||||
postgresql IN CNAME dev.ilnmors.internal.
|
||||
```
|
||||
|
||||
- ~/data/containers/bind/lib/db.1.168.192.in-addr.arpa
|
||||
|
||||
```ini
|
||||
$TTL 86400
|
||||
|
||||
@ IN SOA bind.ilnmors.internal. mail.ilnmors.internal. (
|
||||
yyyymmdd01 ; serial
|
||||
3600 ; refresh (1 hour)
|
||||
1800 ; retry (30 minutes)
|
||||
604800 ; expire (1 week)
|
||||
86400 ; minimum (1 day)
|
||||
)
|
||||
IN NS bind.ilnmors.internal.
|
||||
1 IN PTR opnsense.ilnmors.internal.
|
||||
11 IN PTR console.ilnmors.internal.
|
||||
30 IN PTR printer.ilnmors.internal.
|
||||
```
|
||||
|
||||
- ~/data/containers/bind/lib/db.10.168.192.in-addr.arpa
|
||||
|
||||
```ini
|
||||
$TTL 86400
|
||||
|
||||
@ IN SOA bind.ilnmors.internal. mail.ilnmors.internal. (
|
||||
yyyymmdd01 ; serial
|
||||
3600 ; refresh (1 hour)
|
||||
1800 ; retry (30 minutes)
|
||||
604800 ; expire (1 week)
|
||||
86400 ; minimum (1 day)
|
||||
)
|
||||
IN NS bind.ilnmors.internal.
|
||||
1 IN PTR opnsense.ilnmors.internal.
|
||||
10 IN PTR vmm.ilnmors.internal.
|
||||
11 IN PTR net.ilnmors.internal.
|
||||
12 IN PTR auth.ilnmors.internal.
|
||||
13 IN PTR dev.ilnmors.internal.
|
||||
14 IN PTR app.ilnmors.internal.
|
||||
```
|
||||
|
||||
- ~/data/containers/bind/lib/db.ilnmors.com
|
||||
|
||||
```ini
|
||||
$TTL 86400
|
||||
|
||||
@ IN SOA bind.ilnmors.internal. mail.ilnmors.internal. (
|
||||
yyyymmdd01 ; serial
|
||||
3600 ; refresh (1 hour)
|
||||
1800 ; retry (30 minutes)
|
||||
604800 ; expire (1 week)
|
||||
86400 ; minimum (1 day)
|
||||
)
|
||||
IN NS bind.ilnmors.internal.
|
||||
* IN A 192.168.10.12
|
||||
```
|
||||
|
||||
```bash
|
||||
# Adguard container has Requires=bind.service. When it restarted, then Adguard also restarted.
|
||||
systemctl --user restart bind
|
||||
```
|
||||
### Quadlet
|
||||
|
||||
- File:
|
||||
- ~/data/config/containers/bind/bind.container
|
||||
|
||||
```ini
|
||||
# ~/data/config/containers/bind/bind.container
|
||||
[Quadlet]
|
||||
DefaultDependencies=false
|
||||
|
||||
[Unit]
|
||||
Description=BIND9 DNS
|
||||
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Container]
|
||||
Image=docker.io/internetsystemsconsortium/bind9:9.20
|
||||
|
||||
ContainerName=bind
|
||||
|
||||
PublishPort=2253:53/tcp
|
||||
PublishPort=2253:53/udp
|
||||
|
||||
Volume=%h/data/containers/bind/etc:/etc/bind:rw
|
||||
Volume=%h/data/containers/bind/lib:/var/lib/bind:rw
|
||||
Volume=%h/data/containers/bind/cache:/var/cache/bind:rw
|
||||
Volume=%h/data/containers/bind/log:/var/log:rw
|
||||
|
||||
Environment="TZ=Asia/Seoul"
|
||||
Secret=acme-key,target=/run/secrets/key/acme-key
|
||||
Secret=ddns-key,target=/run/secrets/key/ddns-key
|
||||
|
||||
Label=diun.enable=true
|
||||
Label=diun.watch_repo=true
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
```
|
||||
|
||||
#### Create systemd `.service` file
|
||||
|
||||
```bash
|
||||
# linger has to be activated
|
||||
mkdir -p ~/.config/containers/systemd
|
||||
|
||||
ln -s ~/data/config/containers/bind/bind.container ~/.config/containers/systemd/bind.container
|
||||
|
||||
# This command makes bind.service
|
||||
systemctl --user daemon-reload
|
||||
```
|
||||
|
||||
#### Enable and start service
|
||||
|
||||
```bash
|
||||
systemctl --user start bind.service
|
||||
```
|
||||
|
||||
|
||||
### nsupdate (RFC 2136) verification
|
||||
|
||||
```bash
|
||||
# Update query at net server
|
||||
# Before test, create temp file .bind.acme-key
|
||||
nsupdate -k /run/user/$UID/.bind.acme-key
|
||||
> server 127.0.0.1 2253
|
||||
> zone ilnmors.internal
|
||||
> update add _acme-challenge.ilnmors.internal. 60 TXT "validation-test"
|
||||
> send
|
||||
> `ctrl+c`
|
||||
|
||||
# Verify
|
||||
dig @127.0.0.1 -p 2253 _acme-challenge.ilnmors.internal. TXT
|
||||
# Print
|
||||
;; QUESTION SECTION:
|
||||
;_acme-challenge.ilnmors.internal. IN TXT
|
||||
|
||||
;; ANSWER SECTION:
|
||||
_acme-challenge.ilnmors.internal. 60 IN TXT "validation-test"
|
||||
|
||||
# Delete query at net server
|
||||
nsupdate -k /run/user/$UID/.bind.acme-key
|
||||
> server 127.0.0.1 2253
|
||||
> zone ilnmors.internal
|
||||
> update delete _acme-challenge.ilnmors.internal. 60 TXT "validation-test"
|
||||
> send
|
||||
> `ctrl+c`
|
||||
|
||||
# Verify
|
||||
dig @127.0.0.1 -p 2253 _acme-challenge.ilnmors.internal. TXT
|
||||
# Print
|
||||
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 17572
|
||||
```
|
||||
@@ -0,0 +1,293 @@
|
||||
Tags: #os, #configuration, #network, #virtualization, #container
|
||||
|
||||
## AdGuard Home
|
||||
|
||||
AdGuard Home is one of open sourced recursive DNS resolver which can block malicious domain. It supports powerful DNS query filter based on ruleset and split horizon DNS service, plus recursive query on DoH and DoT towards public or internal authoritative DNS server.
|
||||
|
||||
### Secret
|
||||
|
||||
Adgaurd Home container doesn't need any secret value.
|
||||
|
||||
### Preparation
|
||||
|
||||
#### Add new domain in BIND
|
||||
|
||||
Following [here](../06_network/06_03_net_bind.md).
|
||||
|
||||
- net server
|
||||
- file: ~/data/containers/bind/lib/db.ilnmors.internal
|
||||
|
||||
```ini
|
||||
# ...
|
||||
adguard IN CNAME net.ilnmors.internal.
|
||||
# ...
|
||||
```
|
||||
|
||||
```bash
|
||||
# Adguard container has Requires=bind.service. When it restarted, then Adguard also restarted.
|
||||
systemctl --user restart bind
|
||||
```
|
||||
#### iptables and firewall rules
|
||||
|
||||
- Set iptables first, following [here](../03_common/03_02_iptables.md).
|
||||
- Set firewall rules first, following [here](Latest/05_firewall/05_04_opnsense_rules.md).
|
||||
|
||||
#### Create directory for container
|
||||
|
||||
```bash
|
||||
mkdir -p ~/data/containers/adguard
|
||||
chmod 700 ~/data/containers/adguard
|
||||
sudo setfacl -m d:g::0 ~/data/containers/adguard
|
||||
sudo setfacl -m d:o::0 ~/data/containers/adguard
|
||||
mkdir -p ~/data/containers/adguard/{work,config,certs}
|
||||
|
||||
```
|
||||
|
||||
> AdGuard Home container executes as root permission in container. It is not necessary to give `setfacl`. Because the container's root account is mapped as host's uid.
|
||||
|
||||
### Podman Image
|
||||
|
||||
```bash
|
||||
podman pull adguard/adguardhome:v0.107.68 # Do not use latest version to management
|
||||
```
|
||||
|
||||
### Quadlet
|
||||
|
||||
- File:
|
||||
- ~/data/config/containers/adguard/adguard.container
|
||||
|
||||
```ini
|
||||
# ~/data/config/containers/adguard/adguard.container
|
||||
[Quadlet]
|
||||
DefaultDependencies=false
|
||||
|
||||
[Unit]
|
||||
Description=AdGuard Home DNS
|
||||
|
||||
After=bind.service
|
||||
Requires=bind.service
|
||||
|
||||
|
||||
[Container]
|
||||
Image=docker.io/adguard/adguardhome:v0.107.68
|
||||
|
||||
ContainerName=adguard
|
||||
|
||||
AddHost=bind.ilnmors.internal:host-gateway
|
||||
|
||||
PublishPort=2053:53/tcp
|
||||
PublishPort=2053:53/udp
|
||||
PublishPort=2443:443/tcp
|
||||
PublishPort=2443:443/udp
|
||||
PublishPort=3000:3000/tcp
|
||||
# 3000 is temporary port, After TLS setting, delete it.
|
||||
|
||||
Volume=%h/data/containers/adguard/work:/opt/adguardhome/work:rw
|
||||
Volume=%h/data/containers/adguard/config:/opt/adguardhome/conf:rw
|
||||
Volume=%h/data/containers/adguard/certs:/etc/ssl/adguard:ro
|
||||
|
||||
Environment="TZ=Asia/Seoul"
|
||||
|
||||
Label=diun.enable=true
|
||||
Label=diun.watch_repo=true
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
```
|
||||
|
||||
#### Create systemd `.service` file
|
||||
|
||||
```bash
|
||||
ln -s ~/data/config/containers/adguard/adguard.container ~/.config/containers/systemd/adguard.container
|
||||
|
||||
systemctl --user daemon-reload
|
||||
```
|
||||
|
||||
#### Enable and start service
|
||||
|
||||
```bash
|
||||
systemctl --user start adguard.service
|
||||
```
|
||||
|
||||
|
||||
### Web UI configuration
|
||||
|
||||
#### Access web UI and initial setting
|
||||
|
||||
- URL: http://192.168.10.11:3000
|
||||
> After TLS certificate setting, access to adguard via https `https://192.168.10.11` or `https://adguard.ilnmors.internal`
|
||||
|
||||
#### Initial setting wizard
|
||||
|
||||
- Administrator interface
|
||||
- Network interface: all
|
||||
- Interface port: 3000
|
||||
- DNS server
|
||||
- Network interface: all
|
||||
- Interface port: 53
|
||||
- Authentication
|
||||
- User: adguard
|
||||
- Password: Password
|
||||
|
||||
### AdGuard Configuration
|
||||
|
||||
#### General setting
|
||||
|
||||
- Filter update interval: 1 hour
|
||||
- \[\*\] Use AdGuard browsing security web service
|
||||
- \[\*\] Enable log
|
||||
- Query logs rotation - 90 days
|
||||
- \[\*\] Enable statistics
|
||||
- `Save`
|
||||
|
||||
#### DNS settings
|
||||
|
||||
- Upstream DNS servers
|
||||
|
||||
```text
|
||||
# Cloudflare DNS
|
||||
tls://security.cloudflare-dns.com
|
||||
# Internal DNS bind.ilnmors.internal:2253
|
||||
[/ilnmors.internal/]udp://bind.ilnmors.internal:2253
|
||||
# Split Horizon DNS
|
||||
[/*.ilnmors.com/]udp://bind.ilnmors.internal:2253
|
||||
```
|
||||
|
||||
> Internal authoritative DNS will be BIND. BIND will use 2253 port. `bind.ilnmors.internal` is defined on container's `/etc/hosts`, it allows communication with host system.
|
||||
|
||||
- \[\*\] Parallel requests
|
||||
- Private reverse DNS servers
|
||||
|
||||
```text
|
||||
udp://bind.ilnmors.internal:2253
|
||||
```
|
||||
|
||||
> Set this option after BIND prepared
|
||||
|
||||
- \[\*\] Use private reverse DNS resolvers
|
||||
- \[\*\] Enable reverse resolving of client's IP addresses
|
||||
- `Apply` and `Test upstreams`
|
||||
|
||||
- \[ \] Enable cache
|
||||
|
||||
- `Save`
|
||||
- Disallowed domains
|
||||
|
||||
```text
|
||||
...
|
||||
wpad.ilnmors.internal
|
||||
_ldap._tcp.dc._msdcs.ilnmors.internal
|
||||
```
|
||||
|
||||
- `Save configuration`
|
||||
|
||||
#### Client settings
|
||||
|
||||
- Add Client
|
||||
|
||||
| Client | Name |
|
||||
| :----------------------------------------------: | :-------: |
|
||||
| 169.254.1.2, 192.168.10.11 | localhost |
|
||||
| 10.10.10.2, 10.10.10.3, 10.10.10.4, 192.168.1.11 | console |
|
||||
|
||||
#### Filters - DNS blocklists
|
||||
|
||||
- Add blocklist - Choose from the list
|
||||
- 1Hosts (Lite)
|
||||
- AdGuard DNS filter
|
||||
- AdAway DNS Popup Hosts filter
|
||||
- HaGeZi's Ultimate Blocklist
|
||||
- OISD Blocklist Big
|
||||
- KOR: List-KR DNS
|
||||
|
||||
#### DNS rewrites
|
||||
|
||||
- Filters:DNS rewrites
|
||||
- bind.ilnmors.internal - 192.168.10.11
|
||||
|
||||
#### Clients' DNS setting
|
||||
|
||||
##### OPNsense
|
||||
|
||||
- System:Settings:General
|
||||
- DNS server: 192.168.10.11
|
||||
- Services:KEA DHCP:KEA DHCPv4:Subnets
|
||||
- DNS server: 192.168.10.11
|
||||
|
||||
##### Hypervisor
|
||||
|
||||
- /etc/resolv.conf
|
||||
- nameserver 192.168.10.11
|
||||
|
||||
##### net
|
||||
|
||||
- /etc/resolv.conf
|
||||
- nameserver 192.168.10.11
|
||||
|
||||
##### VPN client
|
||||
```ini
|
||||
[Interface]
|
||||
DNS = 192.168.10.11
|
||||
```
|
||||
|
||||
|
||||
---
|
||||
#### Encryption setting
|
||||
|
||||
> It requires `BIND` and `Step-CA`
|
||||
|
||||
- \[\*\] Enable Encryption (HTTPS, DNS-over_HTTPS, and DNS-over-TLS)
|
||||
- \[\*\] Enable plain DNS
|
||||
- \[\*\] Redirect to HTTPS automatically
|
||||
- HTTPS port: 443
|
||||
|
||||
##### Certificates
|
||||
|
||||
> AdGuard home doesn't support ACME protocol by itself. It is necessary to use OPNsense's ACME client function to automate adguard home certificates.
|
||||
|
||||
- Add new domain in BIND, Following [here](../06_network/06_03_net_bind.md).
|
||||
- net server
|
||||
- file: ~/data/containers/bind/lib/db.ilnmors.internal
|
||||
```text
|
||||
...
|
||||
adguard IN CNAME net.ilnmors.internal.
|
||||
...
|
||||
```
|
||||
|
||||
- ACME setting (OPNsense)
|
||||
- Services:ACME Client:Certificates - Certificates - \[+\]
|
||||
- Common Name: adguard.ilnmors.internal
|
||||
- Description: adguard
|
||||
- ACME Account: acme.ilnmors.internal
|
||||
> Even though provisioner's name includes `@`, it has to use as `.`.
|
||||
>
|
||||
> i.e. `acme@ilnmors.internal` > `acme.ilnmors.internal`
|
||||
- Challenge Type: ilnmors.internal-dns-01-challenge
|
||||
- \[\*\] Auto Renewal
|
||||
- Automations: adguard-auto-acme, adguard-auto-restart
|
||||
|
||||
- Automations (OPNsense)
|
||||
- Services:ACME Client:Automations - Automation - \[+\]
|
||||
- Name: adguard-auto-acme / adguard-auto-restart
|
||||
- Description: adguard acme crt issue / restart adguard after crt is issued
|
||||
- Run Command: Upload certificate via SFTP / Remote command via SSH
|
||||
- SFTP Host: adguard.ilnmors.internal
|
||||
- Username: net
|
||||
- Identity Type: ed25519
|
||||
- Remote Path(SFTP): /home/net/data/containers/adguard/certs
|
||||
- Command(SSH): systemctl --user restart adguard
|
||||
- `Show Identity`
|
||||
> Copy Required parameters `ssh-ed25519 ~~~ root@opnsense.ilnmors.internal`
|
||||
>
|
||||
> Add parameters in net server's ~/.ssh/authorized_keys
|
||||
- `Test Connect` and `Save`
|
||||
|
||||
- \[\*\] Set a certificates file path: `/etc/ssl/adguard/adguard.ilnmors.internal/fullchain.pem`
|
||||
- \[\*\] Set a private key file: `/etc/ssl/adguard/adguard.ilnmors.internal/key.pem`
|
||||
- \[Save configuration\]
|
||||
|
||||
##### Modify container file
|
||||
|
||||
- Delete `PublishPort=3000:3000/tcp` part
|
||||
- `systemctl --user daemon-reload`
|
||||
- `systemctl --user start adguard`
|
||||
@@ -0,0 +1,630 @@
|
||||
Tags: #os, #configuration, #network, #virtualization, #container
|
||||
|
||||
|
||||
# KEA
|
||||
|
||||
#### Generate TSIG key as DDNS key
|
||||
|
||||
```bash
|
||||
podman run --rm --entrypoint /bin/sh internetsystemsconsortium/bind9:9.20 -c "tsig-keygen -a hmac-sha256 acme-key"
|
||||
# Paste and add on .secret.yaml
|
||||
# key "acme-key" {
|
||||
# algorithm hmac-sha256;
|
||||
# secret "your_base64_tsig_secret_here";
|
||||
# };'
|
||||
```
|
||||
|
||||
### DHCPv4
|
||||
#### Add user to group
|
||||
|
||||
```bash
|
||||
sudo apt install kea-dhcp4-server kea-dhcp-ddns-server
|
||||
sudo usermod -aG _kea net
|
||||
sudo chmod 770 /etc/kea /var/lib/kea
|
||||
sudo chmod 660 /etc/kea/kea-dhcp4.conf
|
||||
sudo mkdir /etc/secrets/$(id -u _kea)
|
||||
sudo chown _kea:root /etc/secrets/$(id -u _kea)
|
||||
sudo chmod 500 /etc/secrets/$(id -u _kea)
|
||||
sudo touch /etc/secrets/$(id -u _kea)/ddns-key
|
||||
sudo nano /etc/secrets/$(id -u _kea)/ddns-key
|
||||
sudo chown _kea:root /etc/secrets/$(id -u _kea)/ddns-key
|
||||
sudo chmod 400 /etc/secrets/$(id -u _kea)/ddns-key
|
||||
```
|
||||
|
||||
iptables
|
||||
```
|
||||
*mangle
|
||||
|
||||
*filter
|
||||
-A INPUT -p udp -m udp --dport 67 -m comment --comment "allow upd DHCPv4 connection" -j ACCEPT
|
||||
|
||||
# check
|
||||
sudo bash -c 'iptables-restore --test < /etc/iptables/rules.v4'
|
||||
```
|
||||
|
||||
kea-dhcp4.conf
|
||||
```json
|
||||
{
|
||||
"Dhcp4": {
|
||||
"subnet4": [
|
||||
{
|
||||
"subnet": "192.168.10.0/24",
|
||||
"pools" : [
|
||||
{
|
||||
"pool": "192.168.10.254-192.168.10.254"
|
||||
}
|
||||
],
|
||||
"option-data": [
|
||||
{
|
||||
"name": "routers",
|
||||
"data": "192.168.10.1"
|
||||
},
|
||||
{
|
||||
"name": "ntp-servers",
|
||||
"data": "192.168.10.1"
|
||||
},
|
||||
{
|
||||
"name": "domain-name-servers",
|
||||
"data": "192.168.10.11"
|
||||
},
|
||||
{
|
||||
"name": "domain-name",
|
||||
"data": "ilnmors.internal"
|
||||
}
|
||||
],
|
||||
"reservations": [
|
||||
{
|
||||
"hw-address": "0a:49:6e:4d:02:00",
|
||||
"ip-address": "192.168.10.12",
|
||||
"hostname": "auth"
|
||||
},
|
||||
{
|
||||
"hw-address": "0a:49:6e:4d:03:00",
|
||||
"ip-address": "192.168.10.13",
|
||||
"hostname": "dev"
|
||||
},
|
||||
{
|
||||
"hw-address": "0a:49:6e:4d:04:00",
|
||||
"ip-address": "192.168.10.14",
|
||||
"hostname": "app"
|
||||
}
|
||||
],
|
||||
"id": 1,
|
||||
"interface": "enp1s0"
|
||||
},
|
||||
{
|
||||
"subnet": "192.168.1.0/24",
|
||||
"relay": {
|
||||
"ip-addresses": [ "192.168.1.1" ]
|
||||
},
|
||||
"pools": [
|
||||
{
|
||||
"pool": "192.168.1.100-192.168.1.254"
|
||||
}
|
||||
],
|
||||
"option-data": [
|
||||
{
|
||||
"name": "routers",
|
||||
"data": "192.168.1.1"
|
||||
},
|
||||
{
|
||||
"name": "ntp-servers",
|
||||
"data": "192.168.1.1"
|
||||
},
|
||||
{
|
||||
"name": "domain-name-servers",
|
||||
"data": "192.168.10.11"
|
||||
},
|
||||
{
|
||||
"name": "domain-name",
|
||||
"data": "ilnmors.internal"
|
||||
}
|
||||
],
|
||||
"reservations": [
|
||||
{
|
||||
"hw-address": "d8:e2:df:ff:1b:d5",
|
||||
"ip-address": "192.168.1.11",
|
||||
"hostname": "surface"
|
||||
},
|
||||
{
|
||||
"hw-address": "38:ca:84:94:5e:06",
|
||||
"ip-address": "192.168.1.30",
|
||||
"hostname": "printer"
|
||||
}
|
||||
],
|
||||
"id": 2,
|
||||
"interface": "enp1s0"
|
||||
}
|
||||
],
|
||||
"interfaces-config": {
|
||||
"interfaces": [
|
||||
"enp1s0"
|
||||
],
|
||||
"dhcp-socket-type": "raw",
|
||||
"service-sockets-max-retries": 5,
|
||||
"service-sockets-require-all": true
|
||||
},
|
||||
"renew-timer": 1000,
|
||||
"rebind-timer": 2000,
|
||||
"valid-lifetime": 4000,
|
||||
"loggers": [
|
||||
{
|
||||
"name": "kea-dhcp4",
|
||||
"output_options": [
|
||||
{
|
||||
"output": "stdout"
|
||||
}
|
||||
],
|
||||
"severity": "INFO"
|
||||
}
|
||||
],
|
||||
"lease-database": {
|
||||
"type": "memfile",
|
||||
"persist": true,
|
||||
"name": "/var/lib/kea/kea-leases4.csv",
|
||||
"lfc-interval": 3600
|
||||
},
|
||||
"dhcp-ddns": {
|
||||
"enable-updates": false,
|
||||
"server-ip": "127.0.0.1",
|
||||
"server-port": 53001
|
||||
"ncr-protocol": "UDP"
|
||||
"ncr-format": "JSON"
|
||||
},
|
||||
"ddns-send-updates": true,
|
||||
"ddns-update-on-renew": true,
|
||||
"ddns-qualifying-suffix": "ilnmors.internal",
|
||||
"ddns-override-no-update": true,
|
||||
"ddns-override-client-update": true,
|
||||
"ddns-replace-client-name": "when-present",
|
||||
"ddns-generated-prefix": "host"
|
||||
"hostname-char-set": "[^a-zA-Z0-9.-]",
|
||||
"hostname-char-replacement": "-"
|
||||
}
|
||||
}
|
||||
|
||||
// There is one interface for DHCP host. In this case, the subnet which has dhcp server should be `id=1`. Because Kea DHCP allocate IP from the `id=1` subnet when it recieve pacekt has no giaddr. When Kea DHCP is running on Router, then id is not important because DHCP allocate IP based on their interface.
|
||||
```
|
||||
|
||||
#### OPNsense
|
||||
- Services:Kea DHCP:Kea DHCPv4
|
||||
- \[ \] Enabled
|
||||
- Services:DHCP Relay:Configuration
|
||||
- Destination - \[+\]
|
||||
- Name: kea-dhcp-v4
|
||||
- Server: 192.168.10.11
|
||||
- Relays - \[+\]
|
||||
- \[\*\] Enabled
|
||||
- Interface: LAN
|
||||
- Desitination kea-dhcp-v4
|
||||
- \[ \]Agent Information
|
||||
- Status `green box` check
|
||||
|
||||
### DDNS
|
||||
|
||||
```bash
|
||||
sudo nano /etc/apparmor.d/local/usr.sbin.kea-dhcp-ddns
|
||||
|
||||
# /etc/secrets/** r,
|
||||
|
||||
sudo apparmor_parser -r /etc/apparmor.d/usr.sbin.kea-dhcp-ddns
|
||||
sudo systemctl restart kea-dhcp-ddns-server.service
|
||||
```
|
||||
|
||||
/etc/secrets/102/ddns-key
|
||||
```json
|
||||
[
|
||||
{
|
||||
"name": "ddns-key",
|
||||
"algorithm": hmac-sha256;
|
||||
"secret": "secret_value"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
/etc/kea/kea-dhcp-ddns.conf
|
||||
```json
|
||||
{
|
||||
"DhcpDdns":
|
||||
{
|
||||
"ip-address": "127.0.0.1",
|
||||
"port": 53001,
|
||||
"control-socket": {
|
||||
"socket-type": "unix",
|
||||
"socket-name": "/run/kea/kea-ddns-ctrl-socket"
|
||||
},
|
||||
"tsig-keys": <?include "/etc/secrets/102/ddns-key"?>,
|
||||
"forward-ddns" : {
|
||||
"ddns-domains": [
|
||||
{
|
||||
"name": "ilnmors.internal.",
|
||||
"key-name": "ddns-key",
|
||||
"dns-servers": [
|
||||
{
|
||||
"ip-address": "127.0.0.1",
|
||||
"port": 2053
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"reverse-ddns" : {
|
||||
"ddns-domains": [
|
||||
{
|
||||
"name": "10.168.192.in-addr.arpa.",
|
||||
"key-name": "ddns-key",
|
||||
"dns-servers": [
|
||||
{
|
||||
"ip-address": "127.0.0.1",
|
||||
"port": 2053
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "1.168.192.in-addr.arpa.",
|
||||
"key-name": "ddns-key",
|
||||
"dns-servers": [
|
||||
{
|
||||
"ip-address": "127.0.0.1",
|
||||
"port": 2053
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"loggers": [
|
||||
{
|
||||
"name": "kea-dhcp-ddns",
|
||||
"output-options": [
|
||||
{
|
||||
"output": "stdout"
|
||||
}
|
||||
],
|
||||
"severity": "INFO",
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
kea-dhcp4.conf
|
||||
``` json
|
||||
// ...
|
||||
"dhcp-ddns": {
|
||||
"enable-updates": true,
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## BIND9
|
||||
|
||||
BIND9 is an open source authoritative DNS software of ISC. It can work as a personal authoritative in private network environment.
|
||||
|
||||
### Secret management
|
||||
|
||||
- File:
|
||||
- ~/data/config/secrets/.secret.yaml
|
||||
|
||||
- Edit `.secret.yaml` with `edit_secret.sh`
|
||||
|
||||
#### Generate TSIG key as ACME key
|
||||
|
||||
```bash
|
||||
podman run --rm --entrypoint /bin/sh internetsystemsconsortium/bind9:9.20 -c "tsig-keygen -a hmac-sha256 acme-key"
|
||||
# Paste and add on .secret.yaml
|
||||
# key "acme-key" {
|
||||
# algorithm hmac-sha256;
|
||||
# secret "your_base64_tsig_secret_here";
|
||||
# };'
|
||||
```
|
||||
|
||||
```yaml
|
||||
# ~/data/config/secrets/.secret.yaml
|
||||
BIND9_ACME_KEY: |
|
||||
key "acme-key" {
|
||||
algorithm hmac-sha256;
|
||||
secret "your_base64_tsig_secret_here";
|
||||
};
|
||||
```
|
||||
|
||||
```bash
|
||||
# Copy the secret value from .secret.yaml
|
||||
|
||||
# Podman secret
|
||||
extract_secret.sh ~/data/config/secrets/.secret.yaml -f "BIND9_ACME_KEY" | podman secret create "acme-key" -
|
||||
```
|
||||
|
||||
|
||||
### Preparation
|
||||
|
||||
#### iptables and firewall rules
|
||||
|
||||
- Set iptables first, following [here](../03_common/03_02_iptables.md).
|
||||
- Set firewall rules first, following [here](Latest/05_firewall/05_04_opnsense_rules.md).
|
||||
|
||||
#### Create directory for container
|
||||
|
||||
```bash
|
||||
mkdir -p ~/data/containers/bind
|
||||
chmod 700 ~/data/containers/bind
|
||||
sudo setfacl -m d:g::0 ~/data/containers/bind
|
||||
sudo setfacl -m d:o::0 ~/data/containers/bind
|
||||
sudo setfacl -m u:net:rwx ~/data/containers/bind
|
||||
sudo setfacl -m u:100052:rwx ~/data/containers/bind
|
||||
sudo setfacl -d -m u:net:rwx ~/data/containers/bind
|
||||
sudo setfacl -d -m u:100052:rwx ~/data/containers/bind
|
||||
mkdir -p ~/data/containers/bind/{cache,etc,lib,log}
|
||||
nano ~/data/containers/bind/{etc,lib}/configuration_files
|
||||
```
|
||||
|
||||
> BIND9 container executes as 53:53(bind:bind) permission in container. It is mapped host's 100052. Therefore, directories have to have ACL via `setfacl`
|
||||
|
||||
|
||||
### Podman Image
|
||||
|
||||
```bash
|
||||
podman pull internetsystemsconsortium/bind9:9.20 # Do not use latest version to management
|
||||
```
|
||||
|
||||
### Configuration files
|
||||
|
||||
> If `named.conf` file didn't exist in `/etc/bind` or it had error in it, the container would be terminated without any logs. Before starting BIND container, you should makes all configuration file already.
|
||||
|
||||
#### named.conf
|
||||
|
||||
- file: ~/data/containers/bind/etc/named.conf
|
||||
|
||||
```ini
|
||||
include "/etc/bind/key/acme-key";
|
||||
|
||||
options {
|
||||
directory "/var/cache/bind";
|
||||
|
||||
listen-on { any; };
|
||||
listen-on-v6 { ::1; };
|
||||
|
||||
// Authoritative DNS setting
|
||||
allow-recursion { none; };
|
||||
allow-transfer { none; };
|
||||
allow-update { none; };
|
||||
|
||||
dnssec-validation no;
|
||||
|
||||
check-names master warn;
|
||||
};
|
||||
|
||||
zone "ilnmors.internal." {
|
||||
type primary;
|
||||
file "/var/lib/bind/db.ilnmors.internal";
|
||||
notify yes;
|
||||
// ACME-01 challenge policy. It allows only subdomain's TXT record update.
|
||||
update-policy {
|
||||
grant acme-key subdomain ilnmors.internal. TXT;
|
||||
};
|
||||
};
|
||||
|
||||
zone "1.168.192.in-addr.arpa" {
|
||||
type primary;
|
||||
file "/var/lib/bind/db.1.168.192.in-addr.arpa";
|
||||
notify yes;
|
||||
};
|
||||
|
||||
zone "10.168.192.in-addr.arpa" {
|
||||
type primary;
|
||||
file "/var/lib/bind/db.10.168.192.in-addr.arpa";
|
||||
notify yes;
|
||||
};
|
||||
|
||||
zone "ilnmors.com." {
|
||||
//split horizon dns
|
||||
type primary;
|
||||
file "/var/lib/bind/db.ilnmors.com";
|
||||
notify yes;
|
||||
};
|
||||
|
||||
logging {
|
||||
channel default_log {
|
||||
stderr;
|
||||
severity info;
|
||||
};
|
||||
category default { default_log; };
|
||||
category config { default_log; };
|
||||
category queries { default_log; };
|
||||
};
|
||||
```
|
||||
|
||||
>If ddns function were required, generate tsig key for ddns and give update policy for each zone like below.
|
||||
>
|
||||
> - include "/etc/bind/ddns-key";
|
||||
> - update-policy { grant ddns-key subdomain \[zone_domain\] ANY A AAAA TXT; };
|
||||
|
||||
- Verify the named.conf with the command `named-checkconf`
|
||||
|
||||
#### Zone files
|
||||
|
||||
> When you add the record, you should use `.` at the end of the domain. i.e. mydomain.internal.
|
||||
|
||||
- file:
|
||||
- ~/data/containers/bind/lib/db.ilnmors.internal
|
||||
|
||||
```ini
|
||||
$TTL 86400
|
||||
|
||||
@ IN SOA bind.ilnmors.internal. mail.ilnmors.internal. (
|
||||
yyyymmdd01 ; serial
|
||||
3600 ; refresh (1 hour)
|
||||
1800 ; retry (30 minutes)
|
||||
604800 ; expire (1 week)
|
||||
86400 ; minimum (1 day)
|
||||
)
|
||||
IN NS bind.ilnmors.internal.
|
||||
bind IN A 192.168.10.11
|
||||
opnsense IN A 192.168.10.1
|
||||
vmm IN A 192.168.10.10
|
||||
net IN A 192.168.10.11
|
||||
auth IN A 192.168.10.12
|
||||
dev IN A 192.168.10.13
|
||||
app IN A 192.168.10.14
|
||||
console IN A 192.168.1.11
|
||||
printer IN A 192.168.1.30
|
||||
crowdsec IN CNAME opnsense.ilnmors.internal.
|
||||
adguard IN CNAME net.ilnmors.internal.
|
||||
step-ca IN CNAME auth.ilnmors.internal.
|
||||
caddy IN CNAME auth.ilnmors.internal.
|
||||
ldap IN CNAME auth.ilnmors.internal.
|
||||
postgresql IN CNAME dev.ilnmors.internal.
|
||||
```
|
||||
|
||||
- ~/data/containers/bind/lib/db.1.168.192.in-addr.arpa
|
||||
|
||||
```ini
|
||||
$TTL 86400
|
||||
|
||||
@ IN SOA bind.ilnmors.internal. mail.ilnmors.internal. (
|
||||
yyyymmdd01 ; serial
|
||||
3600 ; refresh (1 hour)
|
||||
1800 ; retry (30 minutes)
|
||||
604800 ; expire (1 week)
|
||||
86400 ; minimum (1 day)
|
||||
)
|
||||
IN NS bind.ilnmors.internal.
|
||||
1 IN PTR opnsense.ilnmors.internal.
|
||||
11 IN PTR console.ilnmors.internal.
|
||||
30 IN PTR printer.ilnmors.internal.
|
||||
```
|
||||
|
||||
- ~/data/containers/bind/lib/db.10.168.192.in-addr.arpa
|
||||
|
||||
```ini
|
||||
$TTL 86400
|
||||
|
||||
@ IN SOA bind.ilnmors.internal. mail.ilnmors.internal. (
|
||||
yyyymmdd01 ; serial
|
||||
3600 ; refresh (1 hour)
|
||||
1800 ; retry (30 minutes)
|
||||
604800 ; expire (1 week)
|
||||
86400 ; minimum (1 day)
|
||||
)
|
||||
IN NS bind.ilnmors.internal.
|
||||
1 IN PTR opnsense.ilnmors.internal.
|
||||
10 IN PTR vmm.ilnmors.internal.
|
||||
11 IN PTR net.ilnmors.internal.
|
||||
12 IN PTR auth.ilnmors.internal.
|
||||
13 IN PTR dev.ilnmors.internal.
|
||||
14 IN PTR app.ilnmors.internal.
|
||||
```
|
||||
|
||||
- ~/data/containers/bind/lib/db.ilnmors.com
|
||||
|
||||
```ini
|
||||
$TTL 86400
|
||||
|
||||
@ IN SOA bind.ilnmors.internal. mail.ilnmors.internal. (
|
||||
yyyymmdd01 ; serial
|
||||
3600 ; refresh (1 hour)
|
||||
1800 ; retry (30 minutes)
|
||||
604800 ; expire (1 week)
|
||||
86400 ; minimum (1 day)
|
||||
)
|
||||
IN NS bind.ilnmors.internal.
|
||||
* IN A 192.168.10.12
|
||||
```
|
||||
|
||||
```bash
|
||||
# Adguard container has Requires=bind.service. When it restarted, then Adguard also restarted.
|
||||
systemctl --user restart bind
|
||||
```
|
||||
### Quadlet
|
||||
|
||||
- File:
|
||||
- ~/data/config/containers/bind/bind.container
|
||||
|
||||
```ini
|
||||
# ~/data/config/containers/bind/bind.container
|
||||
[Quadlet]
|
||||
DefaultDependencies=false
|
||||
|
||||
[Unit]
|
||||
Description=BIND9 DNS
|
||||
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Container]
|
||||
Image=docker.io/internetsystemsconsortium/bind9:9.20
|
||||
|
||||
ContainerName=bind
|
||||
|
||||
PublishPort=2253:53/tcp
|
||||
PublishPort=2253:53/udp
|
||||
|
||||
Volume=%h/data/containers/bind/etc:/etc/bind:rw
|
||||
Volume=%h/data/containers/bind/lib:/var/lib/bind:rw
|
||||
Volume=%h/data/containers/bind/cache:/var/cache/bind:rw
|
||||
Volume=%h/data/containers/bind/log:/var/log:rw
|
||||
|
||||
Environment="TZ=Asia/Seoul"
|
||||
Secret=acme-key,target=/etc/bind/key/acme-key
|
||||
|
||||
Label=diun.enable=true
|
||||
Label=diun.watch_repo=true
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
```
|
||||
|
||||
#### Create systemd `.service` file
|
||||
|
||||
```bash
|
||||
# linger has to be activated
|
||||
mkdir -p ~/.config/containers/systemd
|
||||
|
||||
ln -s ~/data/config/containers/bind/bind.container ~/.config/containers/systemd/bind.container
|
||||
|
||||
# This command makes bind.service
|
||||
systemctl --user daemon-reload
|
||||
```
|
||||
|
||||
#### Enable and start service
|
||||
|
||||
```bash
|
||||
systemctl --user start bind.service
|
||||
```
|
||||
|
||||
|
||||
### nsupdate (RFC 2136) verification
|
||||
|
||||
```bash
|
||||
# Update query at net server
|
||||
# Before test, create temp file .bind.acme-key
|
||||
nsupdate -k /run/user/$UID/.bind.acme-key
|
||||
> server 127.0.0.1 2253
|
||||
> zone ilnmors.internal
|
||||
> update add _acme-challenge.ilnmors.internal. 60 TXT "validation-test"
|
||||
> send
|
||||
> `ctrl+c`
|
||||
|
||||
# Verify
|
||||
dig @127.0.0.1 -p 2253 _acme-challenge.ilnmors.internal. TXT
|
||||
# Print
|
||||
;; QUESTION SECTION:
|
||||
;_acme-challenge.ilnmors.internal. IN TXT
|
||||
|
||||
;; ANSWER SECTION:
|
||||
_acme-challenge.ilnmors.internal. 60 IN TXT "validation-test"
|
||||
|
||||
# Delete query at net server
|
||||
nsupdate -k /run/user/$UID/.bind.acme-key
|
||||
> server 127.0.0.1 2253
|
||||
> zone ilnmors.internal
|
||||
> update delete _acme-challenge.ilnmors.internal. 60 TXT "validation-test"
|
||||
> send
|
||||
> `ctrl+c`
|
||||
|
||||
# Verify
|
||||
dig @127.0.0.1 -p 2253 _acme-challenge.ilnmors.internal. TXT
|
||||
# Print
|
||||
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 17572
|
||||
```
|
||||
@@ -0,0 +1,148 @@
|
||||
Tags: #os, #configuration, #network, #virtualization, #authorization, #authentication
|
||||
|
||||
## Preparation
|
||||
|
||||
### Set DHCP reservation and DNS record
|
||||
|
||||
#### Set DHCP reservation on KEA DHCP in OPNsense
|
||||
|
||||
Following [here](05_07_opnsense_kea.md)
|
||||
|
||||
- Services:Kea DHCP:Kea DHCPv4:Reservations - \[+\]
|
||||
- Subnet: 192.168.10.0/24
|
||||
- IP address: 192.168.10.12
|
||||
- MAC address: 0A:49:6E:4D:02:00
|
||||
- Hostname: auth
|
||||
- Description: auth
|
||||
- `save`
|
||||
|
||||
#### Set DNS records in BIND
|
||||
|
||||
Following [here](../06_network/06_03_net_bind.md).
|
||||
|
||||
- net server
|
||||
- file:
|
||||
- ~/data/containers/bind/lib/db.ilnmors.internal
|
||||
- ~/data/containers/bind/lib/db.10.168.192.in-addr.arpa
|
||||
|
||||
```ini
|
||||
# db.ilnmors.internal
|
||||
# ...
|
||||
auth IN A 192.168.10.12
|
||||
# ...
|
||||
# db.10.168.192.in-addr.arpa
|
||||
# ...
|
||||
12 IN PTR auth.ilnmors.internal.
|
||||
# ...
|
||||
```
|
||||
|
||||
```bash
|
||||
# Adguard container has Requires=bind.service. When it restarted, then Adguard also restarted.
|
||||
systemctl --user restart bind
|
||||
```
|
||||
|
||||
### Create VM template
|
||||
|
||||
- ~/data/config/scripts/auth.sh
|
||||
|
||||
```bash
|
||||
virt-install \
|
||||
--boot uefi \
|
||||
--name auth \
|
||||
--os-variant debian13 \
|
||||
--vcpus 2 \
|
||||
--memory 4096 \
|
||||
--location /var/lib/libvirt/images/debian-13.0.0-amd64-netinst.iso \ # For serial installing, use `--location` instead of `--cdrom`
|
||||
--disk pool=vm-images,size=66,format=qcow2,discard=unmap \
|
||||
--network network=ovs-lan-net,portgroup=vlan10-access,model=virtio,mac=0A:49:6E:4D:02:00 \ # Use designated ovs port group
|
||||
--graphics none \
|
||||
--console pty,target_type=serial \
|
||||
--extra-args "console=ttyS0,115200"
|
||||
# After enter this command, then the console start automatically
|
||||
# Remove all annotation before you make the sh file.
|
||||
```
|
||||
|
||||
### Debian installing
|
||||
|
||||
- Following [here](../03_common/03_01_debian_configuration.md) to install Debian.
|
||||
- Debian installer supports serial mode regardless getty@ttyS0 service is enabled or not.
|
||||
- Following [here](../03_common/03_02_iptables.md) to set iptables.
|
||||
- Following [here](../03_common/03_04_crowdsec.md) to set CrowdSec
|
||||
|
||||
#### Serial console setting
|
||||
|
||||
After installation, use `ctrl + ]` to exit console. Before setting getty@ttyS0, you can't use serial console to access VM. Therefore, use IP address set on installation, and connect net server via ssh first, following the step to enable the getty.
|
||||
|
||||
### Modify VM template settings
|
||||
|
||||
After getty setting, shutdown auth vm with `shutdown` in VM or `sudo virsh shutdown auth` in hypervisor to turn off vm first.
|
||||
|
||||
```bash
|
||||
virsh edit auth
|
||||
```
|
||||
|
||||
```xml
|
||||
<!-- auth -->
|
||||
...
|
||||
</vcpu>
|
||||
<cputune>
|
||||
<shares>1024</shares>
|
||||
</cputune>
|
||||
<!-- cpu priority - 1024: default/2048: high/512: low -->
|
||||
|
||||
<!--
|
||||
<disk type='file' device='cdrom'>
|
||||
...
|
||||
</disk>
|
||||
# Remove booting disk
|
||||
-->
|
||||
```
|
||||
|
||||
```bash
|
||||
virsh dumpxml auth > ~/data/config/vms/dumps/auth.xml
|
||||
virsh start auth && virsh console auth
|
||||
# Start auth server with console
|
||||
```
|
||||
|
||||
### Common setting
|
||||
|
||||
- auth.service
|
||||
|
||||
```ini
|
||||
# ~/data/config/services/auth.service
|
||||
# ~/.config/systemd/user/auth.service
|
||||
[Unit]
|
||||
Description=auth Auto Booting
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
Requires=opnsense.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
|
||||
# Maintain status as active
|
||||
RemainAfterExit=yes
|
||||
|
||||
# CrowdSec should be set
|
||||
ExecStartPre=%h/data/config/scripts/wait-for-it.sh 192.168.10.1:8080 -t 0
|
||||
ExecStartPre=%h/data/config/scripts/wait-for-it.sh 192.168.10.11:53 -t 0
|
||||
|
||||
ExecStartPre=/bin/bash -c "sleep 15"
|
||||
|
||||
# Run the service
|
||||
ExecStart=/usr/bin/virsh -c qemu:///system start auth
|
||||
|
||||
# Stop the service
|
||||
ExecStop=/usr/bin/virsh -c qemu:///system shutdown auth
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
```
|
||||
|
||||
```bash
|
||||
ln -s ~/data/config/services/auth.service ~/.config/systemd/user/auth.service
|
||||
|
||||
systemctl --user daemon-reload
|
||||
systemctl --user enable auth.service
|
||||
systemctl --user start auth.service
|
||||
```
|
||||
@@ -0,0 +1,320 @@
|
||||
Tags: #os, #configuration, #network, #virtualization, #container, #security
|
||||
|
||||
## Step-CA
|
||||
|
||||
Step-CA is the modern CA server which can operate in private network environment. It can issue CA and certificates, apply the policy with provisioner. It supports ACME, JWK, etc.
|
||||
|
||||
### Secret management
|
||||
|
||||
- File:
|
||||
- ~/data/config/secrets/.secret.yaml
|
||||
|
||||
- Edit `.secret.yaml` with `edit_secret.sh`
|
||||
|
||||
|
||||
```yaml
|
||||
# ~/data/config/secrets/.secret.yaml
|
||||
# STEP_CA
|
||||
STEP_CA_PASSWORD: generated_value
|
||||
```
|
||||
|
||||
|
||||
### Preparation
|
||||
|
||||
#### iptables and firewall rules
|
||||
|
||||
- Set iptables first, following [here](../03_common/03_02_iptables.md).
|
||||
- Set firewall rules first, following [here](Latest/05_firewall/05_04_opnsense_rules.md).
|
||||
#### Create directory for container
|
||||
|
||||
```bash
|
||||
mkdir -p ~/data/containers/step-ca
|
||||
chmod 700 ~/data/containers/step-ca
|
||||
setfacl -m d:g::0 ~/data/containers/step-ca
|
||||
setfacl -m d:o::0 ~/data/containers/step-ca
|
||||
setfacl -m u:auth:rwx ~/data/containers/step-ca
|
||||
setfacl -m u:100999:rwx ~/data/containers/step-ca
|
||||
setfacl -d -m u:auth:rwx ~/data/containers/step-ca
|
||||
setfacl -d -m u:100999:rwx ~/data/containers/step-ca
|
||||
# After generating
|
||||
sudo find ~/data/containers/step-ca -type f -exec setfacl -m m::rw {} \;
|
||||
sudo find ~/data/containers/step-ca -type d -exec setfacl -m m::rwx {} \;
|
||||
```
|
||||
|
||||
> Step-CA container executes as 1000:1000(step:step) permission in container. It is mapped host's 100999. Therefore, directories have to have ACL via `setfacl`
|
||||
|
||||
#### Add new domain in BIND
|
||||
|
||||
Following [here](../06_network/06_03_net_bind.md).
|
||||
|
||||
- net server
|
||||
- file: /home/net/data/containers/bind/lib/db.ilnmors.internal
|
||||
|
||||
```ini
|
||||
# ...
|
||||
step-ca IN CNAME auth.ilnmors.internal.
|
||||
# ...
|
||||
```
|
||||
|
||||
```bash
|
||||
# Adguard container has Requires=bind.service. When it restarted, then Adguard also restarted.
|
||||
systemctl --user restart bind
|
||||
```
|
||||
|
||||
### Podman Image
|
||||
|
||||
```bash
|
||||
podman pull smallstep/step-ca:0.28.4 # Do not use latest version to management
|
||||
```
|
||||
|
||||
#### CA generation
|
||||
|
||||
```bash
|
||||
podman run --rm -it \
|
||||
-v /home/auth/data/containers/step-ca:/home/step:rw \
|
||||
smallstep/step-ca:0.28.4 step ca init \
|
||||
--deployment-type standalone \
|
||||
--name ilnmors.internal \
|
||||
--dns step-ca.ilnmors.internal \
|
||||
--address :9000 \
|
||||
--provisioner step-admin@ilnmors.internal
|
||||
# Private mode: standalone
|
||||
|
||||
# Intermediate CA setting options
|
||||
# --ra stepCAS \
|
||||
# --issuer https://step-ca.ilnmors.internal:9000 \
|
||||
# --issuer-fingerprint ~~~~ \
|
||||
# --issuer-provisioner jwk-ca@dev.ilnmors.internal
|
||||
# --confidential-file ~~~
|
||||
|
||||
> [leave empty and we\'ll generate one]: [blank]
|
||||
# Print
|
||||
---
|
||||
✔ Password: Generated_value # Copy this value and paste in .secret.yaml file as STEP_CA_PASSWORD=
|
||||
✔ Root fingerprint: fingerprint
|
||||
---
|
||||
```
|
||||
|
||||
> Password value encrypts root CA's private key
|
||||
|
||||
```bash
|
||||
# Podman secret
|
||||
extract_secret.sh ~/data/config/secrets/.secret.yaml -f "STEP_CA_PASSWORD" | podman secret create "STEP_CA_PASSWORD" -
|
||||
```
|
||||
|
||||
### Configuration files
|
||||
|
||||
- File: ~/data/containers/step-ca/config/ca.json
|
||||
|
||||
#### Provisioner
|
||||
|
||||
Provisioner is basically the object of issuing certificates as a RA. They verify CSR from client and when it is valid with its policy they will sign the certificates with CA's private key. Step-CA supports various type of provisioner. In this homelab, only ACME will be used. Because it is easy to manage when you use OPNsense ACME client. Step-CA supports one root CA and one intermediate CA in one container, only one intermediate CA will be operated in this project. However, the way to set multi intermediate CA will be explained, and jwk way in this document.
|
||||
|
||||
##### jwk-ca@ilnmors.internal
|
||||
|
||||
This provisioner is to issue intermediate CA. It wouldn't be used in this project. The option for CA in X.509 format is optional and defined in as extension option. To define these option in step-ca, the template file is needed.
|
||||
|
||||
- file: ~/data/containers/step-ca/templates/ca.tpl
|
||||
|
||||
```json
|
||||
{
|
||||
"subject": {{ toJson .Subject }},
|
||||
"keyUsage": ["certSign", "crlSign"],
|
||||
"basicConstraints": {
|
||||
"isCA": true,
|
||||
"maxPathLen": 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> keyUsage: Designate to manage certificates and CRL
|
||||
> isCA: Designate the certificate to use CA
|
||||
> maxPathLen: Designate allowed below CA's number
|
||||
|
||||
|
||||
- Define provisioner
|
||||
|
||||
```bash
|
||||
podman exec -it step-ca \
|
||||
step ca provisioner add jwk-ca@ilnmors.internal \
|
||||
--create \ # Generate key pair automatically
|
||||
--type JWK \
|
||||
--ca-config /home/step/config/ca.json \ # Sign on certificate with root CA's private key
|
||||
--x509-template /home/step/template/ca.tpl \ # Use x509 template
|
||||
--x509-max-dur 87600h \ #
|
||||
--x509-default-dur 87600h
|
||||
```
|
||||
|
||||
##### jwk@ilnmors.internal
|
||||
|
||||
This provisioner is to issue the certificates like DB communication based on its identity (Using JWK and JWT pre-shared). The certificate is issued based on enrolled key in provisioner. However, in this project all crt will be used central ACME client `Opnsens ACME client` and `Caddy`.
|
||||
|
||||
- Define provisioner
|
||||
|
||||
```bash
|
||||
podman exec -it step-ca \
|
||||
step ca provisioner add jwk-crt@ilnmors.internal \
|
||||
--create \ # Generate key pair automatically
|
||||
--type JWK \
|
||||
--x509-default-dur 2160h # To set default expire date as 90 days.
|
||||
```
|
||||
|
||||
##### acme@ilnmors.internal
|
||||
|
||||
This provisioner is to issue the certificates for https communication. The certificate is issued based on challenge; the ownership of domain.
|
||||
|
||||
- Define provisioner
|
||||
```bash
|
||||
podman exec -it step-ca \
|
||||
step ca provisioner add acme@ilnmors.internal \
|
||||
--type ACME \
|
||||
--x509-default-dur 2160h # To set default expire date as 90 days.
|
||||
```
|
||||
|
||||
#### Subject
|
||||
|
||||
Step-CA uses subject as a account. It is used to manage Step-CA remotely. To use this, it is necessary to use `--remote-management` option when the step-CA is initially set or fix `ca.json` authority.enableAdmin:true. When subject is enabled, provisioners aren't defined in ca.json but its own DB.
|
||||
|
||||
#### Policy
|
||||
|
||||
Self-hosted Step-CA server doesn't support to give x509 policy for each provisioner. It only allows public policy. Only `ilnmors.internal` and `*.ilnmors.internal` certificates are required, so designate the policy in `ca.json`
|
||||
|
||||
> Policies can be administered using the step CLI application. The commands are part of the step ca policy namespace. In a self-hosted step-ca, policies can be configured on the authority level. Source: [here](https://smallstep.com/docs/step-ca/policies/)
|
||||
|
||||
- file: ~/data/containers/step-ca/config/ca.json
|
||||
|
||||
```json
|
||||
...
|
||||
"authority": {
|
||||
"policy": {
|
||||
"x509": {
|
||||
"allow": {
|
||||
"dns": [
|
||||
"ilnmors.internal",
|
||||
"*.ilnmors.internal"
|
||||
]
|
||||
},
|
||||
"allowWildcardNames": true
|
||||
}
|
||||
},
|
||||
"provisioners": [ ... ]
|
||||
....
|
||||
}
|
||||
...
|
||||
```
|
||||
### Quadlet
|
||||
|
||||
- File:
|
||||
- ~/data/config/containers/step-ca/step-ca.container
|
||||
|
||||
```ini
|
||||
# ~/data/config/containers/step-ca/step-ca.container
|
||||
[Quadlet]
|
||||
DefaultDependencies=false
|
||||
|
||||
[Unit]
|
||||
Description=Step-CA
|
||||
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Container]
|
||||
Image=docker.io/smallstep/step-ca:0.28.4
|
||||
|
||||
ContainerName=step-ca
|
||||
|
||||
PublishPort=9000:9000/tcp
|
||||
|
||||
Volume=%h/data/containers/step-ca:/home/step:rw
|
||||
|
||||
Environment="TZ=Asia/Seoul"
|
||||
Environment="PWDPATH=/run/secrets/STEP_CA_PASSWORD"
|
||||
|
||||
Secret=STEP_CA_PASSWORD,target=/run/secrets/STEP_CA_PASSWORD
|
||||
|
||||
Label=diun.enable=true
|
||||
Label=diun.watch_repo=true
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
```
|
||||
|
||||
#### Create systemd `.service` file
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.config/containers/systemd
|
||||
|
||||
ln -s ~/data/config/containers/step-ca/step-ca.container ~/.config/containers/systemd/step-ca.container
|
||||
|
||||
systemctl --user daemon-reload
|
||||
```
|
||||
|
||||
#### Enable and start service
|
||||
|
||||
```bash
|
||||
systemctl --user start step-ca.service
|
||||
```
|
||||
|
||||
### Verify server
|
||||
|
||||
#### Server health check
|
||||
|
||||
```bash
|
||||
curl -k https://step-ca.ilnmors.internal:9000/health
|
||||
> {"status":"ok"}
|
||||
```
|
||||
|
||||
#### Server policy check
|
||||
|
||||
```bash
|
||||
podman exec -it step-ca step ca certificate test.com test.crt test_key --provisioner acme@ilnmors.internal
|
||||
> error creating new ACME order: The server will not issue certificates for the identifier
|
||||
```
|
||||
|
||||
---
|
||||
### Set trust Root CRT
|
||||
|
||||
#### Linux
|
||||
|
||||
##### Debian/ubuntu
|
||||
|
||||
- File: /usr/local/share/ca-certificates/{ca.crt, ca.pem}
|
||||
- `update-ca-certificates`
|
||||
|
||||
##### Cent/RHEL/Fedora
|
||||
|
||||
- File: /etc/pki/ca-trust/source/anchors/{ca.crt, ca.pem}
|
||||
- `update-ca-trust`
|
||||
|
||||
#### Windows
|
||||
|
||||
- `Windows + R` + `certlm.msc`
|
||||
- `All Task` - `Import`
|
||||
|
||||
#### Firefox
|
||||
|
||||
- Setting - Security - certificates - CA - add
|
||||
|
||||
---
|
||||
### intermediate CA setting
|
||||
|
||||
It won't be used in this project, however here is the way to set.
|
||||
|
||||
#### Example of dev.ilnmors.internal intermediate CA
|
||||
|
||||
- init CA
|
||||
```bash
|
||||
podman run --rm -it \
|
||||
-v /home/dev/data/containers/step-ca:/home/step:rw \
|
||||
smallstep/step-ca:0.28.4 step ca init \
|
||||
--deployment-type standalone \
|
||||
--name dev.ilnmors.internal \
|
||||
--dns step-ca.dev.ilnmors.internal \
|
||||
--address :9000 \
|
||||
--provisioner admin@dev.ilnmors.internal \
|
||||
--ra stepCAS \
|
||||
--issuer https://step-ca.ilnmors.internal:9000 \
|
||||
--issuer-fingerprint ~~~~ \ # root CA's fingerprint
|
||||
--issuer-provisioner jwk-ca@dev.ilnmors.internal
|
||||
--confidential-file ~~~ # jwk-ca's password file
|
||||
```
|
||||
@@ -0,0 +1,231 @@
|
||||
Tags: #os, #configuration, #network, #virtualization, #container, #security, #authentication, #authorization, #sso
|
||||
|
||||
## Caddy - auth
|
||||
|
||||
Caddy is an open source reverse proxy (web server) which supports automatically to apply TLS certificates via ACME protocol from CA. It supports various module including dns module. However, the most important and fundamental services such as OPNsense, AdGuard Home, Step-CA, Authelia would not use caddy for independency.
|
||||
|
||||
### Secret management
|
||||
|
||||
- File:
|
||||
- ~/data/config/secrets/.secret.yaml
|
||||
|
||||
- Edit `.secret.yaml` with `edit_secret.sh`
|
||||
|
||||
```yaml
|
||||
# ~/data/config/secrets/.secret.yaml
|
||||
# CADDY:
|
||||
CADDY_ACME_KEY: acme-key_key_value (Only secret value)
|
||||
CADDY_CROWDSEC_KEY: CADDY_LAPI_KEY
|
||||
```
|
||||
|
||||
```bash
|
||||
# Podman secret
|
||||
extract_secret.sh .secret.yaml -f CADDY_ACME_KEY | podman secret create CADDY_ACME_KEY -
|
||||
|
||||
extract_secret.sh .secret.yaml -f CADDY_CROWDSEC_KEY | podman secret create CADDY_CROWDSEC_KEY -
|
||||
```
|
||||
|
||||
### Preparation
|
||||
|
||||
#### iptables and firewall rules
|
||||
|
||||
- Set iptables first, following [here](../03_common/03_02_iptables.md).
|
||||
- Set firewall rules first, following [here](Latest/05_firewall/05_04_opnsense_rules.md).
|
||||
#### Create directory for container
|
||||
|
||||
```bash
|
||||
mkdir -p ~/data/containers/caddy-auth/{etc,data}
|
||||
chmod -R 700 ~/data/containers/caddy-auth
|
||||
```
|
||||
|
||||
> Caddy container executes as 0:0(root:root) permission in container. It is mapped host's UID. Therefore, directories don't have to have ACL via `setfacl`
|
||||
|
||||
#### Add new domain in BIND
|
||||
|
||||
Following [here](../06_network/06_03_net_bind.md).
|
||||
|
||||
- net server
|
||||
- file: ~/data/containers/bind/lib/db.ilnmors.internal
|
||||
|
||||
```ini
|
||||
# ...
|
||||
login IN CNAME auth.ilnmors.internal.
|
||||
# ...
|
||||
```
|
||||
|
||||
```bash
|
||||
# Adguard container has Requires=bind.service. When it restarted, then Adguard also restarted.
|
||||
systemctl --user restart bind
|
||||
```
|
||||
|
||||
### Podman Image
|
||||
|
||||
#### Podman containerfile
|
||||
|
||||
Caddy supports various module for it. rfc2136(nsupdate) module, crowdsec will be used in this homelab project.
|
||||
|
||||
- file:
|
||||
- ~/data/config/containers/caddy-auth/containerfile-caddy-2.10.2-auth
|
||||
- ~/data/config/containers/caddy-auth/root_ca.crt
|
||||
|
||||
```containerfile
|
||||
FROM caddy:2.10.2-builder-alpine AS builder
|
||||
|
||||
RUN xcaddy build \
|
||||
--with github.com/caddy-dns/rfc2136 \
|
||||
--with github.com/hslatman/caddy-crowdsec-bouncer/crowdsec \
|
||||
--with github.com/hslatman/caddy-crowdsec-bouncer/http
|
||||
|
||||
FROM caddy:2.10.2
|
||||
|
||||
COPY --from=builder /usr/bin/caddy /usr/bin/caddy
|
||||
|
||||
COPY ./root_ca.crt /usr/local/share/ca-certificates/root_ca.crt
|
||||
|
||||
RUN update-ca-certificates
|
||||
```
|
||||
|
||||
|
||||
#### Podman image build
|
||||
|
||||
```bash
|
||||
podman build -t caddy:2.10.2-auth -f ~/data/config/containers/caddy-auth/containerfile-caddy-2.10.2-auth . && podman image prune -f
|
||||
# Delete pure caddy and caddy-builder-alpine images after command above manually.
|
||||
```
|
||||
|
||||
### Configuration files
|
||||
|
||||
Caddyfile will be updated after Authelia setting
|
||||
|
||||
```bash
|
||||
# fix inconsistencies
|
||||
podman exec caddy-auth caddy fmt --overwrite /etc/caddy/Caddyfile
|
||||
# After Caddyfile setting is changed use this command.
|
||||
podman exec caddy-auth caddy reload --config /etc/caddy/Caddyfile
|
||||
```
|
||||
|
||||
- file:
|
||||
- ~/data/containers/caddy-auth/etc/Caddyfile
|
||||
- ~/data/containers/caddy-auth/certs/root_ca.crt
|
||||
|
||||
```ini
|
||||
# Caddyfile
|
||||
# ~/data/containers/caddy-auth/etc/Caddyfile
|
||||
|
||||
# Global option
|
||||
{
|
||||
# CrowdSec LAPI connection
|
||||
crowdsec {
|
||||
api_url https://crowdsec.ilnmors.internal:8080
|
||||
api_key "{file./run/secrets/CADDY_CROWDSEC_KEY}"
|
||||
}
|
||||
}
|
||||
|
||||
# Snippets
|
||||
# CrowdSec log for parser
|
||||
(crowdsec_log) {
|
||||
log {
|
||||
output file /data/access.log {
|
||||
mode 0640
|
||||
roll_size 100MiB
|
||||
roll_keep 1
|
||||
}
|
||||
}
|
||||
}
|
||||
# Private TLS ACME with DNS-01-challenge
|
||||
(private_tls) {
|
||||
tls {
|
||||
issuer acme {
|
||||
dir https://step-ca.ilnmors.internal:9000/acme/acme@ilnmors.internal/directory
|
||||
dns rfc2136 {
|
||||
server bind.ilnmors.internal:2253
|
||||
key_name acme-key
|
||||
key_alg hmac-sha256
|
||||
key "{file./run/secrets/CADDY_ACME_KEY}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test.ilnmors.com {
|
||||
import crowdsec_log
|
||||
route {
|
||||
crowdsec
|
||||
root * /usr/share/caddy
|
||||
file_server
|
||||
}
|
||||
}
|
||||
|
||||
caddy.ilnmors.internal {
|
||||
import private_tls
|
||||
import crowdsec_log
|
||||
route {
|
||||
crowdsec
|
||||
root * /usr/share/caddy
|
||||
file_server
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Quadlet
|
||||
|
||||
- File:
|
||||
- ~/data/config/containers/caddy-auth/caddy-auth.container
|
||||
|
||||
```ini
|
||||
# ~/data/config/containers/caddy-auth/caddy-auth.container
|
||||
[Quadlet]
|
||||
DefaultDependencies=false
|
||||
|
||||
[Unit]
|
||||
Description=Caddy - auth
|
||||
|
||||
After=step-ca.service
|
||||
Requires=step-ca.service
|
||||
|
||||
[Container]
|
||||
Image=localhost/caddy:2.10.2-auth
|
||||
|
||||
ContainerName=caddy-auth
|
||||
|
||||
# To issue certificate from step-ca
|
||||
AddHost=step-ca.ilnmors.internal:host-gateway
|
||||
|
||||
PublishPort=2080:80/tcp
|
||||
PublishPort=2443:443/tcp
|
||||
|
||||
Volume=%h/data/containers/caddy-auth/etc:/etc/caddy:rw
|
||||
Volume=%h/data/containers/caddy-auth/data:/data:rw
|
||||
|
||||
Environment="TZ=Asia/Seoul"
|
||||
|
||||
Secret=CADDY_ACME_KEY,target=/run/secrets/CADDY_ACME_KEY
|
||||
Secret=CADDY_CROWDSEC_KEY,target=/run/secrets/CADDY_CROWDSEC_KEY
|
||||
|
||||
Label=diun.enable=true
|
||||
Label=diun.watch_repo=true
|
||||
# This label need configuration on `diun.yml`
|
||||
Label=diun.regopt=caddy-auth-source
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
```
|
||||
|
||||
#### Create systemd `.service` file
|
||||
|
||||
```bash
|
||||
# linger has to be activated
|
||||
ln -s ~/data/config/containers/caddy-auth/caddy-auth.container ~/.config/containers/systemd/caddy-auth.container
|
||||
|
||||
systemctl --user daemon-reload
|
||||
```
|
||||
|
||||
#### Enable and start service
|
||||
|
||||
```bash
|
||||
systemctl --user start caddy-auth.service
|
||||
```
|
||||
|
||||
### Crowdsec bouncer and agent
|
||||
|
||||
- Following [here](../03_common/03_04_crowdsec.md).
|
||||
@@ -0,0 +1,315 @@
|
||||
Tags: #os, #configuration, #network, #virtualization, #container, #security, #authentication, #authorization, #sso
|
||||
|
||||
## Authentik
|
||||
|
||||
Authentik is one of famous and strong open source idP solutions which support SSO, 2FA, LDAP, RADIUS, etc. It will be combining with Caddy-security module to apply SSO.
|
||||
|
||||
### Secret management
|
||||
|
||||
File:
|
||||
- ~/data/config/secrets/.secret.yaml
|
||||
|
||||
- Edit `.secret.yaml` with `edit_secret.sh`
|
||||
|
||||
```yaml
|
||||
# ~/data/config/secrets/.secret.yaml
|
||||
# Authentik:
|
||||
AUTHENTIK_SECRET_KEY: openssl rand -base64 32 value
|
||||
AUTHENTIK_POSTGRESQL__PASSWORD: openssl rand -base64 32 value
|
||||
```
|
||||
|
||||
```bash
|
||||
# Podman secret
|
||||
extract_secret.sh .secret.yaml -f AUTHENTIK_SECRET_KEY | podman secret create AUTHENTIK_SECRET_KEY -
|
||||
|
||||
extract_secret.sh .secret.yaml -f AUTHENTIK_POSTGRESQL__PASSWORD | podman secret create AUTHENTIK_POSTGRESQL__PASSWORD -
|
||||
```
|
||||
|
||||
### Preparation
|
||||
|
||||
#### iptables and firewall rules
|
||||
|
||||
- Set iptables first, following [here](../03_common/03_02_iptables.md).
|
||||
- Set firewall rules first, following [here](Latest/05_firewall/05_04_opnsense_rules.md).
|
||||
#### Create directory for container
|
||||
|
||||
```bash
|
||||
mkdir -p ~/data/containers/authentik
|
||||
chmod 700 ~/data/containers/authentik
|
||||
setfacl -m d:g::0 ~/data/containers/authentik
|
||||
setfacl -m d:o::0 ~/data/containers/authentik
|
||||
setfacl -m u:auth:rwx ~/data/containers/authentik
|
||||
setfacl -m u:100999:rwx ~/data/containers/authentik
|
||||
setfacl -d -m u:auth:rwx ~/data/containers/authentik
|
||||
setfacl -d -m u:100999:rwx ~/data/containers/authentik
|
||||
mkdir -p ~/data/containers/authentik/{backups,certs,media,templates}
|
||||
# After generating
|
||||
sudo find ~/data/containers/authentik -type f -exec setfacl -m m::rw {} \;
|
||||
sudo find ~/data/containers/authentik -type d -exec setfacl -m m::rwx {} \;
|
||||
```
|
||||
|
||||
> Authentik container executes as 1000:1000(authentik:authentik) permission in container. It is mapped host's 100999. Therefore, directories have to have ACL via `setfacl`
|
||||
|
||||
#### Add new domain in BIND
|
||||
|
||||
Following [here](../06_network/06_03_net_bind.md).
|
||||
|
||||
- net server
|
||||
- file: ~/data/containers/bind/lib/db.ilnmors.internal
|
||||
|
||||
```ini
|
||||
# ...
|
||||
authentik IN CNAME auth.ilnmors.internal.
|
||||
# ...
|
||||
```
|
||||
|
||||
```bash
|
||||
# Adguard container has Requires=bind.service. When it restarted, then Adguard also restarted.
|
||||
systemctl --user restart bind
|
||||
```
|
||||
|
||||
#### Add new database and user
|
||||
|
||||
- Following [here](../08_development/08_02_dev_postgresql.md). Authentik uses postgresql.
|
||||
- dev server
|
||||
|
||||
```bash
|
||||
podman exec -it -u postgres postgresql psql -U postgres
|
||||
> # Create user and database
|
||||
> CREATE USER authentik WITH PASSWORD '$AUTHENTIK_POSTGRESQL__PASSWORD_value';
|
||||
> CREATE DATABASE authentik_db;
|
||||
> ALTER DATABASE authentik_db OWNER TO authentik;
|
||||
> \du
|
||||
> \l
|
||||
```
|
||||
|
||||
#### Add information in caddy-auth
|
||||
|
||||
- Following [here](./07_03_auth_main_caddy.md).
|
||||
- auth server
|
||||
- File: ~/data/containers/caddy-auth/etc/Caddyfile
|
||||
```ini
|
||||
authentik.ilnmors.internal {
|
||||
import internal_tls
|
||||
import crowdsec_log
|
||||
reverse_proxy authentik.ilnmors.internal:9080
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
# After Caddyfile setting is changed use this command.
|
||||
podman exec caddy-auth caddy reload --config /etc/caddy/Caddyfile
|
||||
# fix inconsistencies
|
||||
podman exec caddy-auth caddy fmt --overwrite /etc/caddy/Caddyfile
|
||||
```
|
||||
### Podman Image
|
||||
|
||||
```bash
|
||||
podman pull ghcr.io/goauthentik/server:2025.10.0 # Do not use latest version to management
|
||||
```
|
||||
|
||||
### Configuration file
|
||||
|
||||
- file:
|
||||
- ~/data/containers/authentik/certs/root_ca.crt
|
||||
|
||||
### Quadlet
|
||||
|
||||
- File:
|
||||
- ~/data/config/containers/authentik/authentik-pod.pod
|
||||
- ~/data/config/containers/authentik/authentik.container
|
||||
- ~/data/config/containers/authentik/authentik-worker.container
|
||||
|
||||
```ini
|
||||
# ~/data/config/containers/authentik.pod
|
||||
[Quadlet]
|
||||
DefaultDependencies=false
|
||||
|
||||
[Pod]
|
||||
PodName=authentik
|
||||
|
||||
# web port
|
||||
PublishPort=9080:9000/tcp
|
||||
# LDAP port
|
||||
#PublishPort=[set_port]:3389
|
||||
# Prometheus Port
|
||||
#PublishPort=[set_port]:9300
|
||||
```
|
||||
|
||||
```ini
|
||||
# ~/data/config/containers/authentik/authentik.container
|
||||
[Quadlet]
|
||||
DefaultDependencies=false
|
||||
|
||||
[Unit]
|
||||
Description=Authentik - Server
|
||||
|
||||
After=caddy.service
|
||||
Wants=caddy.service
|
||||
|
||||
[Service]
|
||||
ExecStartPre=%h/data/config/scripts/wait-for-it.sh -h postgresql.ilnmors.internal -p 5432 -t 0
|
||||
ExecStartPre=sleep 5
|
||||
|
||||
[Container]
|
||||
Pod=authentik.pod
|
||||
|
||||
Image=ghcr.io/goauthentik/server:2025.10.0
|
||||
|
||||
ContainerName=authentik-server
|
||||
|
||||
# Change default http port from 9000 to 9080 - in pod
|
||||
|
||||
|
||||
Volume=%h/data/containers/authentik/media:/media:rw
|
||||
Volume=%h/data/containers/authentik/certs:/certs:ro
|
||||
Volume=%h/data/containers/authentik/templates:/templates:rw
|
||||
Volume=%h/data/containers/authentik/backups:/backups:rw
|
||||
|
||||
# Default
|
||||
Environment="TZ=Asia/Seoul"
|
||||
# Listen
|
||||
#AUTHENTIK_LISTEN__HTTP=0.0.0.0:9000
|
||||
# LDAP > 0.0.0.0:3389
|
||||
# METRICS > 0.0.0.0:9300
|
||||
# AUTHENTIK_LISTEN__TRUSTED_PROXY_CIDRS > `127.0.0.0/8`, `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, `fe80::/10`, `::1/128`
|
||||
|
||||
# DB connection - This can be changed as hot-reloading, however adding or removing needs restart.
|
||||
Environment="AUTHENTIK_POSTGRESQL__HOST=postgresql.ilnmors.internal"
|
||||
Environment="AUTHENTIK_POSTGRESQL__PORT=5432"
|
||||
# Password will be injected as secret
|
||||
Environment="AUTHENTIK_POSTGRESQL__USER=authentik"
|
||||
Environment="AUTHENTIK_POSTGRESQL__NAME=authentik_db"
|
||||
|
||||
# SSL DB configuration
|
||||
Environment="AUTHENTIK_POSTGRESQL__SSLMODE=verify-full"
|
||||
Environment="AUTHENTIK_POSTGRESQL__SSLROOTCERT=/certs/root_ca.crt"
|
||||
# This homelab doesn't use mTLS, therefore do not set `AUTHENTIK_POSTGRESQL__SSLCERT` and `AUTHENTIK_POSTGRESQL__SSLKEY`
|
||||
|
||||
# Media configuration - 'file' or 's3'
|
||||
Environment="AUTHENTIK_STORAGE__MEDIA_BACKEND=file"
|
||||
|
||||
# Email configuration - after generate local Email services
|
||||
# AUTHENTIK_EMAIL__HOST=ilnmors.internal
|
||||
# AUTHENTIK_EMAIL__PORT=25
|
||||
# AUTHENTIK_EMAIL__USERNAME=authentik
|
||||
# AUTHENTIK_EMAIL__USE_TLS=true
|
||||
# AUTHENTIK_EMAIL__FROM=authentik@ilnmors.internal
|
||||
|
||||
|
||||
Secret=AUTHENTIK_SECRET_KEY,type=env
|
||||
Secret=AUTHENTIK_POSTGRESQL__PASSWORD,type=env
|
||||
|
||||
# Start server
|
||||
Exec=server
|
||||
|
||||
Label=diun.enable=true
|
||||
Label=diun.watch_repo=true
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
```
|
||||
|
||||
```ini
|
||||
# ~/data/config/containers/authentik/authentik-worker.container
|
||||
[Quadlet]
|
||||
DefaultDependencies=false
|
||||
|
||||
[Unit]
|
||||
Description=Authentik - worker
|
||||
|
||||
After=authentik-server.service
|
||||
Requires=authentik-server.service
|
||||
|
||||
[Container]
|
||||
Pod=authentik.pod
|
||||
|
||||
Image=ghcr.io/goauthentik/server:2025.10.0
|
||||
|
||||
ContainerName=authentik-worker
|
||||
|
||||
# Change default http port from 9000 to 9080 - in pod
|
||||
|
||||
|
||||
Volume=%h/data/containers/authentik/media:/media:rw
|
||||
Volume=%h/data/containers/authentik/certs:/certs:ro
|
||||
Volume=%h/data/containers/authentik/templates:/templates:rw
|
||||
Volume=%h/data/containers/authentik/backups:/backups:rw
|
||||
|
||||
# Default
|
||||
Environment="TZ=Asia/Seoul"
|
||||
# Listen
|
||||
#AUTHENTIK_LISTEN__HTTP=0.0.0.0:9000
|
||||
# LDAP > 0.0.0.0:3389
|
||||
# METRICS > 0.0.0.0:9300
|
||||
# AUTHENTIK_LISTEN__TRUSTED_PROXY_CIDRS > `127.0.0.0/8`, `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, `fe80::/10`, `::1/128`
|
||||
|
||||
# DB connection - This can be changed as hot-reloading, however adding or removing needs restart.
|
||||
Environment="AUTHENTIK_POSTGRESQL__HOST=postgresql.ilnmors.internal"
|
||||
Environment="AUTHENTIK_POSTGRESQL__PORT=5432"
|
||||
# Password will be injected as secret
|
||||
Environment="AUTHENTIK_POSTGRESQL__USER=authentik"
|
||||
Environment="AUTHENTIK_POSTGRESQL__NAME=authentik_db"
|
||||
|
||||
# SSL DB configuration
|
||||
Environment="AUTHENTIK_POSTGRESQL__SSLMODE=verify-full"
|
||||
Environment="AUTHENTIK_POSTGRESQL__SSLROOTCERT=/certs/root_ca.crt"
|
||||
# This homelab doesn't use mTLS, therefore do not set `AUTHENTIK_POSTGRESQL__SSLCERT` and `AUTHENTIK_POSTGRESQL__SSLKEY`
|
||||
|
||||
# Media configuration - 'file' or 's3'
|
||||
Environment="AUTHENTIK_STORAGE__MEDIA_BACKEND=file"
|
||||
|
||||
# Email configuration - after generate local Email services
|
||||
# AUTHENTIK_EMAIL__HOST=ilnmors.internal
|
||||
# AUTHENTIK_EMAIL__PORT=25
|
||||
# AUTHENTIK_EMAIL__USERNAME=authentik
|
||||
# AUTHENTIK_EMAIL__USE_TLS=true
|
||||
# AUTHENTIK_EMAIL__FROM=authentik@ilnmors.internal
|
||||
|
||||
|
||||
Secret=AUTHENTIK_SECRET_KEY,type=env
|
||||
Secret=AUTHENTIK_POSTGRESQL__PASSWORD,type=env
|
||||
|
||||
# Start worker
|
||||
Exec=worker
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
```
|
||||
|
||||
> All configurations, except DB connection information will be saved in postgresql. Do not set Environment varibles. It has the first priority, it will override all configuration from web UI.
|
||||
#### Create systemd `.service` file
|
||||
|
||||
```bash
|
||||
# linger has to be activated
|
||||
ln -s ~/data/config/containers/authentik/authentik-pod.pod ~/.config/containers/systemd/authentik-pod.pod
|
||||
|
||||
ln -s ~/data/config/containers/authentik/authentik-server.container ~/.config/containers/systemd/authentik.container
|
||||
|
||||
ln -s ~/data/config/containers/authentik/authentik-worker.container ~/.config/containers/systemd/authentik-worker.container
|
||||
|
||||
systemctl --user daemon-reload
|
||||
```
|
||||
|
||||
#### Enable and start service
|
||||
|
||||
```bash
|
||||
systemctl --user start authentik.service
|
||||
```
|
||||
|
||||
### Web UI configuration
|
||||
|
||||
#### Access web UI and initial setting
|
||||
|
||||
- URL: https://authentik.ilnmors.internal/if/flow/initial-setup/
|
||||
|
||||
> If you can't initialize the authentik at the first time, `DROP DATABASE authentik_db;` and recreate database after authentik container stop.
|
||||
|
||||
#### Initial setting wizard
|
||||
|
||||
- Email: Admin-email
|
||||
- thiswork21@gmail.com
|
||||
- Password: password
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,386 @@
|
||||
Tags: #os, #configuration, #network, #virtualization, #container, #security, #authentication, #authorization, #sso
|
||||
|
||||
## LLDAP (Light LDAP)
|
||||
|
||||
LLDAP provides LDAP protocol as very light and modern way. It supports not only main function of LDAP protocol such as Group, or User management but also web UI and RESTFul API. Additionally, it is very simple to set and manage and takes a small amount of resources. Following [here](../02_theory/02_05_sso.md) about the structure of LDAP.
|
||||
|
||||
### Secret management
|
||||
|
||||
File:
|
||||
- ~/data/config/secrets/.secret.yaml
|
||||
|
||||
- Edit `.secret.yaml` with `edit_secret.sh`
|
||||
|
||||
```yaml
|
||||
# ~/data/config/secrets/.secret.yaml
|
||||
# LLDAP:
|
||||
LLDAP_DATABASE_URL: postgres://ldap:$PASSWORD@postgresql.ilnmors.internal/ldap_db?sslmode=verify-full&sslrootcert=/etc/ssl/ldap/root_ca.crt # $PASSWORD=openssl rand -base64 32 value and it should be encoded as URL
|
||||
|
||||
LLDAP_LDAP_USER_PASSWORD: $PASSWORD
|
||||
|
||||
LLDAP_KEY_SEED: "$(LC_ALL=C tr -dc 'A-Za-z0-9!#%&()*+,-./:;<=>?@[\]^_{|}~' </dev/urandom | head -c 32)"
|
||||
|
||||
LLDAP_JWT_SECRET: "$(LC_ALL=C tr -dc 'A-Za-z0-9!#%&()*+,-./:;<=>?@[\]^_{|}~' </dev/urandom | head -c 32)"
|
||||
```
|
||||
|
||||
```bash
|
||||
# Podman secret
|
||||
|
||||
extract_secret.sh .secret.yaml -f LLDAP_DATABASE_URL | podman secret create LLDAP_DATABASE_URL -
|
||||
|
||||
extract_secret.sh .secret.yaml -f LLDAP_LDAP_USER_PASSWORD | podman secret create LLDAP_LDAP_USER_PASSWORD -
|
||||
|
||||
extract_secret.sh .secret.yaml -f LLDAP_JWT_SECRET | podman secret create LLDAP_JWT_SECRET -
|
||||
|
||||
extract_secret.sh .secret.yaml -f LLDAP_KEY_SEED | podman secret create LLDAP_KEY_SEED -
|
||||
```
|
||||
|
||||
### Preparation
|
||||
|
||||
#### iptables and firewall rules
|
||||
|
||||
- Set iptables first, following [here](../03_common/03_02_iptables.md).
|
||||
- 636:6360: localhost nat
|
||||
- LDAPS will be only used localhost (Authelia), so PREROUTING doesn't require in nat table. nly OUTPUT requires in it.
|
||||
- Set firewall rules first, following [here](Latest/05_firewall/05_04_opnsense_rules.md).
|
||||
|
||||
#### Create directory for container
|
||||
|
||||
```bash
|
||||
mkdir -p ~/data/containers/ldap
|
||||
chmod 700 ~/data/containers/ldap
|
||||
setfacl -m d:g::0 ~/data/containers/ldap
|
||||
setfacl -m d:o::0 ~/data/containers/ldap
|
||||
setfacl -m u:auth:rwx ~/data/containers/ldap
|
||||
setfacl -m u:100999:rwx ~/data/containers/ldap
|
||||
setfacl -d -m u:auth:rwx ~/data/containers/ldap
|
||||
setfacl -d -m u:100999:rwx ~/data/containers/ldap
|
||||
mkdir ~/data/containers/ldap/{certs,data}
|
||||
|
||||
# After generating
|
||||
sudo find ~/data/containers/ldap -type f -exec setfacl -m m::rw {} \;
|
||||
sudo find ~/data/containers/ldap -type d -exec setfacl -m m::rwx {} \;
|
||||
```
|
||||
|
||||
> lldap container executes as 1000:1000(lldap:lldap) permission in container. It is mapped host's 100999. Therefore, directories have to have ACL via `setfacl`
|
||||
|
||||
#### Add new domain in BIND
|
||||
|
||||
Following [here](../06_network/06_03_net_bind.md).
|
||||
|
||||
- net server
|
||||
- file: ~/data/containers/bind/lib/db.ilnmors.internal
|
||||
|
||||
```ini
|
||||
# ...
|
||||
ldap IN CNAME auth.ilnmors.internal.
|
||||
# ...
|
||||
```
|
||||
|
||||
```bash
|
||||
# Adguard container has Requires=bind.service. When it restarted, then Adguard also restarted.
|
||||
systemctl --user restart bind
|
||||
```
|
||||
|
||||
#### Add new database and user
|
||||
|
||||
- Following [here](../08_development/08_02_dev_postgresql.md). LLDAP can use postgresql (Basic is SQLite).
|
||||
- dev server
|
||||
|
||||
```bash
|
||||
podman exec -it -u postgres postgresql psql -U postgres
|
||||
> # Create user and database
|
||||
> CREATE USER ldap WITH PASSWORD '$POSTGRES_LDAP_PASSWORD';
|
||||
> CREATE DATABASE ldap_db;
|
||||
> ALTER DATABASE ldap_db OWNER TO ldap;
|
||||
> \du
|
||||
> \l
|
||||
```
|
||||
|
||||
#### Add information in caddy-auth
|
||||
|
||||
- Following [here](./07_03_auth_main_caddy.md).
|
||||
- auth server
|
||||
- File: ~/data/containers/caddy-auth/etc/Caddyfile
|
||||
|
||||
```ini
|
||||
ldap.ilnmors.internal {
|
||||
import internal_tls
|
||||
import crowdsec_log
|
||||
reverse_proxy host.containers.internal:17170
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
# fix inconsistencies
|
||||
podman exec caddy-auth caddy fmt --overwrite /etc/caddy/Caddyfile
|
||||
# After Caddyfile setting is changed use this command.
|
||||
podman exec caddy-auth caddy reload --config /etc/caddy/Caddyfile
|
||||
```
|
||||
|
||||
#### Certificates configuration
|
||||
|
||||
- File: ~/data/containers/certs/root_ca.crt
|
||||
|
||||
- ACME setting (OPNsense)
|
||||
- Services:ACME Client:Certificates - Certificates - \[+\]
|
||||
- Common Name: ldap.ilnmors.internal
|
||||
- Description: ldap
|
||||
- ACME Account: ldap.ilnmors.internal
|
||||
> Even though provisioner's name includes `@`, it has to use as `.`.
|
||||
>
|
||||
> i.e. `acme@ilnmors.internal` > `acme.ilnmors.internal`
|
||||
- Challenge Type: ilnmors.internal-dns-01-challenge
|
||||
- \[\*\] Auto Renewal
|
||||
- Automations: ldap-auto-acme, ldap-auto-restart
|
||||
|
||||
- Automations (OPNsense)
|
||||
- Services:ACME Client:Automations - Automation - \[+\]
|
||||
- Name: ldap-auto-acme / ldap-auto-reload
|
||||
- Description: ldap acme crt issue / reload ldap after crt is issued
|
||||
- Run Command: Upload certificate via SFTP / Remote command via SSH
|
||||
- SFTP Host: ldap.ilnmors.internal
|
||||
- Username: auth
|
||||
- Identity Type: ed25519
|
||||
- Remote Path(SFTP): /home/auth/data/containers/ldap/certs
|
||||
- Command(SSH): setfacl -m m::r /home/auth/data/containers/ldap/certs/ldap.ilnmors.internal/* && systemctl --user restart ldap.service
|
||||
- `Show Identity`
|
||||
> Copy Required parameters `ssh-ed25519 ~~~ root@opnsense.ilnmors.internal`
|
||||
>
|
||||
> Add parameters in net server's ~/.ssh/authorized_keys
|
||||
- `Test Connect` and `Save`
|
||||
|
||||
- SSH command will be applied after postgresql start.
|
||||
|
||||
### Podman Image
|
||||
|
||||
```bash
|
||||
podman pull lldap/lldap:v0.6.2 # Do not use latest version to management
|
||||
```
|
||||
|
||||
### Configuration file
|
||||
|
||||
- file:
|
||||
- ~/data/containers/ldap/certs/root_ca.crt
|
||||
- ~/data/containers/ldap/data/lldap_config.toml
|
||||
|
||||
### Initiating
|
||||
|
||||
```bash
|
||||
podman run --rm \
|
||||
--secret LLDAP_DATABASE_URL,type=env \
|
||||
--secret LLDAP_KEY_SEED,type=env \
|
||||
--secret LLDAP_JWT_SECRET,type=env \
|
||||
--secret LLDAP_LDAP_USER_PASSWORD,type=env \
|
||||
-e TZ="Asia/Seoul" \
|
||||
-e LLDAP_LDAP_BASE_DN="dc=ilnmors,dc=internal" \
|
||||
-v "$HOME"/data/containers/ldap/data:/data:rw \
|
||||
-v "$HOME"/data/containers/ldap/certs:/etc/ssl/ldap:ro \
|
||||
lldap/lldap:v0.6.2
|
||||
# `Ctrl + C` exit
|
||||
|
||||
podman secret rm LLDAP_LDAP_USER_PASSWORD
|
||||
```
|
||||
|
||||
### Quadlet
|
||||
|
||||
- File:
|
||||
- ~/data/config/containers/ldap/ldap.container
|
||||
|
||||
```ini
|
||||
# ~/data/config/containers/ldap/ldap.container
|
||||
[Quadlet]
|
||||
DefaultDependencies=false
|
||||
|
||||
[Unit]
|
||||
Description=LDAP
|
||||
|
||||
After=caddy.service
|
||||
Wants=caddy.service
|
||||
|
||||
[Service]
|
||||
ExecStartPre=%h/data/config/scripts/wait-for-it.sh -h postgresql.ilnmors.internal -p 5432 -t 0
|
||||
ExecStartPre=sleep 5
|
||||
|
||||
[Container]
|
||||
|
||||
Image=lldap/lldap:v0.6.2
|
||||
|
||||
ContainerName=ldap
|
||||
|
||||
# For LDAPS - 636 > 6360 iptables
|
||||
PublishPort=6360:6360/tcp
|
||||
# Web UI
|
||||
PublishPort=17170:17170/tcp
|
||||
|
||||
|
||||
Volume=%h/data/containers/ldap/data:/data:rw
|
||||
Volume=%h/data/containers/ldap/certs:/etc/ssl/ldap:ro
|
||||
|
||||
# Default
|
||||
Environment="TZ=Asia/Seoul"
|
||||
|
||||
# Domain
|
||||
Environment="LLDAP_LDAP_BASE_DN=dc=ilnmors,dc=internal"
|
||||
|
||||
# LDAPS
|
||||
Environment="LLDAP_LDAPS_OPTIONS__ENABLED=true"
|
||||
Environment="LLDAP_LDAPS_OPTIONS__CERT_FILE=/etc/ssl/ldap/ldap.ilnmors.internal/fullchain.pem"
|
||||
Environment="LLDAP_LDAPS_OPTIONS__KEY_FILE=/etc/ssl/ldap/ldap.ilnmors.internal/key.pem"
|
||||
|
||||
# SMTP options > you can set all of these at the /data/config.toml instead of Environment
|
||||
# Only `LLDAP_SMTP_OPTIONS__PASSWORD` will be injected by secret
|
||||
# LLDAP_SMTP_OPTIONS__ENABLE_PASSWORD_RESET=true
|
||||
# LLDAP_SMTP_OPTIONS__SERVER=smtp.example.com
|
||||
# LLDAP_SMTP_OPTIONS__PORT=465
|
||||
# LLDAP_SMTP_OPTIONS__SMTP_ENCRYPTION=TLS
|
||||
# LLDAP_SMTP_OPTIONS__USER=no-reply@example.com
|
||||
# LLDAP_SMTP_OPTIONS__PASSWORD=PasswordGoesHere
|
||||
# LLDAP_SMTP_OPTIONS__FROM=no-reply <no-reply@example.com>
|
||||
# LLDAP_SMTP_OPTIONS__TO=admin <admin@example.com>
|
||||
|
||||
# Database
|
||||
Secret=LLDAP_DATABASE_URL,type=env
|
||||
|
||||
# Secrets
|
||||
Secret=LLDAP_KEY_SEED,type=env
|
||||
Secret=LLDAP_JWT_SECRET,type=env
|
||||
|
||||
Label=diun.enable=true
|
||||
Label=diun.watch_repo=true
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
```
|
||||
|
||||
#### Create systemd `.service` file
|
||||
|
||||
```bash
|
||||
# linger has to be activated
|
||||
ln -s ~/data/config/containers/ldap/ldap.container ~/.config/containers/systemd/ldap.container
|
||||
|
||||
systemctl --user daemon-reload
|
||||
```
|
||||
|
||||
#### Enable and start service
|
||||
|
||||
```bash
|
||||
systemctl --user start ldap.service
|
||||
```
|
||||
|
||||
### DB backup
|
||||
|
||||
- Following [here](../08_development/08_02_dev_postgresql.md).
|
||||
- dev server
|
||||
|
||||
```bash
|
||||
systemctl --user enable --now postgresql-data-backup@ldap.timer
|
||||
```
|
||||
|
||||
> The data saved in postgresql are all LDAP data including all users, groups and hashed passwords. However, LLDAP_KEY_SEED and LLDAP_JWT_SECRET are not saved in postgresql. It is for container itself and it is used as Environment value in the container.
|
||||
|
||||
### Configuration
|
||||
|
||||
#### Access web UI and Login
|
||||
|
||||
- URL: https://ldap.ilnmors.internal
|
||||
- ID: admin
|
||||
- PW: $LLDAP_LDAP_USER_PASSWORD
|
||||
|
||||
#### Create the groups
|
||||
|
||||
- Groups - \[\+\] Create a group
|
||||
- Group: admins
|
||||
- Group: users
|
||||
#### Create the authelia user
|
||||
|
||||
- Users: \[\+\] Create a user
|
||||
- Username (cn; uid): authelia
|
||||
- Display name: Authelia
|
||||
- First Name: Authelia
|
||||
- Last Name (sn): Service
|
||||
- Email (mail): authelia@ilnmors.internal
|
||||
- Password: "$(openssl rand -base64 32)"
|
||||
- lldap_strict_readonly \[Add to group\]
|
||||
- This group allow search authority.
|
||||
> Save the password in .secret.yaml
|
||||
|
||||
#### Create the normal user
|
||||
|
||||
- Users: \[\+\] Create a user
|
||||
- Username (cn; uid): user
|
||||
- First Name: John
|
||||
- Last Name (sn): Doe
|
||||
- Email (mail): john_doe@ilnmors.internal
|
||||
- Password: "$PASSWORD"
|
||||
- (admins|users) \[Add to group\]
|
||||
|
||||
> Custom schema in `User schema`, `Gropu schema` doesn't need to be added. This is for advanced function to add additional value such as `identity number` or `phone number`. Hardcoded schema, which means basic schema the lldap provides is enough to use Authelia.
|
||||
|
||||
> After all these steps, now you can integrate the Authelia for SSO.
|
||||
|
||||
### Usage of LDAP
|
||||
|
||||
#### Service Bind
|
||||
|
||||
LDAP call `login` as Bind. When the authelia Bind to the LDAP server, it can get the authority to search in `lldap_strict_readonly` group.
|
||||
|
||||
#### Search
|
||||
|
||||
authelia account has the authority to search, it can search to send the query.
|
||||
|
||||
##### Flow of search
|
||||
|
||||
- Client (authelia) sends the query
|
||||
- `uid=user in dc=ilnmors,dc=ilnmors`
|
||||
- LDAP server searches the DN of entry
|
||||
- `uid=user,ou=people,dc=ilnmors,dc=ilnmors`
|
||||
- LDAP sends the DN to Client (authelia)
|
||||
|
||||
### Authelia's work flow
|
||||
|
||||
#### First login
|
||||
|
||||
##### User login query
|
||||
|
||||
User try to login on login page of Authelia.
|
||||
|
||||
- id: user
|
||||
- password: 1234
|
||||
|
||||
##### Service Bind (Bind and search)
|
||||
|
||||
authelia binds to LLDAP server based on the information in configuration.yml.
|
||||
|
||||
- dn: authelia
|
||||
- password: authelia's password
|
||||
|
||||
##### Search
|
||||
|
||||
authelia sends the query to LLDAP after bind.
|
||||
- `uid=user in dc=ilnmors,dc=internal`
|
||||
|
||||
##### Request
|
||||
|
||||
LLDAP server searches the entry and send the DN information query to authelia.
|
||||
|
||||
- `uid=user,ou=people,dc=ilnmors,dc=internal`
|
||||
|
||||
#### Verify the user login (Second login)
|
||||
|
||||
##### User Bind (Bind only)
|
||||
|
||||
authelia tries to bind LLDAP server based on the information that user input.
|
||||
|
||||
- dn: requested uid
|
||||
- password: 1234
|
||||
|
||||
##### Verification from LLDAP
|
||||
|
||||
LLDAP verify the password from authelia with its hash value saved in LLDAP's database.
|
||||
|
||||
##### Request
|
||||
|
||||
LLDAP server sends the result as `Success` or `Fail`.
|
||||
|
||||
> Search authority is basic authority of user who binds to LDAP server. It is just the way to check success or fail bind is the charge of Authelia.
|
||||
@@ -0,0 +1,651 @@
|
||||
Tags: #os, #configuration, #network, #virtualization, #container, #security, #authentication, #authorization, #sso
|
||||
|
||||
## Authelia
|
||||
|
||||
Authelia is one of the open source authentication and authorization server which can management IAM(Identity and Access Management). It supports SSO and even OpenID Connect 1.0 Provider (idP) on OAuth 2.0. Authelia uses its backend database as the file or LDAP server. LLDAP server is this backend database for authelia in this homelab.
|
||||
|
||||
### Secret management
|
||||
|
||||
File:
|
||||
- ~/data/config/secrets/.secret.yaml
|
||||
|
||||
- Edit `.secret.yaml` with `edit_secret.sh`
|
||||
|
||||
```yaml
|
||||
# ~/data/config/secrets/.secret.yaml
|
||||
# Authelia:
|
||||
AUTHELIA_JWT_SECRET: "$(LC_ALL=C tr -dc 'A-Za-z0-9!#%&()*+,-./:;<=>?@[\]^_{|}~' </dev/urandom | head -c 32)"
|
||||
|
||||
AUTHELIA_SESSION_SECRET: "$(LC_ALL=C tr -dc 'A-Za-z0-9!#%&()*+,-./:;<=>?@[\]^_{|}~' </dev/urandom | head -c 32)"
|
||||
|
||||
AUTHELIA_STORAGE_SECRET: "$(LC_ALL=C tr -dc 'A-Za-z0-9!#%&()*+,-./:;<=>?@[\]^_{|}~' </dev/urandom | head -c 32)"
|
||||
|
||||
AUTHELIA_HMAC_SECRET: "$(openssl rand -base64 32)"
|
||||
|
||||
AUTHELIA_JWKS_RS256: |
|
||||
"$(podman run --rm \
|
||||
-v ~/data/containers/authelia/certs:/output:rw \
|
||||
authelia/authelia:4.39.13 \
|
||||
authelia crypto pair ecdsa \
|
||||
generate --directory /output/ecdsa)"
|
||||
|
||||
AUTHELIA_JWKS_ES256: |
|
||||
"$(podman run --rm \
|
||||
-v ~/data/containers/authelia/certs:/output:rw \
|
||||
authelia/authelia:4.39.13 \
|
||||
authelia crypto pair rsa \
|
||||
generate --directory /output/rsa)"
|
||||
|
||||
# content of private.pem and rm private.pem/ leave public.pem
|
||||
|
||||
# This is set on LLDAP server
|
||||
AUTHELIA_LDAP_PASSWORD: "$(openssl rand -base64 32)"
|
||||
|
||||
POSTGRES_AUTHELIA_PASSWORD: "$(openssl rand -base64 32)"
|
||||
```
|
||||
|
||||
```bash
|
||||
# Podman secret
|
||||
|
||||
extract_secret.sh .secret.yaml -f AUTHELIA_JWT_SECRET | podman secret create AUTHELIA_JWT_SECRET -
|
||||
|
||||
extract_secret.sh .secret.yaml -f AUTHELIA_SESSION_SECRET | podman secret create AUTHELIA_SESSION_SECRET -
|
||||
|
||||
extract_secret.sh .secret.yaml -f AUTHELIA_STORAGE_SECRET | podman secret create AUTHELIA_STORAGE_SECRET -
|
||||
|
||||
extract_secret.sh .secret.yaml -f AUTHELIA_HMAC_SECRET | podman secret create AUTHELIA_HMAC_SECRET -
|
||||
|
||||
extract_secret.sh .secret.yaml -f AUTHELIA_LDAP_PASSWORD | podman secret create AUTHELIA_LDAP_PASSWORD -
|
||||
|
||||
extract_secret.sh .secret.yaml -f POSTGRES_AUTHELIA_PASSWORD | podman secret create POSTGRES_AUTHELIA_PASSWORD -
|
||||
|
||||
extract_secret.sh .secret.yaml -f AUTHELIA_JWKS_RS256 | podman secret create AUTHELIA_JWKS_RS256 -
|
||||
|
||||
extract_secret.sh .secret.yaml -f AUTHELIA_JWKS_ES256 | podman secret create AUTHELIA_JWKS_ES256 -
|
||||
```
|
||||
|
||||
### Preparation
|
||||
|
||||
#### iptables and firewall rules
|
||||
|
||||
- Set iptables first, following [here](../03_common/03_02_iptables.md).
|
||||
- 636:6360: localhost nat
|
||||
- LDAPS will be only used localhost (Authelia), so PREROUTING doesn't require in nat table. nly OUTPUT requires in it.
|
||||
- Set firewall rules first, following [here](Latest/05_firewall/05_04_opnsense_rules.md).
|
||||
|
||||
#### Create directory for container
|
||||
|
||||
```bash
|
||||
mkdir -p ~/data/containers/authelia/{certs,config}
|
||||
chmod -R 700 ~/data/containers/authelia
|
||||
```
|
||||
|
||||
> authelia container executes as 0:0(root:root) permission in container. It is mapped host's UID. Therefore, directories don't have to have ACL via `setfacl`
|
||||
|
||||
#### Add new domain in BIND
|
||||
|
||||
Following [here](../06_network/06_03_net_bind.md).
|
||||
|
||||
- net server
|
||||
- file: ~/data/containers/bind/lib/db.ilnmors.internal
|
||||
|
||||
```ini
|
||||
# ...
|
||||
authelia IN CNAME auth.ilnmors.internal.
|
||||
# ...
|
||||
```
|
||||
|
||||
```bash
|
||||
# Adguard container has Requires=bind.service. When it restarted, then Adguard also restarted.
|
||||
systemctl --user restart bind
|
||||
```
|
||||
|
||||
#### Add new database and user
|
||||
|
||||
- Following [here](../08_development/08_02_dev_postgresql.md). LLDAP can use postgresql (Basic is SQLite).
|
||||
- dev server
|
||||
|
||||
```bash
|
||||
podman exec -it -u postgres postgresql psql -U postgres
|
||||
> # Create user and database
|
||||
> CREATE USER authelia WITH PASSWORD '$POSTGRES_AUTHELIA_PASSWORD';
|
||||
> CREATE DATABASE authelia_db;
|
||||
> ALTER DATABASE authelia_db OWNER TO authelia;
|
||||
> \du
|
||||
> \l
|
||||
```
|
||||
|
||||
#### Add information in caddy-auth
|
||||
|
||||
- Following [here](./07_03_auth_main_caddy.md).
|
||||
- auth server
|
||||
- File: ~/data/containers/caddy-auth/etc/Caddyfile
|
||||
```ini
|
||||
authelia.ilnmors.com {
|
||||
import crowdsec_log
|
||||
route {
|
||||
crowdsec
|
||||
reverse_proxy host.containers.internal:9091
|
||||
}
|
||||
}
|
||||
|
||||
authelia.ilnmors.internal {
|
||||
import internal_tls
|
||||
import crowdsec_log
|
||||
route {
|
||||
crowdsec
|
||||
reverse_proxy host.containers.internal:9091
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
# fix inconsistencies
|
||||
podman exec caddy-auth caddy fmt --overwrite /etc/caddy/Caddyfile
|
||||
# After Caddyfile setting is changed use this command.
|
||||
podman exec caddy-auth caddy reload --config /etc/caddy/Caddyfile
|
||||
```
|
||||
|
||||
### Podman Image
|
||||
|
||||
```bash
|
||||
podman pull authelia/authelia:4.39.13 # Do not use latest version to management
|
||||
```
|
||||
|
||||
### Configuration file
|
||||
|
||||
- file:
|
||||
- ~/data/containers/authelia/certs/root_ca.crt
|
||||
- ~/data/containers/authelia/config/configuration.yml
|
||||
|
||||
#### configuration.yml
|
||||
|
||||
```yaml
|
||||
# authelia configuration.yml
|
||||
---
|
||||
# certificates setting
|
||||
certificates_directory: '/etc/ssl/authelia/'
|
||||
|
||||
# them setting - light, dark, grey, auto.
|
||||
theme: 'auto'
|
||||
|
||||
# Server configuration
|
||||
server:
|
||||
# TLS will be applied on caddy
|
||||
address: 'tcp://:9091/'
|
||||
|
||||
# Log configuration
|
||||
log:
|
||||
level: 'debug'
|
||||
#file_path: 'path/of/log/file' - without this option, using stdout
|
||||
|
||||
# TOTP configuration
|
||||
totp:
|
||||
# issure option is for 2FA app. It works as identifier. "My homelab' or 'ilnmors.internal', 'Authelia - ilnmors'
|
||||
issuer: 'ilnmors.internal'
|
||||
|
||||
# Identity validation confituration
|
||||
identity_validation:
|
||||
reset_password:
|
||||
jwt_secret: '' # $AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET_FILE
|
||||
|
||||
# Authentication backend provider configuration
|
||||
authentication_backend:
|
||||
ldap:
|
||||
# ldaps uses 636 -> NAT automatically change port 636 in output packet -> 6360 which lldap server uses.
|
||||
address: 'ldaps://ldap.ilnmors.internal'
|
||||
implementation: 'lldap'
|
||||
# tls configruation, it uses certificates_directory's /etc/ssl/authelia/root_ca.crt
|
||||
tls:
|
||||
server_name: 'ldap.ilnmors.internal'
|
||||
skip_verify: false
|
||||
# LLDAP base DN
|
||||
base_dn: 'dc=ilnmors,dc=internal'
|
||||
additional_users_dn: 'ou=people'
|
||||
additional_groups_dn: 'ou=groups'
|
||||
# LLDAP filters
|
||||
users_filter: '(&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person))'
|
||||
groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
|
||||
# LLDAP bind account configuration
|
||||
user: 'uid=authelia,ou=people,dc=ilnmors,dc=internal'
|
||||
password: '' # $AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PASSWORD_FILE
|
||||
# LLDAP schema mapping
|
||||
attributes:
|
||||
username: 'uid'
|
||||
display_name: 'displayName'
|
||||
mail: 'mail'
|
||||
group_name: 'cn'
|
||||
|
||||
# Access control configuration
|
||||
access_control:
|
||||
default_policy: 'deny'
|
||||
rules:
|
||||
# authelia portal
|
||||
- domain: 'authelia.ilnmors.internal'
|
||||
policy: 'bypass'
|
||||
- domain: 'authelia.ilnmors.com'
|
||||
policy: 'bypass'
|
||||
|
||||
# Session provider configuration
|
||||
session:
|
||||
secret: '' # $AUTHELIA_SESSION_SECRET_FILE
|
||||
expiration: '24 hours' # Session maintains for 24 hours
|
||||
inactivity: '2 hours' # Session maintains for 2 hours without actions
|
||||
cookies:
|
||||
- name: 'authelia_private_session'
|
||||
domain: 'ilnmors.internal'
|
||||
authelia_url: 'https://authelia.ilnmors.internal'
|
||||
same_site: 'lax'
|
||||
- name: 'authelia_public_session'
|
||||
domain: 'ilnmors.com'
|
||||
authelia_url: 'https://authelia.ilnmors.com'
|
||||
same_site: 'lax'
|
||||
|
||||
# This authelia doesn't use Redis.
|
||||
|
||||
# Storage provider configuration
|
||||
storage:
|
||||
encryption_key: '' # $AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE
|
||||
postgres:
|
||||
address: 'tcp://postgresql.ilnmors.internal:5432'
|
||||
database: 'authelia_db'
|
||||
username: 'authelia'
|
||||
password: '' # $AUTHELIA_STORAGE_POSTGRES_PASSWORD_FILE
|
||||
tls:
|
||||
server_name: 'postgresql.ilnmors.internal'
|
||||
skip_verify: false
|
||||
|
||||
# Notification provider
|
||||
notifier:
|
||||
filesystem:
|
||||
filename: '/config/notification.txt'
|
||||
# Following goal, After `Postfix` and `Dovecot` setting.
|
||||
#smtp:
|
||||
#address: 'smtp.ilnmors.internal'
|
||||
|
||||
# OIDC preperation
|
||||
# Identity provisioner configuration
|
||||
#identity_providers:
|
||||
# oidc:
|
||||
# hmac_secret: '' # $AUTHELIA_IDENTITY_PROVIDERS_OIDC_HMAC_SECRET_FILE
|
||||
# jwks:
|
||||
# - algorithm: 'RS256'
|
||||
# use: 'sig'
|
||||
# key: {{/* {{ secret "/run/secrets/AUTHELIA_JWKS_RS256" | mindent 10 "|" | msquote }} /*}}
|
||||
# - algorithm: 'ES256'
|
||||
# use: 'sig'
|
||||
# key: {{/* {{ secret "/run/secrets/AUTHELIA_JWKS_ES256" | mindent 10 "|" | msquote }} /*}}
|
||||
# clients:
|
||||
...
|
||||
```
|
||||
|
||||
### Quadlet
|
||||
|
||||
- File:
|
||||
- ~/data/config/containers/authelia/authelia.container
|
||||
|
||||
```ini
|
||||
# ~/data/config/containers/authelia/authelia.container
|
||||
[Quadlet]
|
||||
DefaultDependencies=false
|
||||
|
||||
[Unit]
|
||||
Description=Authelia
|
||||
|
||||
After=caddy.service
|
||||
Wants=caddy.service
|
||||
After=ldap.service
|
||||
Requires=ldap.service
|
||||
|
||||
[Service]
|
||||
ExecStartPre=%h/data/config/scripts/wait-for-it.sh -h localhost -p 6360 -t 0
|
||||
ExecStartPre=sleep 5
|
||||
|
||||
[Container]
|
||||
|
||||
Image=authelia/authelia:4.39.13
|
||||
|
||||
ContainerName=authelia
|
||||
|
||||
AddHost=ldap.ilnmors.internal:host-gateway
|
||||
|
||||
# Web UI
|
||||
PublishPort=9091:9091/tcp
|
||||
|
||||
|
||||
Volume=%h/data/containers/authelia/config:/config:rw
|
||||
Volume=%h/data/containers/ldap/certs:/etc/ssl/authelia:ro
|
||||
|
||||
# Default
|
||||
Environment="TZ=Asia/Seoul"
|
||||
# Enable Go template engine
|
||||
# !CAUTION!
|
||||
# If this environment were enabled, you would have to use {{/* ... /*}} for {{ go_filter }} options. Go engine always processes its own grammar first.
|
||||
Environment="X_AUTHELIA_CONFIG_FILTERS=template"
|
||||
|
||||
# Encryption
|
||||
|
||||
## JWT
|
||||
Environment="AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET_FILE=/run/secrets/AUTHELIA_JWT_SECRET"
|
||||
|
||||
Secret=AUTHELIA_JWT_SECRET,target=/run/secrets/AUTHELIA_JWT_SECRET
|
||||
|
||||
## Session
|
||||
Environment="AUTHELIA_SESSION_SECRET_FILE=/run/secrets/AUTHELIA_SESSION_SECRET"
|
||||
|
||||
Secret=AUTHELIA_SESSION_SECRET,target=/run/secrets/AUTHELIA_SESSION_SECRET
|
||||
|
||||
## Storage
|
||||
Environment="AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE=/run/secrets/AUTHELIA_STORAGE_SECRET"
|
||||
|
||||
Secret=AUTHELIA_STORAGE_SECRET,target=/run/secrets/AUTHELIA_STORAGE_SECRET
|
||||
|
||||
# OIDC (HMAC, JWKS)
|
||||
|
||||
# Environment="AUTHELIA_IDENTITY_PROVIDERS_OIDC_HMAC_SECRET_FILE=/run/secrets/AUTHELIA_HMAC_SECRET"
|
||||
|
||||
# Secret=AUTHELIA_HMAC_SECRET,target=/run/secrets/AUTHELIA_HMAC_SECRET
|
||||
|
||||
# Secret=AUTHELIA_JWKS_RS256,target=/run/secrets/AUTHELIA_JWKS_RS256
|
||||
|
||||
# Secret=AUTHELIA_JWKS_ES256,target=/run/secrets/AUTHELIA_JWKS_ES256
|
||||
|
||||
# LDAP
|
||||
Environment="AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PASSWORD_FILE=/run/secrets/AUTHELIA_LDAP_PASSWORD"
|
||||
|
||||
Secret=AUTHELIA_LDAP_PASSWORD,target=/run/secrets/AUTHELIA_LDAP_PASSWORD
|
||||
|
||||
# Database
|
||||
Environment="AUTHELIA_STORAGE_POSTGRES_PASSWORD_FILE=/run/secrets/POSTGRES_AUTHELIA_PASSWORD"
|
||||
|
||||
Secret=POSTGRES_AUTHELIA_PASSWORD,target=/run/secrets/POSTGRES_AUTHELIA_PASSWORD
|
||||
|
||||
Label=diun.enable=true
|
||||
Label=diun.watch_repo=true
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
```
|
||||
|
||||
#### Create systemd `.service` file
|
||||
|
||||
```bash
|
||||
# linger has to be activated
|
||||
ln -s ~/data/config/containers/authelia/authelia.container ~/.config/containers/systemd/authelia.container
|
||||
|
||||
systemctl --user daemon-reload
|
||||
```
|
||||
|
||||
#### Enable and start service
|
||||
|
||||
```bash
|
||||
systemctl --user start authelia.service
|
||||
```
|
||||
|
||||
### DB backup
|
||||
|
||||
- Following [here](../08_development/08_02_dev_postgresql.md).
|
||||
- dev server
|
||||
|
||||
```bash
|
||||
systemctl --user enable --now postgresql-data-backup@authelia.timer
|
||||
```
|
||||
|
||||
### Verification
|
||||
|
||||
- Web UI:
|
||||
- https://authelia.ilnmors.internal
|
||||
- https://authelia.ilnmors.com
|
||||
|
||||
- Login with LLDAP's User
|
||||
- Login: LLDAP User
|
||||
- Password: LLDAP Password
|
||||
|
||||
- Check the session
|
||||
|
||||
### Apply Forward_Auth
|
||||
|
||||
Basically, OIDC will be used in this homelab for the application which supports OIDC natively. However, some applications don't support OIDC or even login system. Therfore, Forward_Auth function in Caddy is used for authentication of those services.
|
||||
|
||||
#### Configuration files
|
||||
|
||||
- File:
|
||||
- ~/data/containers/caddy-auth/etc/Caddyfile
|
||||
- ~/data/containers/authelia/config/configuration.yml
|
||||
|
||||
#### Caddy
|
||||
|
||||
```ini
|
||||
# Caddyfile
|
||||
|
||||
# ...
|
||||
|
||||
# Foward auth test in local
|
||||
test-admin.ilnmors.com {
|
||||
import crowdsec_log
|
||||
route {
|
||||
crowdsec
|
||||
forward_auth host.containers.internal:9091 {
|
||||
# Authelia Forward Auth endpoint URI
|
||||
uri /api/authz/forward-auth
|
||||
copy_headers Remote-User Remote-Groups Remote-Email Remote-Name
|
||||
}
|
||||
# back end service
|
||||
root * /usr/share/caddy
|
||||
file_server
|
||||
}
|
||||
}
|
||||
|
||||
# Forward auth test in local
|
||||
test-default.ilnmors.com {
|
||||
import crowdsec_log
|
||||
route {
|
||||
crowdsec
|
||||
forward_auth host.containers.internal:9091 {
|
||||
# Authelia Forward Auth endpoint URI
|
||||
uri /api/authz/forward-auth
|
||||
copy_headers Remote-User Remote-Groups Remote-Email Remote-Name
|
||||
}
|
||||
# back end service
|
||||
root * /usr/share/caddy
|
||||
file_server
|
||||
}
|
||||
}
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
#### Authelia
|
||||
|
||||
```yaml
|
||||
# configuration.yml
|
||||
|
||||
# ...
|
||||
# Access control configuration
|
||||
access_control:
|
||||
default_policy: 'deny'
|
||||
rules:
|
||||
# authelia portal
|
||||
- domain: 'authelia.ilnmors.internal'
|
||||
policy: 'bypass'
|
||||
- domain: 'authelia.ilnmors.com'
|
||||
policy: 'bypass'
|
||||
- domain: 'test-admin.ilnmors.com'
|
||||
policy: 'one_factor'
|
||||
# Access control for Forward_Auth
|
||||
subject:
|
||||
- 'group:admins'
|
||||
- domain: 'test-default.ilnmors.com'
|
||||
policy: 'one_factor'
|
||||
# Access control for Forward_Auth
|
||||
subject:
|
||||
- 'group:admins'
|
||||
- 'group:users'
|
||||
|
||||
session:
|
||||
secret: '' # $AUTHELIA_SESSION_SECRET_FILE
|
||||
cookies:
|
||||
- name: 'authelia_internal_session'
|
||||
domain: 'ilnmors.internal'
|
||||
authelia_url: 'https://authelia.ilnmors.internal'
|
||||
# When login succeed, redirect
|
||||
# default_redirection_url: 'https://authelia.ilnmors.internal'
|
||||
same_site: 'lax'
|
||||
- name: 'authelia_com_session'
|
||||
domain: 'ilnmors.com'
|
||||
authelia_url: 'https://authelia.ilnmors.com'
|
||||
# default_redirection_url: 'https://authelia.ilnmors.com'
|
||||
same_site: 'lax'
|
||||
# ...
|
||||
```
|
||||
|
||||
#### Verification
|
||||
|
||||
- https://test-admin.ilnmors.com
|
||||
- user_test (gruop: users): 403 Forbidden
|
||||
- admin_test (group: admins): File server
|
||||
- https://test-default.ilnmors.com
|
||||
- admin_test (Just move): File server
|
||||
- user_test (Session initiating): File server
|
||||
|
||||
---
|
||||
|
||||
### OIDC
|
||||
|
||||
```bash
|
||||
openssl rand -base64 32
|
||||
# Add this value to .secret.yaml
|
||||
# APP_OIDC_KEY: secret value
|
||||
# Make the hash value of this secret
|
||||
# It is needed or not depends on app
|
||||
|
||||
extract_secret.sh .secret.yaml -f APP_OIDC_KEY | podman secret create APP_OIDC_KEY -
|
||||
|
||||
# Copy this value and paste next to client_secret: in configuration.yml
|
||||
|
||||
# Hash value generate
|
||||
extract_secret.sh .secret.yaml -f CADDY_OIDC_KEY
|
||||
> secret value
|
||||
|
||||
podman run --rm \
|
||||
authelia/authelia:4.39.13 authelia
|
||||
rypto hash generate --password secret_value --no-confirm
|
||||
|
||||
# Hash value validate
|
||||
podman run --rm authelia/authelia:4.39.13 authelia crypto hash validate --password secret_value -- 'HASH_VALUE'
|
||||
> The password matches the digest.
|
||||
```
|
||||
|
||||
##### Authelia
|
||||
|
||||
- Remove annotation of OIDC in container file
|
||||
|
||||
```ini
|
||||
# ~/data/config/containers/authelia/authelia.container
|
||||
# ...
|
||||
# OIDC (HMAC, JWKS)
|
||||
|
||||
Environment=AUTHELIA_IDENTITY_PROVIDERS_OIDC_HMAC_SECRET_FILE=/run/secrets/AUTHELIA_HMAC_SECRET"
|
||||
|
||||
Secret=AUTHELIA_HMAC_SECRET,target=/run/secrets/AUTHELIA_HMAC_SECRET
|
||||
|
||||
Secret=AUTHELIA_JWKS_RS256,target=/run/secrets/AUTHELIA_JWKS_RS256
|
||||
|
||||
Secret=AUTHELIA_JWKS_ES256,target=/run/secrets/AUTHELIA_JWKS_ES256
|
||||
# Remove annotation
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
- Fix the configuration.yml
|
||||
|
||||
```yaml
|
||||
---
|
||||
# certificates setting
|
||||
|
||||
# ...
|
||||
|
||||
# Identity provisioner configuration
|
||||
identity_providers:
|
||||
oidc:
|
||||
hmac_secret: '' # $AUTHELIA_IDENTITY_PROVIDERS_OIDC_HMAC_SECRET_FILE
|
||||
jwks:
|
||||
- algorithm: 'RS256'
|
||||
use: 'sig'
|
||||
key: {{ secret "/run/secrets/AUTHELIA_JWKS_RS256" | mindent 10 "|" | msquote }}
|
||||
- algorithm: 'ES256'
|
||||
use: 'sig'
|
||||
key: {{ secret "/run/secrets/AUTHELIA_JWKS_ES256" | mindent 10 "|" | msquote }}
|
||||
clients:
|
||||
- client_id: 'app'
|
||||
client_name: 'app'
|
||||
# It depends on application
|
||||
client_secret: 'HASH_VALUE'
|
||||
# If there were not client secret, public should be `true`
|
||||
public: \[ true | false \]
|
||||
response_types:
|
||||
- 'code'
|
||||
scopes:
|
||||
- 'openid'
|
||||
- 'profile'
|
||||
- 'email'
|
||||
- 'groups'
|
||||
redirect_uris:
|
||||
- 'https://app.ilnmors.com/oauth2/callback
|
||||
- 'https://app.ilnmors.com/'
|
||||
token_endpoint_auth_method: 'client_secret_post | client_secret_basic'
|
||||
authorization_policy: 'one_factor'
|
||||
...
|
||||
```
|
||||
|
||||
```bash
|
||||
# restart the service
|
||||
systemctl --user daemon-reload
|
||||
systemctl --user restart authelia
|
||||
```
|
||||
|
||||
#### Caddy
|
||||
|
||||
- Add the reverse proxy.
|
||||
|
||||
```ini
|
||||
# Caddyfile
|
||||
# ~/data/containers/caddy-auth/etc/Caddyfile
|
||||
|
||||
# ...
|
||||
app.ilnmors.com {
|
||||
import crowdsec_log
|
||||
route {
|
||||
crowdsec
|
||||
# X-Forward-Host Domain doesn't need to appy on DNS
|
||||
header_up X-Forwarded-Host app.app.ilnmors.internal
|
||||
reverse_porxy app.ilnmors.internal
|
||||
}
|
||||
}
|
||||
# ...
|
||||
```
|
||||
|
||||
```ini
|
||||
# app server's sidecar caddy
|
||||
|
||||
app.ilnmors.internal
|
||||
{
|
||||
import internal_tls
|
||||
@notes header X-Forwarded-Host app.app.ilnmors.internal
|
||||
reverse_proxy @notes host.containers.internal:3000
|
||||
}
|
||||
```
|
||||
|
||||
- Fix Caddyfile format and restart service
|
||||
|
||||
```bash
|
||||
# fix inconsistencies
|
||||
podman exec caddy-auth caddy fmt --overwrite /etc/caddy/Caddyfile
|
||||
# After Caddyfile setting is changed use this command.
|
||||
systemctl --user daemon-reload
|
||||
systemctl --user restart caddy-auth
|
||||
```
|
||||
|
||||
#### Verification
|
||||
|
||||
- Following.
|
||||
@@ -0,0 +1,149 @@
|
||||
Tags: #os, #configuration, #development, #virtualization
|
||||
|
||||
## Preparation
|
||||
|
||||
### Set DHCP reservation and DNS record
|
||||
|
||||
#### Set DHCP reservation on KEA DHCP in OPNsense
|
||||
|
||||
Following [here](05_07_opnsense_kea.md)
|
||||
|
||||
- Services:Kea DHCP:Kea DHCPv4:Reservations - \[+\]
|
||||
- Subnet: 192.168.10.0/24
|
||||
- IP address: 192.168.10.13
|
||||
- MAC address: 0A:49:6E:4D:03:00
|
||||
- Hostname: dev
|
||||
- Description: dev
|
||||
- `save`
|
||||
|
||||
#### Set DNS records in BIND
|
||||
|
||||
Following [here](../06_network/06_03_net_bind.md).
|
||||
|
||||
- net server
|
||||
- file:
|
||||
- ~/data/containers/bind/lib/db.ilnmors.internal
|
||||
- ~/data/containers/bind/lib/db.10.168.192.in-addr.arpa
|
||||
|
||||
```ini
|
||||
# db.ilnmors.internal
|
||||
# ...
|
||||
dev IN A 192.168.10.13
|
||||
# ...
|
||||
# db.10.168.192.in-addr.arpa
|
||||
# ...
|
||||
13 IN PTR dev.ilnmors.internal.
|
||||
# ...
|
||||
```
|
||||
|
||||
```bash
|
||||
# Adguard container has Requires=bind.service. When it restarted, then Adguard also restarted.
|
||||
systemctl --user restart bind
|
||||
```
|
||||
|
||||
### Create VM template
|
||||
|
||||
- ~/data/config/scripts/dev.sh
|
||||
|
||||
```bash
|
||||
virt-install \
|
||||
--boot uefi \
|
||||
--name dev \
|
||||
--os-variant debian13 \
|
||||
--vcpus 2 \
|
||||
--memory 6144 \
|
||||
--location /var/lib/libvirt/images/debian-13.0.0-amd64-netinst.iso \ # For serial installing, use `--location` instead of `--cdrom`
|
||||
--disk pool=vm-images,size=258,format=qcow2,discard=unmap \
|
||||
--network network=ovs-lan-net,portgroup=vlan10-access,model=virtio,mac=0A:49:6E:4D:03:00 \ # Use designated ovs port group
|
||||
--graphics none \
|
||||
--console pty,target_type=serial \
|
||||
--extra-args "console=ttyS0,115200"
|
||||
# After enter this command, then the console start automatically
|
||||
# Remove all annotation before you make the sh file.
|
||||
```
|
||||
|
||||
### Debian installing
|
||||
|
||||
- Following [here](../03_common/03_01_debian_configuration.md) to install Debian.
|
||||
- Debian installer supports serial mode regardless getty@ttyS0 service is enabled or not.
|
||||
- Following [here](../03_common/03_02_iptables.md) to set iptables.
|
||||
- Following [here](../03_common/03_04_crowdsec.md) to set CrowdSec
|
||||
|
||||
#### Serial console setting
|
||||
|
||||
After installation, use `ctrl + ]` to exit console. Before setting getty@ttyS0, you can't use serial console to access VM. Therefore, use IP address set on installation, and connect net server via ssh first, following the step to enable the getty.
|
||||
|
||||
### Modify VM template settings
|
||||
|
||||
After getty setting, shutdown dev vm with `shutdown` in VM or `sudo virsh shutdown dev` in hypervisor to turn off vm first.
|
||||
|
||||
```bash
|
||||
virsh edit dev
|
||||
```
|
||||
|
||||
```xml
|
||||
<!-- dev -->
|
||||
...
|
||||
</vcpu>
|
||||
<cputune>
|
||||
<shares>1024</shares>
|
||||
</cputune>
|
||||
<!-- cpu priority - 1024: default/2048: high/512: low -->
|
||||
|
||||
<!--
|
||||
<disk type='file' device='cdrom'>
|
||||
...
|
||||
</disk>
|
||||
# Remove booting disk
|
||||
-->
|
||||
```
|
||||
|
||||
```bash
|
||||
virsh dumpxml dev > ~/data/config/vms/dumps/dev.xml
|
||||
virsh start dev && virsh console dev
|
||||
# Start dev server with console
|
||||
```
|
||||
|
||||
### Common setting
|
||||
|
||||
- dev.service
|
||||
|
||||
```ini
|
||||
# ~/data/config/services/dev.service
|
||||
# ~/.config/systemd/user/dev.service
|
||||
[Unit]
|
||||
Description=dev Auto Booting
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
Requires=opnsense.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
|
||||
# Maintain status as active
|
||||
RemainAfterExit=yes
|
||||
|
||||
# CrowdSec should be set
|
||||
ExecStartPre=%h/data/config/scripts/wait-for-it.sh 192.168.10.1:8080 -t 0
|
||||
ExecStartPre=%h/data/config/scripts/wait-for-it.sh 192.168.10.11:53 -t 0
|
||||
ExecStartPre=%h/data/config/scripts/wait-for-it.sh 192.168.10.12:9000 -t 0
|
||||
|
||||
ExecStartPre=/bin/bash -c "sleep 15"
|
||||
|
||||
# Run the service
|
||||
ExecStart=/usr/bin/virsh -c qemu:///system start dev
|
||||
|
||||
# Stop the service
|
||||
ExecStop=/usr/bin/virsh -c qemu:///system shutdown dev
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
```
|
||||
|
||||
```bash
|
||||
ln -s ~/data/config/services/dev.service ~/.config/systemd/user/dev.service
|
||||
|
||||
systemctl --user daemon-reload
|
||||
systemctl --user enable dev.service
|
||||
systemctl --user start dev.service
|
||||
```
|
||||
@@ -0,0 +1,534 @@
|
||||
Tags: #os, #configuration, #virtualization, #container, #database
|
||||
|
||||
## postgresql
|
||||
|
||||
Postgresql is one of most famous open source RDBMS (Relational Database Management System). RDBMS saves the data as table which is based on row and coloumn. This uses SQL (Structure Quarey Language) to manage a lot of data and guarantee integrity of data.
|
||||
|
||||
### Secret management
|
||||
|
||||
All transaction of postgresql which needs root permission will be conducted on local container or podman exec (without other Pod) in this porject. However, the environment value `POSTGRES_PASSWORD` should be set when the database is initiated. It makes postgresql programs root permission access way as trust which means only local access is allowed. This infromation will be set on `pg_hba.conf` like below.
|
||||
|
||||
- `local all postgres trust`
|
||||
- `hostssl all all 192.168.10.x/32 scram-sha-256` (The server that uses postgresql)
|
||||
- `host all all 127.0.0.1/32 scram-sha-256`
|
||||
|
||||
#### Secret
|
||||
|
||||
- File:
|
||||
- ~/data/config/secrets/.secret.yaml
|
||||
|
||||
- Edit `.secret.yaml` with `edit_secret.sh`
|
||||
|
||||
|
||||
```yaml
|
||||
# ~/data/config/secrets/.secret.yaml
|
||||
# postgresql
|
||||
POSTGRES_PASSWORD: secret
|
||||
```
|
||||
|
||||
### Preparation
|
||||
|
||||
#### iptables and firewall rules
|
||||
|
||||
- Set iptables first, following [here](../03_common/03_02_iptables.md).
|
||||
- 5432: auth, app
|
||||
- Set firewall rules first, following [here](Latest/05_firewall/05_04_opnsense_rules.md).
|
||||
|
||||
#### Create directory for container
|
||||
|
||||
```bash
|
||||
mkdir -p ~/data/containers/postgresql
|
||||
chmod 700 ~/data/containers/postgresql
|
||||
setfacl -m d:g::0 ~/data/containers/postgresql
|
||||
setfacl -m d:o::0 ~/data/containers/postgresql
|
||||
setfacl -m u:dev:rwx ~/data/containers/postgresql
|
||||
setfacl -m u:100998:rwx ~/data/containers/postgresql
|
||||
setfacl -d -m u:dev:rwx ~/data/containers/postgresql
|
||||
setfacl -d -m u:100998:rwx ~/data/containers/postgresql
|
||||
mkdir ~/data/containers/postgresql/{backups,config,certs,data,initdb}
|
||||
|
||||
# After generating
|
||||
c
|
||||
sudo find ~/data/containers/postgresql -type d -exec setfacl -m m::rwx {} \;
|
||||
```
|
||||
|
||||
> postgresql container executes as 999:999(postgres:postgres) permission in container. It is mapped host's 100998. Therefore, directories have to have ACL via `setfacl`
|
||||
|
||||
#### Add new domain in BIND
|
||||
|
||||
Following [here](../06_network/06_03_net_bind.md).
|
||||
|
||||
- net server
|
||||
- file: ~/data/containers/bind/lib/db.ilnmors.internal
|
||||
|
||||
```ini
|
||||
# ...
|
||||
postgresql IN CNAME dev.ilnmors.internal.
|
||||
# ...
|
||||
```
|
||||
|
||||
```bash
|
||||
# Adguard container has Requires=bind.service. When it restarted, then Adguard also restarted.
|
||||
systemctl --user restart bind
|
||||
```
|
||||
|
||||
### Podman Image
|
||||
|
||||
```bash
|
||||
podman pull postgres:18.0 # Do not use latest version to management
|
||||
```
|
||||
|
||||
### Config file
|
||||
|
||||
- File:
|
||||
- ~/data/containers/postgresql/config/postgresql.conf
|
||||
- ~/data/containers/postgresql/config/pg_hba.conf
|
||||
|
||||
|
||||
```bash
|
||||
# extracting config file
|
||||
podman run --rm postgres:18.0 cat /usr/share/postgresql/postgresql.conf.sample > ~/data/containers/postgresql/config/postgresql.conf
|
||||
|
||||
podman run --rm postgres:18.0 cat /usr/share/postgresql/18/pg_hba.conf.sample > ~/data/containers/postgresql/config/pg_hba.conf
|
||||
|
||||
# Generate the password
|
||||
openssl rand -base64 32
|
||||
|
||||
# Podman secret
|
||||
extract_secret.sh ~/data/config/secrets/.secret.yaml -f "POSTGRES_PASSWORD" | podman secret create "POSTGRES_PASSWORD" -
|
||||
```
|
||||
|
||||
> If there were the schema backup file, then go to the `Restore` section.
|
||||
|
||||
```ini
|
||||
# ~/data/containers/postgresql/config/postgresql.conf
|
||||
|
||||
# Add settings for extensions here
|
||||
|
||||
# hba_file directory
|
||||
hba_file = '/config/pg_hba.conf'
|
||||
|
||||
# Listen_address
|
||||
listen_addresses = '*'
|
||||
|
||||
# listen_port
|
||||
port = 5432
|
||||
|
||||
# SSL
|
||||
ssl = on
|
||||
ssl_ca_file = '/etc/ssl/postgresql/root_ca.crt'
|
||||
ssl_cert_file = '/etc/ssl/postgresql/postgresql.ilnmors.internal/fullchain.pem'
|
||||
ssl_key_file = '/etc/ssl/postgresql/postgresql.ilnmors.internal/key.pem'
|
||||
ssl_ciphers = 'HIGH:!aNULL:!MD5'
|
||||
ssl_prefer_server_ciphers = on
|
||||
|
||||
# log
|
||||
log_destination = 'stderr'
|
||||
log_checkpoints = on
|
||||
log_temp_files = 0
|
||||
log_min_duration_statement = 500
|
||||
|
||||
```
|
||||
|
||||
```ini
|
||||
# ~/data/containers/postgresql/config/pg_hba.conf
|
||||
|
||||
# Local host `trust`
|
||||
local all all trust
|
||||
|
||||
# Local connection (container) needs password (127.0.0.1 - container loopback)
|
||||
host all all 127.0.0.1/32 scram-sha-256
|
||||
# Local connection (dev) needs password (169.254.1.2 - host-gateway)
|
||||
# Maybe Grafana(SQLite), Uptime kuma(SQLite), Loki(BoltDB), Dovecot(LDAP or />
|
||||
#hostssl all all 169.254.1.2/32 scram-sha-256
|
||||
|
||||
# auth VM (Authentik, 192.168.10.12)
|
||||
hostssl all all 192.168.10.12/32 scram-sha-256
|
||||
|
||||
# app VM (Applications, 192.168.10.14)
|
||||
hostssl all all 192.168.10.14/32 scram-sha-256
|
||||
|
||||
# explicit deny
|
||||
host all all 192.168.10.0/24 reject
|
||||
```
|
||||
|
||||
#### Initiating (Restoring)
|
||||
|
||||
- File:
|
||||
- ~/data/config/scripts/postgresql/postgresql_init.sh
|
||||
- ~/data/containers/postgresql/backups/postgresql-cluster_$(date "+%Y-%m-%d").sql
|
||||
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# ~/data/config/scripts/postgresql/postgresql_init.sh [FILE_PATH]
|
||||
set -e
|
||||
DATA_PATH="$HOME/data/containers/postgresql/data"
|
||||
FILE_PATH="$1"
|
||||
VERSION=18
|
||||
FLAG=""
|
||||
|
||||
# Check the PostgreSQL service
|
||||
|
||||
if [ $(systemctl --user is-active postgresql) == "active" ]; then
|
||||
echo "PostgreSQL should be terminated"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check the podman secret
|
||||
|
||||
if [ -z "$(podman secret list | grep "POSTGRES_PASSWORD")" ]; then
|
||||
echo "POSTGRES_PASSWORD has to be in podman secret"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check the data path
|
||||
|
||||
if [ -n "$(ls -A $DATA_PATH)" ]; then
|
||||
echo "$DATA_PATH should be empty"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check the sql file
|
||||
|
||||
if [ -z "$FILE_PATH" ]; then
|
||||
echo "Initiating PostgreSQL"
|
||||
FLAG="FALSE"
|
||||
else
|
||||
if [ ! -f "$FILE_PATH" -o ! -s "$FILE_PATH" -o -z "$(echo $FILE_PATH | grep "\.sql$")" ]; then
|
||||
echo "Availavble .sql format file is needed"
|
||||
exit 1
|
||||
fi
|
||||
echo "Restoring PostgreSQL"
|
||||
FLAG="TRUE"
|
||||
fi
|
||||
|
||||
|
||||
|
||||
# Initiating
|
||||
podman run --rm \
|
||||
--secret POSTGRES_PASSWORD,type=env \
|
||||
-e TZ="Asia/Seoul" \
|
||||
-v "$DATA_PATH":/var/lib/postgresql:rw \
|
||||
postgres:18.0 \
|
||||
-C "port" || true
|
||||
|
||||
# postgresql start
|
||||
echo "Start postgresql service"
|
||||
systemctl --user start postgresql
|
||||
|
||||
# Restoring
|
||||
if [ "$FLAG" == "TRUE" ]; then
|
||||
while [ -z "$(systemctl --user status postgresql | grep "database system is ready to accept connections")" ]; do
|
||||
sleep 1
|
||||
done
|
||||
echo "Start restoring PostgreSQL"
|
||||
cat "$FILE_PATH" | podman exec -i -u postgres postgresql psql -U postgres
|
||||
echo "Finish restoring PostgreSQL"
|
||||
fi
|
||||
|
||||
exit 0
|
||||
```
|
||||
#### Certificates configuration
|
||||
|
||||
- File: ~/data/containers/postgresql/certs/root_ca.crt
|
||||
|
||||
- ACME setting (OPNsense)
|
||||
- Services:ACME Client:Certificates - Certificates - \[+\]
|
||||
- Common Name: postgresql.ilnmors.internal
|
||||
- Description: postgresql
|
||||
- ACME Account: acme.ilnmors.internal
|
||||
> Even though provisioner's name includes `@`, it has to use as `.`.
|
||||
>
|
||||
> i.e. `acme@ilnmors.internal` > `acme.ilnmors.internal`
|
||||
- Challenge Type: ilnmors.internal-dns-01-challenge
|
||||
- \[\*\] Auto Renewal
|
||||
- Automations: postgresql-auto-acme, postgresql-auto-restart
|
||||
|
||||
- Automations (OPNsense)
|
||||
- Services:ACME Client:Automations - Automation - \[+\]
|
||||
- Name: postgresql-auto-acme / postgresql-auto-reload
|
||||
- Description: postgresql acme crt issue / reload postgresql after crt is issued
|
||||
- Run Command: Upload certificate via SFTP / Remote command via SSH
|
||||
- SFTP Host: postgresql.ilnmors.internal
|
||||
- Username: dev
|
||||
- Identity Type: ed25519
|
||||
- Remote Path(SFTP): /home/dev/data/containers/postgresql/certs
|
||||
- Command(SSH): setfacl -m m::r /home/dev/data/containers/postgresql/certs/postgresql.ilnmors.internal/* && podman exec -u postgres postgresql pg_ctl reload
|
||||
- `Show Identity`
|
||||
> Copy Required parameters `ssh-ed25519 ~~~ root@opnsense.ilnmors.internal`
|
||||
>
|
||||
> Add parameters in net server's ~/.ssh/authorized_keys
|
||||
- `Test Connect` and `Save`
|
||||
|
||||
- SSH command will be applied after postgresql start.
|
||||
|
||||
#### Quadlet
|
||||
|
||||
- File:
|
||||
- ~/data/config/containers/postgresql/postgresql.container
|
||||
|
||||
```ini
|
||||
# ~/data/config/containers/postgresql/postgresql.container
|
||||
[Quadlet]
|
||||
DefaultDependencies=false
|
||||
|
||||
[Unit]
|
||||
Description=PostgreSQL
|
||||
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Container]
|
||||
Image=postgres:18.0
|
||||
|
||||
ContainerName=postgresql
|
||||
|
||||
PublishPort=5432:5432/tcp
|
||||
|
||||
Volume=%h/data/containers/postgresql/data:/var/lib/postgresql:rw
|
||||
Volume=%h/data/containers/postgresql/config:/config:ro
|
||||
Volume=%h/data/containers/postgresql/backups:/backups:rw
|
||||
Volume=%h/data/containers/postgresql/certs:/etc/ssl/postgresql:ro
|
||||
|
||||
Environment="TZ=Asia/Seoul"
|
||||
|
||||
Exec=postgres -c 'config_file=/config/postgresql.conf'
|
||||
|
||||
Label=diun.enable=true
|
||||
Label=diun.watch_repo=true
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
```
|
||||
|
||||
#### Create systemd `.service` file
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.config/containers/systemd
|
||||
chmod -R 700 ~/.config/containers/systemd
|
||||
|
||||
ln -s ~/data/config/containers/postgresql/postgresql.container ~/.config/containers/systemd/postgresql.container
|
||||
|
||||
systemctl --user daemon-reload
|
||||
```
|
||||
|
||||
#### Enable and start service
|
||||
|
||||
```bash
|
||||
# Before start service, setfacl for certificates (before register automation)
|
||||
setfacl -m m::r /home/dev/data/containers/postgresql/certs/postgresql.ilnmors.internal/*
|
||||
|
||||
systemctl --user start postgresql.service
|
||||
```
|
||||
|
||||
### Create user
|
||||
|
||||
- Login to psql in the container
|
||||
|
||||
```bash
|
||||
podman exec -it -u postgres postgresql psql -U postgres
|
||||
> # Create user and database
|
||||
> CREATE USER $USER WITH PASSWORD 'password';
|
||||
> CREATE DATABASE $DB;
|
||||
> ALTER DATABASE $DB OWNER TO $USER;
|
||||
> \du
|
||||
> \l
|
||||
|
||||
# Whenever you modify the schema including user or database structure, conduct postgresql-cluster-backup.service
|
||||
systemctl --user start postgresql-cluster-backup.service
|
||||
|
||||
|
||||
> # If you want to change the password
|
||||
> ALTER USER $USER WITH PASSWORD 'password';
|
||||
> # After this, update the .secret.yaml file and podman secret
|
||||
```
|
||||
|
||||
### Backup
|
||||
|
||||
#### Backup service
|
||||
|
||||
- File:
|
||||
- ~/data/config/services/postgresql/postgresql-cluster-backup.service
|
||||
- ~/data/config/services/postgresql/postgresql-cluster-backup.timer
|
||||
- ~/data/config/services/postgresql/postgresql-data-backup@.service
|
||||
- ~/data/config/services/postgresql/postgresql-data-backup@.timer
|
||||
|
||||
#### Cluster
|
||||
|
||||
```ini
|
||||
# ~/data/config/services/postgresql/postgresql-cluster-backup.service
|
||||
# ~/.config/systemd/user/postgresql-cluster-backup.service
|
||||
[Unit]
|
||||
Description=PostgreSQL Cluster Backup Service
|
||||
After=postgresql.service
|
||||
BindsTo=postgresql.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
|
||||
# logging
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
|
||||
ExecStartPre=podman exec -u postgres postgresql sh -c "mkdir -p /backups/cluster"
|
||||
|
||||
# Run the script
|
||||
ExecStart=podman exec -u postgres postgresql sh -c "pg_dumpall -U postgres --schema-only | grep -v -E \"ROLE postgres\" > /backups/cluster/postgresql-cluster_$(date "+%%Y-%%m-%%d").sql"
|
||||
|
||||
ExecStop=podman exec -u postgres postgresql sh -c "find /backups/cluster -maxdepth 1 -type f -mtime +7 -delete"
|
||||
```
|
||||
|
||||
```ini
|
||||
# ~/data/config/services/postgresql/postgresql-cluster-backup.timer
|
||||
# ~/.config/systemd/user/postgresql-cluster-backup.timer
|
||||
[Unit]
|
||||
Description=Run PostgreSQL Cluster Backup service every day
|
||||
|
||||
[Timer]
|
||||
# Execute service after 1 min on booting
|
||||
OnBootSec=1min
|
||||
|
||||
# Execute service every day 00:00
|
||||
OnCalendar=*-*-* 00:00:00
|
||||
# Random time to postpone the timer
|
||||
RandomizedDelaySec=15min
|
||||
Persistent=true
|
||||
|
||||
# When timer is activated, Service also starts.
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
```
|
||||
|
||||
##### Restore cluster
|
||||
|
||||
- File:
|
||||
- ~/data/config/scripts/postgresql/postgresql_init.sh
|
||||
- ~/data/containers/postgresql/backups/cluster/postgresql-cluster_$(date "+%Y-%m-%d").sql
|
||||
|
||||
- Use `postgresql_init.sh postgresql-cluster_$(date "+%Y-%m-%d").sql` command
|
||||
|
||||
#### Data for each app
|
||||
|
||||
```ini
|
||||
# ~/data/config/services/postgresql/postgresql-data-backup@.service
|
||||
# ~/.config/systemd/user/postgresql-data-backup@.service
|
||||
[Unit]
|
||||
Description=PostgreSQL Data %i Backup Service
|
||||
After=postgresql.service
|
||||
BindsTo=postgresql.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
|
||||
# logging
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
|
||||
ExecStartPre=podman exec -u postgres postgresql sh -c "mkdir -p /backups/%i"
|
||||
# Run the script
|
||||
ExecStart=podman exec -u postgres postgresql sh -c "pg_dump -U postgres -d %i_db --data-only > /backups/%i/postgresql-%i-data_$(date "+%%Y-%%m-%%d").sql"
|
||||
|
||||
ExecStop=podman exec -u postgres postgresql sh -c "find "/backups/%i" -maxdepth 1 -type f -mtime +7 -delete"
|
||||
```
|
||||
|
||||
```ini
|
||||
# ~/data/config/services/postgresql/postgresql-data-backup@.timer
|
||||
# ~/data/config/services/postgresql/postgresql-data-backup@.timer
|
||||
[Unit]
|
||||
Description=Run %i Data Backup service every day
|
||||
|
||||
[Timer]
|
||||
# Execute service after 1 min on booting
|
||||
OnBootSec=1min
|
||||
|
||||
# Execute service every day 00:00
|
||||
OnCalendar=*-*-* 00:00:00
|
||||
# Random time to postpone the timer
|
||||
RandomizedDelaySec=15min
|
||||
Persistent=true
|
||||
|
||||
# When timer is activated, Service also starts.
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
```
|
||||
|
||||
##### Data restore
|
||||
|
||||
- File: postgresql-app-data_$(date "+%Y-%m-%d").sql
|
||||
|
||||
```bash
|
||||
# The Schema must be needed. (cluster, DB and user)
|
||||
|
||||
# DB owner's session terminate
|
||||
# server where app is located
|
||||
systemctl --user stop app.service
|
||||
# Check session
|
||||
# dev server
|
||||
# Print the all session
|
||||
podman exec -u postgres postgresql psql -U postgres -c "SELECT * from pg_stat_activity;"
|
||||
> $TARGET_PID
|
||||
# exit session
|
||||
podman exec -u postgres postgresql psql -U postgres -c "SELECT pg_terminate_backend($TARGET_PID);"
|
||||
|
||||
# Using psql
|
||||
cat postgresql-app-data_$(date "+%Y-%m-%d").sql | podman exec -i -u postgres postgresql psql -U app
|
||||
```
|
||||
|
||||
#### Register service
|
||||
|
||||
```bash
|
||||
# Register service
|
||||
mkdir -p ~/.config/systemd/user && chmod 700 ~/.config/systemd/user
|
||||
|
||||
ln -s ~/data/config/services/postgresql/postgresql-cluster-backup.service ~/.config/systemd/user/postgresql-cluster-backup.service
|
||||
|
||||
ln -s ~/data/config/services/postgresql/postgresql-cluster-backup.timer ~/.config/systemd/user/postgresql-cluster-backup.timer
|
||||
|
||||
ln -s ~/data/config/services/postgresql/postgresql-data-backup\@.service ~/.config/systemd/user/postgresql-data-backup\@.service
|
||||
|
||||
ln -s ~/data/config/services/postgresql/postgresql-data-backup\@.timer ~/.config/systemd/user/postgresql-data-backup\@.timer
|
||||
|
||||
systemctl --user daemon-reload
|
||||
|
||||
# Start timer and enable
|
||||
systemctl --user enable --now postgresql-cluster-backup.timer
|
||||
systemctl --user enable --now postgresql-data-backup@app.timer
|
||||
```
|
||||
|
||||
### Verification
|
||||
|
||||
```bash
|
||||
# Init database
|
||||
postgresql_init.sh
|
||||
# ... Start postgresql service
|
||||
|
||||
# Create user and database
|
||||
podman exec -it -u postgres postgresql psql -U postgres
|
||||
> CREATE USER test WITH PASSWORD 'abc';
|
||||
> CREATE DATABASE test_db;
|
||||
> ALTER DATABASE test_db OWNER TO test;
|
||||
> \du
|
||||
> \l
|
||||
> \q
|
||||
|
||||
# Backup service executes
|
||||
systemctl --user start postgresql-cluster-backup.service
|
||||
|
||||
# Stop and remove all data
|
||||
systemctl --stop postgresql
|
||||
sudo find "/home/dev/data/containers/postgresql/data" -mindepth 1 -delete
|
||||
|
||||
# Restore database
|
||||
postgresql_init.sh ~/data/containers/backups/filename.sql
|
||||
|
||||
# Check restoring
|
||||
podman exec -it -u postgres postgresql psql -U postgres
|
||||
> \du
|
||||
> \l
|
||||
```
|
||||
|
||||
@@ -0,0 +1,259 @@
|
||||
Tags: #os, #configuration, #network, #virtualization, #container, #security
|
||||
|
||||
## Caddy - dev
|
||||
|
||||
Caddy is an open source reverse proxy (web server) which supports automatically to apply TLS certificates via ACME protocol from CA. This caddy will work as sidecar caddy, so it only uses private TLS. It can only communication with auth's main caddy. This means it doesn't need any module except RFC2136 module. Because main caddy will conduct WAF function at all.
|
||||
|
||||
### Secret management
|
||||
|
||||
- File:
|
||||
- ~/data/config/secrets/.secret.yaml
|
||||
|
||||
- Edit `.secret.yaml` with `edit_secret.sh`
|
||||
|
||||
```yaml
|
||||
# ~/data/config/secrets/.secret.yaml
|
||||
# CADDY:
|
||||
CADDY_ACME_KEY: acme-key_key_value (Only secret value)
|
||||
```
|
||||
|
||||
```bash
|
||||
# Podman secret
|
||||
extract_secret.sh .secret.yaml -f CADDY_ACME_KEY | podman secret create CADDY_ACME_KEY -
|
||||
```
|
||||
|
||||
### Preparation
|
||||
|
||||
#### iptables and firewall rules
|
||||
|
||||
- Set iptables first, following [here](../03_common/03_02_iptables.md).
|
||||
- Limit access client as Caddy - auth ( -s 192.168.10.12/32 )
|
||||
- 443 > 2443 (iptables setting)
|
||||
- Set firewall rules first, following [here](Latest/05_firewall/05_04_opnsense_rules.md).
|
||||
#### Create directory for container
|
||||
|
||||
```bash
|
||||
mkdir -p ~/data/containers/caddy-dev/{etc,data}
|
||||
chmod -R 700 ~/data/containers/caddy-dev
|
||||
```
|
||||
|
||||
> Caddy container executes as 0:0(root:root) permission in container. It is mapped host's UID. Therefore, directories don't have to have ACL via `setfacl`
|
||||
|
||||
#### Add new domain in BIND
|
||||
|
||||
Following [here](../06_network/06_03_net_bind.md).
|
||||
|
||||
- net server
|
||||
- file: ~/data/containers/bind/lib/db.ilnmors.internal
|
||||
|
||||
```ini
|
||||
# ...
|
||||
dev IN A 192.168.10.13
|
||||
# ...
|
||||
```
|
||||
|
||||
```bash
|
||||
# Adguard container has Requires=bind.service. When it restarted, then Adguard also restarted.
|
||||
systemctl --user restart bind
|
||||
```
|
||||
|
||||
### Podman Image
|
||||
|
||||
#### Podman containerfile
|
||||
|
||||
Caddy supports various module for it. rfc2136(nsupdate) module, Sidecar caddy only recieves the request from the main caddy. Therefore it doesn't need anymore.
|
||||
|
||||
- file:
|
||||
- ~/data/config/containers/caddy-dev/containerfile-caddy-2.10.2-dev
|
||||
- ~/data/config/containers/caddy-dev/root_ca.crt
|
||||
|
||||
```containerfile
|
||||
FROM caddy:2.10.2-builder-alpine AS builder
|
||||
|
||||
RUN xcaddy build \
|
||||
--with github.com/caddy-dns/rfc2136
|
||||
|
||||
FROM caddy:2.10.2
|
||||
|
||||
COPY --from=builder /usr/bin/caddy /usr/bin/caddy
|
||||
|
||||
COPY ./root_ca.crt /usr/local/share/ca-certificates/root_ca.crt
|
||||
|
||||
RUN update-ca-certificates
|
||||
```
|
||||
|
||||
#### Podman image build
|
||||
|
||||
```bash
|
||||
podman build -t caddy:2.10.2-dev -f ~/data/config/containers/caddy-dev/containerfile-caddy-2.10.2-dev . && podman image prune -f
|
||||
# Delete pure caddy and caddy-builder-alpine images after command above manually.
|
||||
```
|
||||
|
||||
### Configuration files
|
||||
|
||||
Caddyfile will be updated after Authelia setting
|
||||
|
||||
```bash
|
||||
# fix inconsistencies
|
||||
podman exec caddy-dev caddy fmt --overwrite /etc/caddy/Caddyfile
|
||||
# After Caddyfile setting is changed use this command.
|
||||
podman exec caddy-dev caddy reload --config /etc/caddy/Caddyfile
|
||||
```
|
||||
|
||||
- file:
|
||||
- ~/data/containers/caddy-auth/etc/Caddyfile
|
||||
- ~/data/containers/authelia/config/configuration.yml
|
||||
- ~/data/containers/caddy-dev/etc/Caddyfile
|
||||
|
||||
```ini
|
||||
# Caddyfile
|
||||
# ~/data/containers/caddy-auth/etc/Caddyfile
|
||||
|
||||
# Forward Auth for other vms
|
||||
(apply_forward_auth) {
|
||||
forward_auth host.containers.internal:9091 {
|
||||
uri /api/authz/forward-auth
|
||||
copy_headers Remote-User Remote-Groups Remote-Email R>
|
||||
}
|
||||
reverse_proxy {args[0]} {
|
||||
header_up Host {http.reverse_proxy.upstream.host}
|
||||
# X-Forwarded-Host header contains original Host value
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# ...
|
||||
dev-test.ilnmors.com {
|
||||
import crowdsec_log
|
||||
route {
|
||||
crowdsec
|
||||
import apply_forward_auth https://dev.ilnmors.internal
|
||||
}
|
||||
}
|
||||
# ...
|
||||
```
|
||||
|
||||
```ini
|
||||
# Caddyfile
|
||||
# ~/data/containers/caddy-dev/etc/Caddyfile
|
||||
{
|
||||
server {
|
||||
trusted_proxies static 192.168.10.12/32
|
||||
trusted_proxies_strict
|
||||
# To find the real client's IP.
|
||||
# default = false : left to right, when the Caddy met the untrusted IP first, then it would treat as client IP
|
||||
# true : right to left > It is easy to find currupted client IP
|
||||
}
|
||||
}
|
||||
|
||||
(private_tls) {
|
||||
tls {
|
||||
issuer acme {
|
||||
dir https://step-ca.ilnmors.internal:9000/acme/acme@ilnmors.internal/directory
|
||||
dns rfc2136 {
|
||||
server bind.ilnmors.internal:2253
|
||||
key_name acme-key
|
||||
key_alg hmac-sha256
|
||||
key "{file./run/secrets/CADDY_ACME_KEY}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dev.ilnmors.internal {
|
||||
import private_tls
|
||||
@test header X-Forwarded-Host dev-test.ilnmors.com
|
||||
route @test {
|
||||
root * /usr/share/caddy
|
||||
file_server
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```yaml
|
||||
# configuration.yml
|
||||
|
||||
# ...
|
||||
# Access control configuration
|
||||
access_control:
|
||||
default_policy: 'deny'
|
||||
rules:
|
||||
# authelia portal
|
||||
- domain: 'authelia.ilnmors.internal'
|
||||
policy: 'bypass'
|
||||
- domain: 'authelia.ilnmors.com'
|
||||
policy: 'bypass'
|
||||
- domain: 'dev-test.ilnmors.com'
|
||||
policy: 'one_factor'
|
||||
# Access control for Forward_Auth
|
||||
subject:
|
||||
- 'group:admins'
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
### Quadlet
|
||||
|
||||
- File:
|
||||
- ~/data/config/containers/caddy-auth/caddy-dev.container
|
||||
|
||||
```ini
|
||||
# ~/data/config/containers/caddy-dev/caddy-dev.container
|
||||
# ~/data/config/containers/caddy-dev/caddy-dev.container
|
||||
[Quadlet]
|
||||
DefaultDependencies=false
|
||||
|
||||
[Unit]
|
||||
Description=Caddy - dev
|
||||
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
# Main Caddy and Step-CA
|
||||
ExecStartPre=%h/data/config/scripts/wait-for-it.sh -h 192.168.10.12 -p 443 -t 0
|
||||
ExecStartPre=%h/data/config/scripts/wait-for-it.sh -h 192.168.10.12 -p 9000 -t 0
|
||||
ExecStartPre=sleep 5
|
||||
|
||||
[Container]
|
||||
Image=localhost/caddy:2.10.2-dev
|
||||
|
||||
ContainerName=caddy-dev
|
||||
|
||||
PublishPort=2080:80/tcp
|
||||
PublishPort=2443:443/tcp
|
||||
|
||||
Volume=%h/data/containers/caddy-dev/etc:/etc/caddy:rw
|
||||
Volume=%h/data/containers/caddy-dev/data:/data:rw
|
||||
|
||||
Environment="TZ=Asia/Seoul"
|
||||
|
||||
Secret=CADDY_ACME_KEY,target=/run/secrets/CADDY_ACME_KEY
|
||||
|
||||
Label=diun.enable=true
|
||||
Label=diun.watch_repo=true
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
```
|
||||
|
||||
#### Create systemd `.service` file
|
||||
|
||||
```bash
|
||||
# linger has to be activated
|
||||
ln -s ~/data/config/containers/caddy-dev/caddy-dev.container ~/.config/containers/systemd/caddy-dev.container
|
||||
|
||||
systemctl --user daemon-reload
|
||||
```
|
||||
|
||||
#### Enable and start service
|
||||
|
||||
```bash
|
||||
systemctl --user start caddy-dev.service
|
||||
```
|
||||
|
||||
#### Verification
|
||||
|
||||
- https://dev-test.ilnmors.com
|
||||
- user_test (gruop: users): 403 Forbidden
|
||||
- admin_test (group: admins): File server
|
||||
@@ -0,0 +1,341 @@
|
||||
Tags: #os, #configuration, #security , #virtualization, #container, #development
|
||||
|
||||
## Code-Server
|
||||
|
||||
Code-Server is an open source and self-hosted Code editer (or IDE with its plugins) to use on web browser. This supports terminal and code editer, git, and ansible. It will be used as a bastion host in the home-lab. Code-Server doesn't support login system. Therefore, authelia and caddy's Forward-Auth function will be used in this homelab system.
|
||||
|
||||
### Secret management
|
||||
|
||||
- File:
|
||||
- ~/data/config/secrets/.secret.yaml
|
||||
|
||||
- Edit `.secret.yaml` with `edit_secret.sh`
|
||||
|
||||
```yaml
|
||||
# ~/data/config/secrets/.secret.yaml
|
||||
# CODE-SERVER:
|
||||
CODESERVER_SSH_KEY: SSH_KEY_VALUE
|
||||
```
|
||||
|
||||
```bash
|
||||
|
||||
ssh-keygen -t ed25519 -f /run/user/$UID/codeserver -C "code-server@ilnmors.internal"
|
||||
> [enter] # no passphrase
|
||||
|
||||
cat /run/user/$UID/codeserver
|
||||
> Private key value
|
||||
# add in .secret.yaml as CODESERVER_SSH_KEY
|
||||
cat /run/user/$UID/codeserver.pub
|
||||
> Public key value
|
||||
# add in .secret.yaml as a annotation
|
||||
|
||||
rm -rf /run/user/$UID/codeserver*
|
||||
|
||||
extract_secret.sh .secret.yaml -f CODESERVER_SSH_KEY | podman secret create CODESERVER_SSH_KEY -
|
||||
```
|
||||
|
||||
### Preparation
|
||||
|
||||
#### Create directory for container
|
||||
|
||||
```bash
|
||||
mkdir -p ~/data/containers/code-server
|
||||
chmod -R 700 ~/data/containers/code-server
|
||||
setfacl -m d:g::0 ~/data/containers/code-server
|
||||
setfacl -m d:o::0 ~/data/containers/code-server
|
||||
setfacl -m u:dev:rwx ~/data/containers/code-server
|
||||
setfacl -m u:100999:rwx ~/data/containers/code-server
|
||||
setfacl -d -m u:dev:rwx ~/data/containers/code-server
|
||||
setfacl -d -m u:100999:rwx ~/data/containers/code-server
|
||||
mkdir -p ~/data/containers/code-server/{config,local,ssh,workspace}
|
||||
echo $PUBLIC_SSH_KEY_VALUE > ~/data/containers/code-server/ssh/id_codeserver.pub
|
||||
|
||||
```
|
||||
|
||||
> Code-Server container executes as 1000:1000(coder:coder) permission in container. It is mapped host's 100999. Therefore, directories have to have ACL via `setfacl`
|
||||
|
||||
#### Add new domain in BIND
|
||||
|
||||
Following [here](../06_network/06_03_net_bind.md).
|
||||
|
||||
- net server
|
||||
- file: ~/data/containers/bind/lib/db.ilnmors.internal
|
||||
|
||||
```ini
|
||||
# ...
|
||||
code-server IN CNAME auth.ilnmors.internal.
|
||||
# ...
|
||||
```
|
||||
|
||||
```bash
|
||||
# Adguard container has Requires=bind.service. When it restarted, then Adguard also restarted.
|
||||
systemctl --user restart bind
|
||||
```
|
||||
|
||||
#### Add new rules in Caddy and authelia
|
||||
|
||||
##### caddy-auth
|
||||
|
||||
- auth server
|
||||
- file: ~/data/containers/caddy-auth/etc/Caddyfile
|
||||
|
||||
```ini
|
||||
# ...
|
||||
code-server.ilnmors.internal {
|
||||
import private_tls
|
||||
import crowdsec_log
|
||||
route {
|
||||
crowdsec
|
||||
import apply_forward_auth https://dev.ilnmors.internal
|
||||
}
|
||||
# ...
|
||||
```
|
||||
|
||||
##### caddy-dev
|
||||
|
||||
- dev server
|
||||
- file: ~/data/containers/caddy-dev/etc/Caddyfile
|
||||
|
||||
```ini
|
||||
# ...
|
||||
dev.ilnmors.internal {
|
||||
import private_tls
|
||||
# ...
|
||||
@code-server header X-Forwarded-Host code-server.ilnmors.internal
|
||||
# ...
|
||||
route @code-server {
|
||||
reverse_proxy host.containers.internal:8000 {
|
||||
# Sidecar caddy's Caddyfile should change `Host` header as original `Host` value from `X-Forwarded-Host` to prevent websocket problem.
|
||||
header_up Host {http.request.header.X-Forwarded-Host}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
##### authelia
|
||||
|
||||
```yaml
|
||||
# configuration.yml
|
||||
|
||||
# ...
|
||||
# Access control configuration
|
||||
access_control:
|
||||
default_policy: 'deny'
|
||||
rules:
|
||||
# authelia portal
|
||||
- domain: 'authelia.ilnmors.internal'
|
||||
policy: 'bypass'
|
||||
- domain: 'authelia.ilnmors.com'
|
||||
policy: 'bypass'
|
||||
- domain: 'dev-test.ilnmors.com'
|
||||
policy: 'one_factor'
|
||||
# Access control for Forward_Auth
|
||||
subject:
|
||||
- 'group:admins'
|
||||
- domain: 'code-server.ilnmors.internal'
|
||||
policy: 'one_factor'
|
||||
subject:
|
||||
- 'group:admins'
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
#### SSH configuration
|
||||
|
||||
##### Each server
|
||||
|
||||
- file: ~/.ssh/authorized_keys
|
||||
|
||||
```ini
|
||||
# ...
|
||||
Contents of code-server's public key value
|
||||
# ...
|
||||
```
|
||||
|
||||
##### Code-Server container
|
||||
|
||||
- file: ~/data/containers/code-server/ssh/config
|
||||
|
||||
```ini
|
||||
Host vmm
|
||||
HostName 192.168.1.10
|
||||
User vmm
|
||||
IdentityFile /run/secrets/CODESERVER_SSH_KEY
|
||||
|
||||
Host net
|
||||
HostName 192.168.10.11
|
||||
User net
|
||||
IdentityFile /run/secrets/CODESERVER_SSH_KEY
|
||||
|
||||
Host auth
|
||||
HostName 192.168.10.12
|
||||
User auth
|
||||
IdentityFile /run/secrets/CODESERVER_SSH_KEY
|
||||
|
||||
# dev is the server where code-server is located. It needs host.containers.internal
|
||||
Host dev
|
||||
HostName host.containers.internal
|
||||
User dev
|
||||
IdentityFile /run/secrets/CODESERVER_SSH_KEY
|
||||
|
||||
Host app
|
||||
HostName 192.168.10.14
|
||||
User app
|
||||
IdentityFile /run/secrets/CODESERVER_SSH_KEY
|
||||
```
|
||||
|
||||
```bash
|
||||
podman unshare chown 1000:1000 ~/data/containers/code-server/ssh/config
|
||||
```
|
||||
|
||||
### Podman Image
|
||||
|
||||
#### Podman containerfile
|
||||
|
||||
Code-server supports various module for it. Git and ansible will be used in this project.
|
||||
|
||||
- file:
|
||||
- ~/data/config/containers/code-server/containerfile-code-server-4.105.1
|
||||
- ~/data/config/containers/code-server/root_ca.crt
|
||||
|
||||
```containerfile
|
||||
FROM codercom/code-server:4.105.1
|
||||
|
||||
USER root
|
||||
|
||||
RUN export SUDO_FORCE_REMOVE=yes && \
|
||||
apt-get update && \
|
||||
apt-get install -y --no-install-recommends git ansible curl jq age gnupg && \
|
||||
apt-get purge -y --auto-remove sudo && \
|
||||
apt-get clean
|
||||
|
||||
RUN curl -LO https://github.com/getsops/sops/releases/download/v3.11.0/sops-v3.11.0.linux.amd64 && \
|
||||
mv sops-v3.11.0.linux.amd64 /usr/local/bin/sops && \
|
||||
chmod +x /usr/local/bin/sops && \
|
||||
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
COPY ./root_ca.crt /usr/local/share/ca-certificates/root_ca.crt
|
||||
|
||||
RUN update-ca-certificates
|
||||
|
||||
USER coder
|
||||
```
|
||||
|
||||
#### Podman image build
|
||||
|
||||
```bash
|
||||
podman build -t code-server:4.105.1 -f ~/data/config/containers/code-server/containerfile-code-server-4.105.1 . && podman image prune -f
|
||||
# Delete pure code-server
|
||||
```
|
||||
|
||||
### Quadlet
|
||||
|
||||
- File:
|
||||
- ~/data/config/containers/code-server/code-server.container
|
||||
|
||||
```ini
|
||||
# ~/data/config/containers/code-server/code-server.container
|
||||
# ~/.config/containers/systemd/code-server.container
|
||||
[Quadlet]
|
||||
DefaultDependencies=false
|
||||
|
||||
[Unit]
|
||||
Description=Code-Server
|
||||
|
||||
After=caddy-dev.service
|
||||
Wants=caddy-dev.service
|
||||
|
||||
[Container]
|
||||
Image=localhost/code-server:4.105.1
|
||||
|
||||
ContainerName=code-server
|
||||
|
||||
HostName=code-server
|
||||
|
||||
# CrowdSec uses 8080 port
|
||||
PublishPort=8000:8080/tcp
|
||||
|
||||
Volume=%h/data/containers/code-server/workspace:/home/coder/workspace:rw
|
||||
Volume=%h/data/containers/code-server/config:/home/coder/.config:rw
|
||||
Volume=%h/data/containers/code-server/local:/home/coder/.local:rw
|
||||
Volume=%h/data/containers/code-server/ssh:/home/coder/.ssh:rw
|
||||
|
||||
Environment="TZ=Asia/Seoul"
|
||||
# when you needs root permission, you have to access the container via dev server's command 'poman exec -it code-server'
|
||||
|
||||
Secret=CODESERVER_SSH_KEY,target=/run/secrets/CODESERVER_SSH_KEY
|
||||
|
||||
Label=diun.enable=true
|
||||
Label=diun.watch_repo=true
|
||||
# This label need configuration on `diun.yml`
|
||||
Label=diun.regopt=code-server-source
|
||||
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
```
|
||||
|
||||
#### Create systemd `.service` file
|
||||
|
||||
```bash
|
||||
# linger has to be activated
|
||||
ln -s ~/data/config/containers/code-server/code-server.container ~/.config/containers/systemd/code-server.container
|
||||
|
||||
systemctl --user daemon-reload
|
||||
```
|
||||
|
||||
#### Enable and start service
|
||||
|
||||
```bash
|
||||
systemctl --user start code-server.service
|
||||
```
|
||||
|
||||
#### Disable password
|
||||
|
||||
```bash
|
||||
nano ~/data/containers/code-server/config/code-server/config.yaml
|
||||
# bind-addr: 127.0.0.1:8080
|
||||
# auth: none <- edit this as `none` from `password`
|
||||
# password: <- remove this part
|
||||
# cert: false
|
||||
```
|
||||
|
||||
|
||||
#### Set default workspace
|
||||
|
||||
- Setting:Profile:Default:Folders&workspaces
|
||||
- Add Folder > /home/coder/workspace
|
||||
- Setting:Settings:Workbench:Settings Editor
|
||||
- Terminal > Integrated: Gpu Acceleration: off
|
||||
- Edit in settings.json
|
||||
|
||||
```json
|
||||
{
|
||||
|
||||
"workbench.settings.applyToAllProfiles": [
|
||||
|
||||
],
|
||||
|
||||
"workbench.colorCustomizations": {
|
||||
|
||||
"terminal.background": "#0C0C0C",
|
||||
|
||||
"terminal.foreground": "#CCCCCC"
|
||||
|
||||
},
|
||||
|
||||
"files.associations": {
|
||||
|
||||
"*.container": "ini",
|
||||
|
||||
"*.service": "ini",
|
||||
|
||||
"*.timer": "ini",
|
||||
|
||||
"containerfile*": "dockerfile"
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
#### Verification
|
||||
@@ -0,0 +1,253 @@
|
||||
Tags: #os, #configuration, #application, #virtualization
|
||||
|
||||
## Preparation
|
||||
|
||||
### Set DHCP reservation and DNS record
|
||||
|
||||
#### Set DHCP reservation on KEA DHCP in OPNsense
|
||||
|
||||
Following [here](05_07_opnsense_kea.md)
|
||||
|
||||
- Services:Kea DHCP:Kea DHCPv4:Reservations - \[+\]
|
||||
- Subnet: 192.168.10.0/24
|
||||
- IP address: 192.168.10.14
|
||||
- MAC address: 0A:49:6E:4D:04:00
|
||||
- Hostname: app
|
||||
- Description: app
|
||||
- `save`
|
||||
|
||||
#### Set DNS records in BIND
|
||||
|
||||
Following [here](../06_network/06_03_net_bind.md).
|
||||
|
||||
- net server
|
||||
- file:
|
||||
- ~/data/containers/bind/lib/db.ilnmors.internal
|
||||
- ~/data/containers/bind/lib/db.10.168.192.in-addr.arpa
|
||||
|
||||
```ini
|
||||
# db.ilnmors.internal
|
||||
# ...
|
||||
app IN A 192.168.10.14
|
||||
# ...
|
||||
# db.10.168.192.in-addr.arpa
|
||||
# ...
|
||||
14 IN PTR app.ilnmors.internal.
|
||||
# ...
|
||||
```
|
||||
|
||||
### Create VM template
|
||||
|
||||
```bash
|
||||
virt-install \
|
||||
--boot uefi \
|
||||
--name app \
|
||||
--os-variant debian13 \
|
||||
--vcpus 4 \
|
||||
--memory 12288 \
|
||||
--location /home/vmm/data/vms/images/debian-13.0.0-amd64-netinst.iso \ # For serial installing, use `--location` instead of `--cdrom`
|
||||
--disk pool=vm-images,size=258,format=qcow2,discard=unmap \
|
||||
--network network=ovs-lan-net,portgroup=vlan10-access,model=virtio,mac=0A:49:6E:4D:04:00 \ # Use designated ovs port group
|
||||
--graphics none \
|
||||
--console pty,target_type=serial \
|
||||
--extra-args "console=ttyS0,115200"
|
||||
# After enter this command, then the console start automatically
|
||||
# Remove all annotation before you make the sh file.
|
||||
```
|
||||
|
||||
### Debian installing
|
||||
|
||||
- Following [here](../03_common/03_01_debian_configuration.md) to install Debian.
|
||||
- Debian installer supports serial mode regardless getty@ttyS0 service is enabled or not.
|
||||
- Following [here](../03_common/03_02_iptables.mc) to set iptables.
|
||||
- Following [here](../03_common/03_04_crowdsec.md) to set CrowdSec
|
||||
|
||||
#### Serial console setting
|
||||
|
||||
After installation, use `ctrl + ]` to exit console. Before setting getty@ttyS0, you can't use serial console to access VM. Therefore, use IP address set on installation, and connect net server via ssh first, following the step to enable the getty.
|
||||
|
||||
#### btrfs RAID setting
|
||||
|
||||
Following [here](03_06_btrfs.md) how to use btrfs
|
||||
|
||||
- directory: /home/app/hdd
|
||||
|
||||
```bash
|
||||
# Make the directory, RAID partition will be mounted
|
||||
mkdir /home/app/hdd
|
||||
|
||||
# check btrfs-progs package
|
||||
sudo apt list --installed | grep btrfs-progs
|
||||
# btrfs-progs/stable,now 6.14-1 amd64 [installed]
|
||||
|
||||
# Check the disk status
|
||||
lsblk -o NAME,PTTYPE,FSTYPE,SIZE,MOUNTPOINT
|
||||
# - /dev/sda: Physical slot 2
|
||||
# - /dev/sdb: Physical slot 1
|
||||
# - /dev/sdc: Physical slot 4
|
||||
# - /dev/sdd: Physical slot 3
|
||||
# If you want to manage the partition or disk, then use fsdisk.
|
||||
|
||||
sudo fdisk "$DIVICE_PATH"
|
||||
> n # create the new parition
|
||||
> 1 # Partition number
|
||||
> Default # First Sector
|
||||
> Default # Last Sectort
|
||||
> w # write the new partition
|
||||
|
||||
lsblk -o NAME,PTTYPE,FSTYPE,SIZE,MOUNTPOINT
|
||||
# - /dev/sda1: partition of slot 2
|
||||
# - /dev/sdb1: partition of slot 1
|
||||
# - /dev/sdc1: partition of slot 4
|
||||
# - /dev/sdd1: partition of slot 3
|
||||
|
||||
# btrfs RAID10 volume creation
|
||||
# -d: the way data store, -m: the way metadata store, -L: create label
|
||||
sudo mkfs.btrfs -d raid10 -m raid10 -L hdd /dev/sda1 /dev/sdb1 /dev/sdc1 /dev/sdd1
|
||||
|
||||
# check the RAID10 volume, it shows label and uuid.
|
||||
sudo btrfs filesystem show
|
||||
lsblk -f
|
||||
|
||||
# Mount RAID10 volume permanently
|
||||
sudo nano /etc/fstab
|
||||
# # btrfs RAID10 storage pool; mount option, compress=zstd: realtime compression. autodefrag: conduct auto defragmentation
|
||||
# LABEL=hdd /home/app/hdd btrfs defaults,compress=zstd,autodefrag 0 0
|
||||
|
||||
# release the fs
|
||||
sudo systemctl daemon-reload
|
||||
sudo mount -a
|
||||
|
||||
# check the mount
|
||||
df -h
|
||||
/dev/sda1 3.7T /home/app/hdd
|
||||
|
||||
# Scrubbing btrfs
|
||||
sudo btrfs scrub start /home/app/hdd
|
||||
sudo btrfs scrub status /home/app/hdd
|
||||
```
|
||||
> btrfs RAID doesn't use fixed hdds pair. It uses flexible chunck unit data management. Therefore user doesn't have to know about which disks are the pair, just change the disk which is broken.
|
||||
|
||||
#### btrfs scrub on systemd
|
||||
|
||||
File:
|
||||
- ~/data/config/services/btrfs-scrub/btrfs-scrub.service
|
||||
- ~/data/config/services/btrfs-scrub/btrfs-scrub.timer
|
||||
|
||||
```ini
|
||||
# ~/data/config/services/btrfs-scrub/btrfs-scrub.service
|
||||
# /etc/systemd/system
|
||||
[Unit]
|
||||
Description=BTRFS Scrub for /home/app/hdd
|
||||
After=home-app-hdd.mount
|
||||
Wants=home-app-hdd.mount
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=btrfs scrub start /home/app/hdd
|
||||
|
||||
Nice=19
|
||||
IOSchedulingClass=idle
|
||||
# Nice: CPU priority; -20: highest, 0: default, 19: lowest
|
||||
# IOSchedulingClass: Disk priority; realtime: highest, best-effort: default, idle: lowest
|
||||
```
|
||||
|
||||
```ini
|
||||
# ~/data/config/services/btrfs-scrub/btrfs-scrub.timer
|
||||
# /etc/systemd/system
|
||||
[Unit]
|
||||
Description=Run BTRFS scrub for /home/app/hdd monthly
|
||||
|
||||
[Timer]
|
||||
OnCalendar=*-*-01 03:00:00
|
||||
Persistent=true
|
||||
# Persistent=true: If the service couldn't run because of some reasons, it execute the service immediately when it is possible
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
```
|
||||
|
||||
```bash
|
||||
|
||||
sudo ln -s ~/data/config/services/btrfs-scrub/btrfs-scrub.service /etc/systemd/system/btrfs-scrub.service
|
||||
|
||||
sudo ln -s ~/data/config/services/btrfs-scrub/btrfs-scrub.timer /etc/systemd/system/btrfs-scrub.timer
|
||||
|
||||
sudo systemctl daemon-reload
|
||||
|
||||
sudo systemctl enable --now btrfs-scrub.timer
|
||||
```
|
||||
### Modify VM template settings
|
||||
|
||||
After getty setting, shutdown app vm with `shutdown` in VM or `sudo virsh shutdown app` in hypervisor to turn off vm first.
|
||||
|
||||
```bash
|
||||
virsh edit app
|
||||
```
|
||||
|
||||
```xml
|
||||
<!-- app -->
|
||||
...
|
||||
</vcpu>
|
||||
<cputune>
|
||||
<shares>2048</shares>
|
||||
</cputune>
|
||||
<!-- cpu priority - 1024: default/2048: high/512: low -->
|
||||
|
||||
<!--
|
||||
<disk type='file' device='cdrom'>
|
||||
...
|
||||
</disk>
|
||||
# Remove booting disk
|
||||
-->
|
||||
```
|
||||
|
||||
```bash
|
||||
virsh dumpxml app > ~/data/config/vms/dumps/app.xml
|
||||
virsh start app && virsh console app
|
||||
# Start app server with console
|
||||
```
|
||||
|
||||
### Common setting
|
||||
|
||||
- app.service
|
||||
|
||||
```ini
|
||||
# ~/data/config/services/app.service
|
||||
# ~/.config/systemd/user/app.service
|
||||
[Unit]
|
||||
Description=app Auto Booting
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
Requires=opnsense.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
|
||||
# Maintain status as active
|
||||
RemainAfterExit=yes
|
||||
|
||||
# CrowdSec should be set
|
||||
ExecStartPre=%h/data/config/scripts/wait-for-it.sh 192.168.10.1:8080 -t 0
|
||||
ExecStartPre=%h/data/config/scripts/wait-for-it.sh 192.168.10.11:53 -t 0
|
||||
ExecStartPre=%h/data/config/scripts/wait-for-it.sh 192.168.10.12:9000 -t 0
|
||||
|
||||
ExecStartPre=/bin/bash -c "sleep 15"
|
||||
|
||||
# Run the service
|
||||
ExecStart=/usr/bin/virsh -c qemu:///system start app
|
||||
|
||||
# Stop the service
|
||||
ExecStop=/usr/bin/virsh -c qemu:///system shutdown app
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
```
|
||||
|
||||
```bash
|
||||
ln -s ~/data/config/services/app.service ~/.config/systemd/user/app.service
|
||||
|
||||
systemctl --user daemon-reload
|
||||
systemctl --user enable app.service
|
||||
systemctl --user start app.service
|
||||
```
|
||||
@@ -0,0 +1,360 @@
|
||||
# Console client
|
||||
|
||||
Tags: #os, #windows, #virtualization, #wsl, #vscode
|
||||
|
||||
## Preparation
|
||||
|
||||
### WSL
|
||||
|
||||
#### WSL command
|
||||
|
||||
Run the commands in PowerShell or CMD. Installation and uninstallation process needs admin privileges.
|
||||
|
||||
```PowerShell
|
||||
# --- Install and setup ---
|
||||
# Activate WSL (First time only)
|
||||
wsl --install
|
||||
|
||||
# Install specific OS
|
||||
wsl --install -d Debian
|
||||
|
||||
# Check the list
|
||||
wsl --list --online
|
||||
|
||||
# Check the version
|
||||
wsl -l -v
|
||||
|
||||
# --- Run and manage ---
|
||||
# Run WSL
|
||||
wsl -d Debian # -u root # run with root
|
||||
|
||||
# Shutdown WSL
|
||||
# This is needed when the configuration is changed
|
||||
wsl --shutdown
|
||||
|
||||
# Shutdown specific version
|
||||
wsl --terminate Debian
|
||||
|
||||
# --- Backup and restore ---
|
||||
|
||||
# Backup WSL
|
||||
wsl --export Debian C:\backups\wsl.tar
|
||||
# Import WSL
|
||||
wsl --import Debian C:\WSL\Debian C:\backups\wsl.tar
|
||||
|
||||
# Open the linux directory on windows explorer
|
||||
# bash
|
||||
# explorer.exe .
|
||||
# Windows explorer
|
||||
# \\wsl$ on the windows explorer
|
||||
|
||||
# --- Reset or inactivate ---
|
||||
# Reset the specific version
|
||||
wsl --unregister Debian
|
||||
|
||||
# Inactive WSL
|
||||
wsl --uninstall
|
||||
```
|
||||
|
||||
#### WSL configuration
|
||||
|
||||
##### Installation
|
||||
|
||||
```PowerShell
|
||||
# Activate WSL and install Debian
|
||||
wsl --install -d Debian
|
||||
# Enter new UNIX username: debian
|
||||
# Enter new password: debian
|
||||
|
||||
```
|
||||
|
||||
##### Configuration
|
||||
|
||||
- `Win`:Windows Linux Subsystem Configuration \(GUI\)
|
||||
- Processor and memory
|
||||
- Processor: 4
|
||||
- Memory: 4096MB
|
||||
- Swap: 0
|
||||
- Filesystem
|
||||
- Basic VHD: 32768MB
|
||||
- Networking
|
||||
- Mode: Mirrored
|
||||
|
||||
#### WSL Start
|
||||
|
||||
```PowerShell
|
||||
# Start WSL
|
||||
wsl -d Debian
|
||||
# User and group configuration
|
||||
sudo groupadd -g 2000 svadmins
|
||||
sudo useradd -u 2999 -g svadmins -G sudo -c "Console Client" -m -d /home/console -s /bin/bash console
|
||||
sudo passwd console
|
||||
# New password: random string
|
||||
exit
|
||||
|
||||
# PowerShell
|
||||
wsl --shutdown
|
||||
wsl -d Debian -u console
|
||||
|
||||
# Delete default account
|
||||
sudo userdel -r debian
|
||||
|
||||
# Set default user
|
||||
sudo nano /etc/wsl.conf
|
||||
# ...
|
||||
# [user]
|
||||
# default=console
|
||||
exit
|
||||
|
||||
# PowerShell
|
||||
wsl --shutdown
|
||||
wsl -d Debian
|
||||
# Check `console` login
|
||||
|
||||
# Create the directory for VS Code
|
||||
mkdir workspace && chmod 700 workspace
|
||||
```
|
||||
|
||||
### VS Code
|
||||
|
||||
#### Installation
|
||||
|
||||
- Site: https://code.visualstudio.com/
|
||||
- Download for Windows
|
||||
- Execute the installation file
|
||||
|
||||
#### Configuration
|
||||
|
||||
- Extensions\(`Ctrl` + `shift` + `x`\):WSL
|
||||
- Install WSL by Microsoft
|
||||
- Remote Explorer:Debian:Connect in Current Windows
|
||||
- `Ctrl` + `k` and `Ctrl` + `t` for theme
|
||||
- Dark Modern
|
||||
- `Ctrl` + `k` and `Ctrl` + `o` for `open folder`
|
||||
- /home/console/workspace/
|
||||
- Do you trust the authors of the files in this folder - `Yes, I trust the authors`
|
||||
- `Ctrl` + `Shift` + `` ` `` for `open terminal`
|
||||
|
||||
## Bastion host
|
||||
|
||||
### Directory structures
|
||||
|
||||
Use `mkdir` to make these directories.
|
||||
- ~/workspace/homelab/data/
|
||||
- utils
|
||||
- common - wait-for-it.sh, sops, etc...
|
||||
- \[server_name\]/\[bin_name\] - ddns, init_db, etc ...
|
||||
- servers
|
||||
- os/\[iso or img files for installation\]
|
||||
- \[server_name\]/\[service_name; iptables, interface, ssh, vfio, etc..\] - rules.v4, sshd_config, etc...
|
||||
- services
|
||||
- \[server_name\]/\[services_name\]
|
||||
- *.containers or *.service (systemd files)
|
||||
- config - services configuration (named.conf, etc; !No live data files like DB file or media file. Only configuration files based on text or binary files.)
|
||||
- secrets - secret_scripts, secret.yaml (central secret management)
|
||||
- ~/workspace/homelab/docs
|
||||
- library
|
||||
- archives
|
||||
- before_bastion_host/current_documents_and_directories
|
||||
- references
|
||||
- techs
|
||||
- current_common_documents
|
||||
- theories
|
||||
- current_theory_documents
|
||||
- images
|
||||
- media
|
||||
- etc.
|
||||
- plans
|
||||
- plan.md
|
||||
- milestone.md
|
||||
- infrastructures
|
||||
- common
|
||||
- debian_configuration.md (OS, network, uid/gid, packages)
|
||||
- deployment.md
|
||||
- security_policies.md (iptables, crowdsec)
|
||||
- data_polices.md (storage, backup, database)
|
||||
- \[server_name\]
|
||||
- \[server_name\].md - virtual hardware, security, services, etc...
|
||||
- \[services_name\].md
|
||||
|
||||
### Packages
|
||||
|
||||
- External binary packages are located in here
|
||||
- ~/workspace/homelab/data/bin/common
|
||||
|
||||
```bash
|
||||
sudo apt update && sudo apt upgrade
|
||||
# Packages from repository
|
||||
sudo apt install gnupg acl curl jq age git openssh-client
|
||||
|
||||
# Git config
|
||||
git config --global user.name "il"
|
||||
git config --global user.email "il@ilnmors.internal"
|
||||
|
||||
# Sops
|
||||
## Sops for amd processor (N150)
|
||||
curl -LO https://github.com/getsops/sops/releases/download/v3.11.0/sops-v3.11.0.linux.amd64
|
||||
|
||||
## Sops for arm processor (Snapdragon Plus)
|
||||
curl -LO https://github.com/getsops/sops/releases/download/v3.11.0/sops-v3.11.0.linux.arm64
|
||||
|
||||
mkdir -p ~/workspace/homelab/data/bin/common && chmod 700 ~/workspace/homelab/data/bin/common
|
||||
|
||||
mv sops-v3.11.0.linux.amd64 sops-v3.11.0.linux.arm64 ~/workspace/homelab/data/bin/common/
|
||||
|
||||
sudo cp ~/workspace/homelab/data/bin/common/sops-v3.11.0.linux.arm64 /usr/local/bin/sops
|
||||
|
||||
sudo chmod +x /usr/local/bin/sops
|
||||
|
||||
# wait-for-it.sh
|
||||
curl -LO https://github.com/vishnubob/wait-for-it/blob/master/wait-for-it.sh
|
||||
|
||||
mv wait-for-it.sh ~/workspace/homelab/data/bin/common/
|
||||
|
||||
# acme.sh
|
||||
curl -LO https://github.com/acmesh-official/acme.sh/blob/master/acme.sh
|
||||
mv acme.sh ~/workspace/homelab/data/bin/common/
|
||||
```
|
||||
|
||||
### Secret management
|
||||
|
||||
- Files:
|
||||
- ~/workspace/homelab/data/secrets/secret.yaml
|
||||
- ~/workspace/homelab/data/secrets/.sops.yaml
|
||||
- ~/workspace/homelab/data/secrets/age-key.gpg
|
||||
- ~/workspace/homelab/data/secrets/edit_secret.sh
|
||||
- ~/workspace/homelab/data/secrets/extract_secret.sh
|
||||
|
||||
#### Apply the secrets
|
||||
|
||||
- Server: console
|
||||
|
||||
##### Generate and encrypt age key
|
||||
|
||||
|
||||
```bash
|
||||
# Generate the key for sops
|
||||
age-keygen -o ~/workspace/homelab/data/secrets/age-key
|
||||
# # created: 2025-10-17T13:30:00Z
|
||||
# # public key: age1ql3z7h0cfscg......
|
||||
# AGE-SECRET-KEY-1.....
|
||||
|
||||
# Public key is printed when key generated
|
||||
gpg --symmetric age-key && rm age-key
|
||||
> GPG password: password
|
||||
|
||||
nano ~/workspace/homelab/data/secrets/.sops.yaml
|
||||
```
|
||||
|
||||
##### Key value setting for sops
|
||||
|
||||
```yaml
|
||||
# ~/workspace/homelab/data/secrets/.sops.yaml
|
||||
creation_rules:
|
||||
- path_regex: secret\.yaml$
|
||||
age: [public_key value; age~~~]
|
||||
```
|
||||
|
||||
##### Mnagement secret
|
||||
|
||||
```bash
|
||||
# Create secret
|
||||
cd ~/workspace/homelab/data/secrets
|
||||
nano secret.yaml
|
||||
|
||||
# Replace the file as secret file
|
||||
sops --encrypt --in-place secret.yaml
|
||||
|
||||
# edit secret.yaml
|
||||
./edit_secret.sh secret.yaml
|
||||
|
||||
# Create secret files in each server
|
||||
./extract_secret.sh secret.yaml [-n] (-e|-f $ENV) > $TMP_PATH/tmp_secret
|
||||
|
||||
# deploy the tmp_secret to server to /run/user/$UID/filename
|
||||
scp $TMP_PATH/tmp_secret [server]:/run/user/$TARGET_UID/filename
|
||||
|
||||
# `<< 'EOF'` sends string itself
|
||||
# `<< EOF` sends string after interpreting
|
||||
ssh [server] << 'EOF'
|
||||
sudo mv /run/user/$UID/filename /etc/secrets/$UID/secret_file
|
||||
rm -rf /run/user/$UID/filename
|
||||
sudo chown $UID:root /etc/secrets/$UID/secret_file
|
||||
sudo chmod 400 /etc/secrets/$UID/secret_file
|
||||
EOF
|
||||
|
||||
rm -rf $TMP_PATH/tmp_secret
|
||||
|
||||
# Podman secret in each server
|
||||
./extract_secret.sh secret.yaml [-n] -f $ENV | ssh sv "podman secret create $ENV -"
|
||||
```
|
||||
|
||||
#### Usage of podman secret
|
||||
|
||||
```container
|
||||
#...
|
||||
#...
|
||||
[Container]
|
||||
# ..
|
||||
Secret=env,type=env,target=env
|
||||
Secret=app,target=/run/secrets/app
|
||||
```
|
||||
|
||||
### ssh configuration
|
||||
|
||||
#### ssh key gen
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.ssh && chmod 700 ~/.ssh
|
||||
ssh-keygen -t ed25519 -f ~/.ssh/id_console -C "il@ilnmors.internal"
|
||||
|
||||
# Add private key value to ~/workspace/homelab/data/secret/secret.yaml with sops
|
||||
## # console ssh public key:
|
||||
## # ed25519 ~~~~ il@ilnmors.internal
|
||||
## # console ssh private key
|
||||
## CONSOLE_SSH_PRIVATE_KEY: |
|
||||
## ----BEGIN----
|
||||
## ...
|
||||
## ----END----
|
||||
|
||||
sudo mkdir -p /etc/secrets/2999 # $UID of `console`
|
||||
sudo chown root:root /etc/secrets && sudo chmod 711 /etc/secrets
|
||||
sudo chown console:root /etc/secrets/2999 && sudo chmod 500 /etc/secrets/2999
|
||||
sudo mv ~/.ssh/id_console /etc/secrets/2999/ && sudo chown console:root /etc/secrets/2999/id_console && sudo chmod 400 /etc/secrets/2999/id_console
|
||||
```
|
||||
|
||||
#### ssh key config
|
||||
|
||||
```ini
|
||||
# ~/.ssh/config
|
||||
|
||||
Host vmm
|
||||
HostName [vmm ip from ncpa.cpl's temporary dhcp ip address]
|
||||
User vmm
|
||||
IdentityFile /etc/secrets/2999/id_console
|
||||
|
||||
# Host vmm
|
||||
# HostName 192.168.10.10
|
||||
# User vmm
|
||||
# IdentityFile /etc/secrets/2999/id_console
|
||||
|
||||
# Host net
|
||||
# HostName 192.168.10.11
|
||||
# User net
|
||||
# IdentityFile /etc/secrets/2999/id_console
|
||||
|
||||
# Host auth
|
||||
# HostName 192.168.10.12
|
||||
# User auth
|
||||
# IdentityFile /etc/secrets/2999/id_console
|
||||
|
||||
# Host dev
|
||||
# HostName 192.168.10.13
|
||||
# User dev
|
||||
# IdentityFile /etc/secrets/2999/id_console
|
||||
|
||||
# Host app
|
||||
# HostName 192.168.10.14
|
||||
# User app
|
||||
# IdentityFile /etc/secrets/2999/id_console
|
||||
```
|
||||
@@ -0,0 +1,452 @@
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
# edit_secret.sh /path/of/secret
|
||||
|
||||
|
||||
|
||||
set -e
|
||||
|
||||
|
||||
|
||||
KEY_PATH="$HOME/workspace/homelab/data/secrets"
|
||||
|
||||
TMP_PATH="/run/user/$UID"
|
||||
|
||||
SECRET_FILE="$1"
|
||||
|
||||
|
||||
|
||||
# Usage function
|
||||
|
||||
usage() {
|
||||
|
||||
echo "Usage: $0 \"/path/of/secret/file\"" >&2
|
||||
|
||||
exit 1
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
# log function
|
||||
|
||||
log()
|
||||
|
||||
{
|
||||
|
||||
local text="$1"
|
||||
|
||||
echo -e "$(date "+%Y-%m-%d %H:%M:%S"): [edit_script] $text" >&2
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
# Secret file check
|
||||
|
||||
if [ -z "$SECRET_FILE" -o ! -f "$SECRET_FILE" ]; then
|
||||
|
||||
log "Error: Secret file path is needed"
|
||||
|
||||
usage
|
||||
|
||||
fi
|
||||
|
||||
|
||||
|
||||
# age-key file check
|
||||
|
||||
if [ ! -f "$KEY_PATH/age-key.gpg" ]; then
|
||||
|
||||
log "Error: There is no key file"
|
||||
|
||||
exit 1
|
||||
|
||||
fi
|
||||
|
||||
|
||||
|
||||
# Dependency check
|
||||
|
||||
if ! command -v sops >/dev/null; then
|
||||
|
||||
log "Error: sops package is needed"
|
||||
|
||||
exit
|
||||
|
||||
fi
|
||||
|
||||
|
||||
|
||||
if ! command -v gpg >/dev/null; then
|
||||
|
||||
log "Error: gnupg package is needed"
|
||||
|
||||
exit
|
||||
|
||||
fi
|
||||
|
||||
|
||||
|
||||
|
||||
# Delete password file after script certainly
|
||||
|
||||
cleanup() {
|
||||
|
||||
if [ -f "$TMP_PATH/age-key" ]; then
|
||||
|
||||
log "Notice: age-key was deleted"
|
||||
|
||||
rm -f "$TMP_PATH/age-key"
|
||||
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
trap cleanup EXIT
|
||||
|
||||
|
||||
|
||||
|
||||
# Get GPG password from prompt
|
||||
|
||||
echo -n "Enter GPG passphrase: " >&2
|
||||
|
||||
read -s GPG_PASSPHRASE
|
||||
|
||||
echo "" >&2
|
||||
|
||||
|
||||
|
||||
# Decrypt age-key on memory
|
||||
|
||||
echo "$GPG_PASSPHRASE" | gpg --batch --yes --passphrase-fd 0 \
|
||||
|
||||
--output "$TMP_PATH/age-key" \
|
||||
|
||||
--decrypt "$KEY_PATH/age-key.gpg" && \
|
||||
|
||||
chmod 600 "$TMP_PATH/age-key"
|
||||
|
||||
|
||||
|
||||
unset GPG_PASSPHRASE
|
||||
|
||||
|
||||
|
||||
# Check the decrypted key on memory
|
||||
|
||||
if [ ! -f "$TMP_PATH/age-key" ]; then
|
||||
|
||||
log "Error: Decrypted key file does not exist"
|
||||
|
||||
exit 1
|
||||
|
||||
fi
|
||||
|
||||
|
||||
|
||||
# kill the gpg session
|
||||
|
||||
gpgconf --kill gpg-agent
|
||||
|
||||
|
||||
|
||||
# Open sops editor
|
||||
|
||||
SOPS_AGE_KEY_FILE="$TMP_PATH/age-key" sops "$SECRET_FILE"
|
||||
|
||||
rm -f "$TMP_PATH/age-key" >&2
|
||||
|
||||
|
||||
|
||||
exit 0
|
||||
```
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
# extract_secret.sh /path/of/secret [-n] (-f|-e <value>)
|
||||
|
||||
|
||||
|
||||
set -e
|
||||
|
||||
|
||||
|
||||
KEY_PATH="$HOME/workspace/homelab/data/secrets"
|
||||
|
||||
TMP_PATH="/run/user/$UID"
|
||||
|
||||
SECRET_FILE=$1
|
||||
|
||||
VALUE=""
|
||||
|
||||
TYPE=""
|
||||
|
||||
NEWLINE="true"
|
||||
|
||||
|
||||
|
||||
# Remove $1 and shift $(n-1) < $n
|
||||
|
||||
shift
|
||||
|
||||
|
||||
|
||||
# usage() function
|
||||
|
||||
usage() {
|
||||
|
||||
echo "Usage: $0 \"/path/of/secret/file\" [-n] (-f|-e \"yaml section name\")" >&2
|
||||
|
||||
echo "-n: remove the newline" >&2
|
||||
|
||||
echo "-f <type name>: Print secret file" >&2
|
||||
|
||||
echo "-e <type name>: Print secret env file" >&2
|
||||
|
||||
exit 1
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
# log() function
|
||||
|
||||
log()
|
||||
|
||||
{
|
||||
|
||||
local text="$1"
|
||||
|
||||
echo -e "$(date "+%Y-%m-%d %H:%M:%S"): [extract_script] $text" >&2
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
while getopts "f:e:n" opt; do
|
||||
|
||||
case $opt in
|
||||
|
||||
f)
|
||||
|
||||
VALUE="$OPTARG"
|
||||
|
||||
TYPE="FILE"
|
||||
|
||||
;;
|
||||
|
||||
e)
|
||||
|
||||
VALUE="$OPTARG"
|
||||
|
||||
TYPE="ENV"
|
||||
|
||||
;;
|
||||
|
||||
n)
|
||||
|
||||
NEWLINE="false"
|
||||
|
||||
;;
|
||||
|
||||
\?) # unknown options
|
||||
|
||||
log "Invalid option: -$OPTARG"
|
||||
|
||||
usage
|
||||
|
||||
;;
|
||||
|
||||
:) # parameter required option
|
||||
|
||||
log "Option -$OPTARG requires an argument."
|
||||
|
||||
usage
|
||||
|
||||
;;
|
||||
|
||||
esac
|
||||
|
||||
done
|
||||
|
||||
|
||||
|
||||
# Get option and move to parameters
|
||||
|
||||
shift $((OPTIND - 1))
|
||||
|
||||
|
||||
|
||||
# Check necessary options
|
||||
|
||||
if [ -z "$SECRET_FILE" -o ! -f "$SECRET_FILE" ]; then
|
||||
|
||||
log "Error: secret file path is required"
|
||||
|
||||
usage
|
||||
|
||||
fi
|
||||
|
||||
|
||||
|
||||
if [ -z "$TYPE" ]; then
|
||||
|
||||
log "Error: -f or -e option requires"
|
||||
|
||||
usage
|
||||
|
||||
fi
|
||||
|
||||
|
||||
|
||||
# age-key file check
|
||||
|
||||
if [ ! -f "$KEY_PATH/age-key.gpg" ]; then
|
||||
|
||||
log "Error: There is no key file"
|
||||
|
||||
exit 1
|
||||
|
||||
fi
|
||||
|
||||
|
||||
|
||||
# Dependency check
|
||||
|
||||
if ! command -v sops >/dev/null; then
|
||||
|
||||
log "Error: sops package is needed"
|
||||
|
||||
exit
|
||||
|
||||
fi
|
||||
|
||||
|
||||
|
||||
if ! command -v gpg >/dev/null; then
|
||||
|
||||
log "Error: gnupg package is needed"
|
||||
|
||||
exit
|
||||
|
||||
fi
|
||||
|
||||
|
||||
|
||||
|
||||
# Delete password file after script certainly
|
||||
|
||||
cleanup() {
|
||||
|
||||
if [ -f "$TMP_PATH/age-key" ]; then
|
||||
|
||||
log "Notice: age-key was deleted"
|
||||
|
||||
rm -f "$TMP_PATH/age-key"
|
||||
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
trap cleanup EXIT
|
||||
|
||||
|
||||
|
||||
echo -n "Enter GPG passphrase: " >&2
|
||||
|
||||
read -s GPG_PASSPHRASE
|
||||
|
||||
echo "" >&2
|
||||
|
||||
|
||||
|
||||
echo "$GPG_PASSPHRASE" | gpg --batch --yes --passphrase-fd 0 \
|
||||
|
||||
--output "$TMP_PATH/age-key" \
|
||||
|
||||
--decrypt "$KEY_PATH/age-key.gpg" && \
|
||||
|
||||
chmod 600 "$TMP_PATH/age-key"
|
||||
|
||||
|
||||
|
||||
unset GPG_PASSPHRASE
|
||||
|
||||
|
||||
|
||||
if [ ! -f "$TMP_PATH/age-key" ]; then
|
||||
|
||||
log "Error: Decrypted key file does not exist"
|
||||
|
||||
exit 1
|
||||
|
||||
fi
|
||||
|
||||
|
||||
|
||||
gpgconf --kill gpg-agent
|
||||
|
||||
|
||||
|
||||
if [ "$TYPE" == "FILE" ]; then
|
||||
|
||||
if RESULT=$(SOPS_AGE_KEY_FILE="$TMP_PATH/age-key" sops --decrypt --extract "[\"$VALUE\"]" --output-type binary "$SECRET_FILE") ; then
|
||||
|
||||
if [ "$NEWLINE" == "true" ]; then
|
||||
|
||||
echo "$RESULT"
|
||||
|
||||
else
|
||||
|
||||
echo -n "$RESULT"
|
||||
|
||||
fi
|
||||
|
||||
exit 0
|
||||
|
||||
else
|
||||
|
||||
log "Error: SOPS extract error"
|
||||
|
||||
exit 1
|
||||
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
|
||||
|
||||
if [ "$TYPE" == "ENV" ]; then
|
||||
|
||||
if RESULT=$(SOPS_AGE_KEY_FILE="$TMP_PATH/age-key" sops --decrypt --extract "[\"$VALUE\"]" --output-type dotenv "$SECRET_FILE") ; then
|
||||
|
||||
if [ "$NEWLINE" == "true" ]; then
|
||||
|
||||
echo "$RESULT"
|
||||
|
||||
else
|
||||
|
||||
echo -n "$RESULT"
|
||||
|
||||
fi
|
||||
|
||||
exit 0
|
||||
|
||||
else
|
||||
|
||||
log "Error: SOPS extract error"
|
||||
|
||||
exit 1
|
||||
|
||||
fi
|
||||
|
||||
fi
|
||||
```
|
||||
Reference in New Issue
Block a user