Skip to content

Commit 80c8e59

Browse files
committed
Hardening improvements
Signed-off-by: Scott R. Shinn <scott@atomicorp.com>
1 parent d650d90 commit 80c8e59

5 files changed

Lines changed: 107 additions & 18 deletions

File tree

README.md

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,16 +63,31 @@ Edit `/etc/chelon/chelon.conf`:
6363

6464
```bash
6565
# Server binding
66-
ORACLE_HOST=127.0.0.1
67-
ORACLE_PORT=5050
66+
CHELON_HOST=127.0.0.1
67+
CHELON_PORT=5050
6868

6969
# GPG home directory
7070
GNUPGHOME=/var/lib/chelon/.gnupg
7171

72+
# Logging
7273
# Logging
7374
LOG_LEVEL=INFO
75+
76+
# Transport Security (HTTPS/mTLS)
77+
# Paths to your certificate files:
78+
CHELON_SSL_CERT=/etc/chelon/certs/server.crt
79+
CHELON_SSL_KEY=/etc/chelon/certs/server.key
80+
CHELON_SSL_CA=/etc/chelon/certs/ca.crt
81+
82+
# Enforce Client Certificate Authentication (mTLS)
83+
CHELON_SSL_VERIFY_CLIENT=true
7484
```
7585

