feat: CMDB full-stack app - FastAPI + PostgreSQL + React
- PostgreSQL schema: 14 tables, JSONB attributes, audit triggers, soft delete - FastAPI backend: CRUD, search/filter, relationship graph, bulk import, JWT RBAC - React frontend: CI table, detail card, force-graph, dashboard - Seed data: homelab scenario (Proxmox, Mikrotik, VMs, services) - Docker Compose + Kubernetes manifests - 20 backend tests (pytest + httpx)
This commit is contained in:
91
docs/ansible-integration.md
Normal file
91
docs/ansible-integration.md
Normal file
@@ -0,0 +1,91 @@
|
||||
# Ansible + CMDB Integration Guide
|
||||
|
||||
## Overview
|
||||
|
||||
Ansible can auto-discover infrastructure and register it as CIs in the CMDB.
|
||||
|
||||
## Setup
|
||||
|
||||
1. Install `ansible` and `requests` on your control node
|
||||
2. Get a CMDB API token: `curl -X POST .../api/auth/login -d '{"username":"admin","password":"..."}'`
|
||||
3. Set `CMDB_TOKEN` and `CMDB_URL` in your inventory
|
||||
|
||||
## Playbook: Import Hosts
|
||||
|
||||
```yaml
|
||||
# playbooks/cmdb-sync.yml
|
||||
---
|
||||
- name: Sync Ansible inventory to CMDB
|
||||
hosts: all
|
||||
gather_facts: true
|
||||
vars:
|
||||
cmdb_url: "http://cmdb-host:8000/api"
|
||||
cmdb_token: "{{ lookup('env', 'CMDB_TOKEN') }}"
|
||||
|
||||
tasks:
|
||||
- name: Register host as CI
|
||||
uri:
|
||||
url: "{{ cmdb_url }}/ci"
|
||||
method: POST
|
||||
headers:
|
||||
Authorization: "Bearer {{ cmdb_token }}"
|
||||
Content-Type: "application/json"
|
||||
body_format: json
|
||||
body:
|
||||
name: "{{ inventory_hostname }}"
|
||||
ci_type_name: "{% if 'proxmox' in inventory_hostname %}PhysicalServer{% elif 'vm-' in inventory_hostname %}VirtualMachine{% else %}Application{% endif %}"
|
||||
status: active
|
||||
tags: "{{ group_names }}"
|
||||
attributes:
|
||||
os: "{{ ansible_distribution }} {{ ansible_distribution_version }}"
|
||||
cpu_cores: "{{ ansible_processor_vcpus }}"
|
||||
ram_mb: "{{ ansible_memtotal_mb }}"
|
||||
ip: "{{ ansible_default_ipv4.address | default('N/A') }}"
|
||||
kernel: "{{ ansible_kernel }}"
|
||||
hostname: "{{ ansible_hostname }}"
|
||||
status_code: [201, 409]
|
||||
register: cmdb_result
|
||||
|
||||
- name: Show result
|
||||
debug:
|
||||
msg: "{{ inventory_hostname }} → {{ cmdb_result.status }}"
|
||||
```
|
||||
|
||||
## Playbook: Update Software Versions
|
||||
|
||||
```yaml
|
||||
- name: Update software instances in CMDB
|
||||
hosts: all
|
||||
tasks:
|
||||
- name: Get installed packages
|
||||
package_facts:
|
||||
become: true
|
||||
|
||||
- name: Report key packages to CMDB
|
||||
uri:
|
||||
url: "{{ cmdb_url }}/ci/bulk/import"
|
||||
method: POST
|
||||
headers:
|
||||
Authorization: "Bearer {{ cmdb_token }}"
|
||||
body_format: json
|
||||
body:
|
||||
items: >-
|
||||
{{
|
||||
package_facts.packages.keys() | select('search', 'nginx|docker|postgresql|redis|prometheus') |
|
||||
map(attribute='-', {
|
||||
'name': item,
|
||||
'ci_type_name': 'Application',
|
||||
'attributes': {'version': package_facts.packages[item].version}
|
||||
}) | list
|
||||
}}
|
||||
status_code: [201]
|
||||
when: "'nginx' in package_facts.packages or 'docker-ce' in package_facts.packages"
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Idempotent registration** — use 409 handling to update existing CIs
|
||||
2. **Tag with inventory groups** — auto-tag CIs with Ansible group names
|
||||
3. **Schedule via cron** — `0 */6 * * * ansible-playbook cmdb-sync.yml`
|
||||
4. **Use lookup plugins** — `uri` plugin for API calls
|
||||
5. **Store token securely** — use Ansible Vault for `CMDB_TOKEN`
|
||||
132
docs/security.md
Normal file
132
docs/security.md
Normal file
@@ -0,0 +1,132 @@
|
||||
# Security & Operations Guide
|
||||
|
||||
## PostgreSQL Security
|
||||
|
||||
### User Privileges
|
||||
```sql
|
||||
-- Create limited user for the application
|
||||
CREATE USER cmdb_app WITH PASSWORD 'strong_password';
|
||||
GRANT CONNECT ON DATABASE cmdb TO cmdb_app;
|
||||
GRANT USAGE ON SCHEMA public TO cmdb_app;
|
||||
GRANT SELECT, INSERT, UPDATE ON ALL TABLES IN SCHEMA public TO cmdb_app;
|
||||
GRANT USAGE ON ALL SEQUENCES IN SCHEMA public TO cmdb_app;
|
||||
|
||||
-- Read-only user for reporting
|
||||
CREATE USER cmdb_reader WITH PASSWORD 'reader_password';
|
||||
GRANT CONNECT ON DATABASE cmdb TO cmdb_reader;
|
||||
GRANT USAGE ON SCHEMA public TO cmdb_reader;
|
||||
GRANT SELECT ON ALL TABLES IN SCHEMA public TO cmdb_reader;
|
||||
```
|
||||
|
||||
### SSL/TLS
|
||||
```ini
|
||||
# postgresql.conf
|
||||
ssl = on
|
||||
ssl_cert_file = '/etc/ssl/certs/server.crt'
|
||||
ssl_key_file = '/etc/ssl/private/server.key'
|
||||
ssl_min_protocol_version = 'TLSv1.2'
|
||||
```
|
||||
|
||||
```bash
|
||||
# pg_hba.conf — force SSL for remote connections
|
||||
hostssl cmdb cmdb_app 10.0.0.0/24 scram-sha-256
|
||||
```
|
||||
|
||||
## Backup Strategy
|
||||
|
||||
### pg_dump (logical backup)
|
||||
```bash
|
||||
# Daily backup script
|
||||
#!/bin/bash
|
||||
BACKUP_DIR="/var/backups/cmdb"
|
||||
DATE=$(date +%Y%m%d_%H%M%S)
|
||||
pg_dump -h localhost -U cmdb cmdb | gzip > "$BACKUP_DIR/cmdb_$DATE.sql.gz"
|
||||
# Keep 30 days
|
||||
find $BACKUP_DIR -name "*.sql.gz" -mtime +30 -delete
|
||||
```
|
||||
|
||||
### pg_basebackup (physical backup)
|
||||
```bash
|
||||
# For PITR (Point-in-Time Recovery)
|
||||
pg_basebackup -h localhost -U replicator -D /var/lib/cmdb-backup \
|
||||
-Fp -Xs -P -R
|
||||
```
|
||||
|
||||
### pgBackRest (enterprise)
|
||||
```ini
|
||||
# pgbackrest.conf
|
||||
[cmdb]
|
||||
pg1-path=/var/lib/postgresql/data
|
||||
repo1-path=/var/lib/pgbackrest
|
||||
repo1-retention-full=2
|
||||
repo1-retention-diff=7
|
||||
```
|
||||
|
||||
### Recovery
|
||||
```bash
|
||||
# From pg_dump
|
||||
dropdb cmdb && createdb cmdb
|
||||
zcat /var/backups/cmdb/cmdb_20240101_030000.sql.gz | psql -U cmdb cmdb
|
||||
|
||||
# From WAL replay
|
||||
restore_command = 'cp /var/lib/pgbackrest/archive/cmdb/%f %p'
|
||||
recovery_target_time = '2024-01-01 12:00:00'
|
||||
```
|
||||
|
||||
## API Security
|
||||
|
||||
### Rate Limiting
|
||||
Configured in `backend/.env`: `RATE_LIMIT_PER_MINUTE=120`
|
||||
|
||||
### CORS
|
||||
```python
|
||||
# Restrict in production
|
||||
CORS_ORIGINS=["https://cmdb.yourdomain.com"]
|
||||
```
|
||||
|
||||
### JWT Best Practices
|
||||
- Rotate secrets quarterly
|
||||
- Short expiration (60 min default)
|
||||
- Store in HTTP-only cookies for web clients
|
||||
- Validate `exp`, `iss`, `aud` claims
|
||||
|
||||
## Monitoring
|
||||
|
||||
### Prometheus Metrics (add to FastAPI)
|
||||
```python
|
||||
from prometheus_client import Counter, Histogram
|
||||
|
||||
REQUEST_COUNT = Counter('cmdb_requests_total', 'Total requests', ['method', 'endpoint'])
|
||||
REQUEST_LATENCY = Histogram('cmdb_request_latency_seconds', 'Request latency', ['endpoint'])
|
||||
```
|
||||
|
||||
### Health Checks
|
||||
```bash
|
||||
# Backend
|
||||
curl http://localhost:8000/api/health
|
||||
# → {"status": "healthy", "version": "1.0.0"}
|
||||
|
||||
# PostgreSQL
|
||||
pg_isready -h localhost -p 5432 -U cmdb
|
||||
```
|
||||
|
||||
### Log Aggregation
|
||||
```yaml
|
||||
# docker-compose logging
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
```
|
||||
|
||||
## Hardening Checklist
|
||||
|
||||
- [ ] Run containers as non-root
|
||||
- [ ] Use read-only filesystem mounts where possible
|
||||
- [ ] Enable seccomp/AppArmor profiles
|
||||
- [ ] Scan images with Trivy/Snyk
|
||||
- [ ] Rotate database passwords regularly
|
||||
- [ ] Audit changelog table weekly
|
||||
- [ ] Monitor for failed login attempts
|
||||
- [ ] Set up alerting for 5xx errors
|
||||
Reference in New Issue
Block a user