86+
> [!IMPORTANT]
87+
> Ensure `/etc/chelon/chelon.conf` is readable ONLY by the `chelon` user (`chmod 600`).
88+
> The service contains sensitive passphrases and will refuse to start if permissions are insecure.
89+
90+
7691
Restart after changes:
7792
```bash
7893
sudo systemctl restart chelon
@@ -145,8 +160,43 @@ sudo journalctl -u chelon -n 100
145160
- Tokens hashed with SHA-256
146161
- Rate limiting prevents abuse
147162
- All operations logged to audit trail
163+
- Rate limiting prevents abuse
164+
- All operations logged to audit trail
165+
166+
## Transport Security (HTTPS/mTLS)
167+
168+
To enable HTTPS and Mutual TLS (mTLS):
169+
170+
1. **Generate Certificates**: Creating a CA, Server, and Client certificate.
171+
```bash
172+
# Create directory for certs
173+
sudo mkdir -p /etc/chelon/certs
174+
175+
# Generate CA
176+
openssl req -x509 -newkey rsa:2048 -keyout ca.key -out ca.crt -days 365 -nodes -subj "/CN=Chelon CA"
177+
178+
# Server Cert
179+
openssl req -newkey rsa:2048 -keyout server.key -out server.csr -nodes -subj "/CN=chelon-server"
180+
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 365
181+
182+
# Client Cert
183+
openssl req -newkey rsa:2048 -keyout client.key -out client.csr -nodes -subj "/CN=build-runner"
184+
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -out client.crt -days 365
185+
186+
# Install
187+
sudo mv *.crt *.key /etc/chelon/certs/
188+
sudo chown -R chelon:chelon /etc/chelon/certs
189+
sudo chmod 600 /etc/chelon/certs/*.key
190+
```
191+
192+
2. **Configure Chelon**: Update `/etc/chelon/chelon.conf` with the paths (see Configuration section).
193+
194+
3. **Client Usage**:
195+
```bash
196+
curl --cert client.crt --key client.key --cacert ca.crt \
197+
https://chelon-server:5050/api/v1/health
198+
```
148199

149-
## File Locations
150200

151201
| Path | Purpose |
152202
|------|---------|

chelon.spec

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ Requires(pre): shadow-utils
2525
# Prevent auto-generated requires for user/group (we create them in %pre)
2626
%global __requires_exclude ^(user|group)\\(chelon\\)$
2727

28+
Provides: user(chelon)
29+
Provides: group(chelon)
30+
2831
%description
2932
Chelon is a secure remote signing service for RPM packages and repository
3033
metadata. Build servers send package hashes to Chelon via HTTPS API and
@@ -81,11 +84,12 @@ chown -R chelon:chelon %{_localstatedir}/lib/%{name} 2>/dev/null || true
8184

8285
%files
8386
%doc README.md
84-
%{_datadir}/%{name}/
87+
%attr(0755, root, root) %{_datadir}/%{name}/
8588
%{_bindir}/chelon-admin
8689
%{_unitdir}/chelon.service
87-
%config(noreplace) %{_sysconfdir}/%{name}/chelon.conf
88-
%dir %{_localstatedir}/lib/%{name}
90+
%config(noreplace) %attr(0600, chelon, chelon) %{_sysconfdir}/%{name}/chelon.conf
91+
%dir %attr(0750, chelon, chelon) %{_localstatedir}/lib/%{name}
92+
%dir %attr(0750, root, chelon) %{_sysconfdir}/%{name}/
8993

9094
%changelog
9195
* Tue Jan 06 2026 Atomicorp <support@atomicorp.com> - 1.0.0-1

server/chelon-service.py

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,14 @@
1919

2020
app = Flask(__name__)
2121

22+
# Setup logging (stdout only - journald will capture it)
23+
logging.basicConfig(
24+
level=logging.INFO,
25+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
26+
handlers=[logging.StreamHandler(sys.stdout)]
27+
)
28+
logger = logging.getLogger(__name__)
29+
2230
# Configuration
2331
CONFIG_FILE = os.environ.get('CHELON_CONFIG', '/etc/chelon/chelon.conf')
2432
DATA_DIR = '/var/lib/chelon'
@@ -28,6 +36,19 @@ def load_config(path):
2836
config = {}
2937
if not os.path.exists(path):
3038
return config
39+
40+
# Security check using stat
41+
try:
42+
st = os.stat(path)
43+
# Check for world access (read/write/execute)
44+
if st.st_mode & 0o007:
45+
logger.critical(f"Config file {path} is world-accessible ({oct(st.st_mode & 0o777)}).")
46+
logger.critical("Please secure it: chmod 600 or 640 " + path)
47+
sys.exit(1)
48+
except OSError as e:
49+
logger.error(f"Error checking config permissions: {e}")
50+
sys.exit(1)
51+
3152
try:
3253
with open(path, 'r') as f:
3354
for line in f:
@@ -38,28 +59,18 @@ def load_config(path):
3859
k, v = line.split('=', 1)
3960
config[k.strip()] = v.strip()
4061
except Exception as e:
41-
print(f"Error loading config: {e}")
62+
logger.error(f"Error loading config: {e}")
4263
return config
4364

4465
# Initialize components
4566
config = load_config(CONFIG_FILE)
4667
# Log config status
47-
lp = config.get('LEGACY_PASSPHRASE')
48-
mp = config.get('MODERN_PASSPHRASE')
49-
print(f"DEBUG: Config loaded. Legacy PP len: {len(lp) if lp else 0}, Modern PP len: {len(mp) if mp else 0}")
68+
logger.info("Configuration loaded successfully")
5069

5170
signing_engine = SigningEngine()
5271
token_auth = TokenAuth(config_file=CONFIG_FILE)
5372
audit_logger = AuditLogger()
5473

55-
# Setup logging (stdout only - journald will capture it)
56-
logging.basicConfig(
57-
level=logging.INFO,
58-
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
59-
handlers=[logging.StreamHandler(sys.stdout)]
60-
)
61-
logger = logging.getLogger(__name__)
62-
6374

6475
def _handle_signing(operation):
6576
"""Common signing logic for both RPMs and repodata"""
@@ -88,6 +99,11 @@ def _handle_signing(operation):
8899
return jsonify({'error': 'Invalid JSON'}), 400
89100

90101
raw_data_b64 = data.get('data')
102+
103+
# DoS Protection: Limit payload size
104+
if raw_data_b64 and len(raw_data_b64) > 10 * 1024 * 1024: # 10MB limit
105+
return jsonify({'error': 'Payload too large (limit 10MB)'}), 413
106+
91107
package_hash = data.get('package_hash')
92108
repodata_hash = data.get('repodata_hash')
93109
key_type = data.get('key_type', 'legacy')

systemd/chelon.service

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,27 @@ RestartSec=10
1717
# Security hardening
1818
NoNewPrivileges=true
1919
PrivateTmp=true
20+
PrivateDevices=yes
2021
ProtectSystem=strict
2122
ProtectHome=true
23+
ProtectKernelTunables=yes
24+
ProtectKernelModules=yes
25+
ProtectControlGroups=yes
2226
ReadWritePaths=/var/lib/chelon
2327

28+
# Network isolation
29+
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
30+
31+
# Capability restriction
32+
CapabilityBoundingSet=
33+
AmbientCapabilities=
34+
RestrictSUIDSGID=yes
35+
36+
# Application sandboxing
37+
LockPersonality=yes
38+
RestrictRealtime=yes
39+
RestrictNamespaces=yes
40+
SystemCallFilter=@system-service
41+
2442
[Install]
2543
WantedBy=multi-user.target

tools/chelon-admin

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ Oracle Admin CLI Tool
44
Manage tokens and view audit logs
55
"""
66

7+
import os
78
import sys
89
import argparse
910
import json

0 commit comments

Comments
 (0)