Skip to content

Commit c57441d

Browse files
committed
AUditing impreovements
Signed-off-by: Scott R. Shinn <scott@atomicorp.com>
1 parent 80c8e59 commit c57441d

3 files changed

Lines changed: 112 additions & 29 deletions

File tree

server/audit.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ def __init__(self, log_dir: str = '/var/lib/chelon'):
2323

2424
def log_signing(self, token_id: str, operation: str, key_used: str,
2525
data_hash: str, success: bool, client_ip: str,
26+
request_id: str = None, latency: float = None,
27+
key_fingerprint: str = None, payload_size: int = None,
2628
error: str = None):
2729
"""
2830
Log a signing operation
@@ -34,14 +36,22 @@ def log_signing(self, token_id: str, operation: str, key_used: str,
3436
data_hash: Hash of data signed
3537
success: Whether operation succeeded
3638
client_ip: Client IP address
39+
request_id: Unique request ID
40+
latency: Processing time in seconds
41+
key_fingerprint: Full GPG key fingerprint
42+
payload_size: Size of payload in bytes
3743
error: Error message if failed
3844
"""
3945
log_entry = {
4046
'timestamp': datetime.now(UTC).isoformat(),
47+
'request_id': request_id,
4148
'token_id': token_id,
4249
'operation': operation,
4350
'key_used': key_used,
51+
'key_fingerprint': key_fingerprint,
4452
'data_hash': data_hash,
53+
'payload_size': payload_size,
54+
'latency': latency,
4555
'success': success,
4656
'client_ip': client_ip
4757
}

server/chelon-service.py

Lines changed: 94 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@
66
import os
77
import sys
88
import json
9+
import uuid
10+
import time
911
import logging
12+
import hashlib
1013
from datetime import datetime, UTC
1114
from flask import Flask, request, jsonify
1215
from pathlib import Path
@@ -74,63 +77,115 @@ def load_config(path):
7477

7578
def _handle_signing(operation):
7679
"""Common signing logic for both RPMs and repodata"""
80+
request_id = str(uuid.uuid4())
81+
start_time = time.time()
82+
7783
# Authenticate request
7884
auth_header = request.headers.get('Authorization')
7985
if not auth_header or not auth_header.startswith('Bearer '):
80-
return jsonify({'error': 'Missing or invalid authorization header'}), 401
86+
logger.warning(f"[{request_id}] Missing or invalid authorization header")
87+
return jsonify({'error': 'Missing or invalid authorization header', 'request_id': request_id}), 401
8188

8289
token = auth_header.split(' ', 1)[1]
8390

8491
try:
8592
token_info = token_auth.validate_token(token)
93+
except ValueError as e:
94+
latency = time.time() - start_time
95+
err_msg = str(e)
96+
status_code = 429 if "Rate limit" in err_msg else 401
97+
98+
logger.warning(f"[{request_id}] Auth failure: {err_msg}")
99+
100+
# Log auth failures (limit audit spam for unauthorized?)
101+
# For rate limits, we definitely want to audit
102+
if status_code == 429 or "Unknown token" not in err_msg:
103+
audit_logger.log_signing(
104+
token_id=getattr(token_auth, 'last_failed_token_id', 'unknown'), # We might not have ID
105+
operation=operation,
106+
key_used=None,
107+
data_hash=None,
108+
success=False,
109+
client_ip=request.remote_addr,
110+
request_id=request_id,
111+
latency=latency,
112+
error=err_msg
113+
)
114+
return jsonify({'error': err_msg, 'request_id': request_id}), status_code
86115
except Exception as e:
87-
logger.warning(f"Authentication failed: {e}")
88-
return jsonify({'error': 'Invalid token'}), 401
116+
logger.error(f"[{request_id}] Systematic auth error: {e}")
117+
return jsonify({'error': 'Authentication system error', 'request_id': request_id}), 500
89118

90119
# Check permissions
91120
required_perm = f'sign:{operation.split("_")[1]}'
92121
if required_perm not in token_info.get('permissions', []):
93-
logger.warning(f"Token {token_info.get('token_id')} lacks {required_perm}")
94-
return jsonify({'error': 'Insufficient permissions'}), 403
122+
latency = time.time() - start_time
123+
logger.warning(f"[{request_id}] Token {token_info.get('token_id')} lacks {required_perm}")
124+
125+
audit_logger.log_signing(
126+
token_id=token_info['token_id'],
127+
operation=operation,
128+
key_used=None,
129+
data_hash=None,
130+
success=False,
131+
client_ip=request.remote_addr,
132+
request_id=request_id,
133+
latency=latency,
134+
error='Insufficient permissions'
135+
)
136+
return jsonify({'error': 'Insufficient permissions', 'request_id': request_id}), 403
95137

96138
# Parse request
97-
data = request.get_json()
139+
try:
140+
data = request.get_json()
141+
except Exception:
142+
return jsonify({'error': 'Invalid JSON', 'request_id': request_id}), 400
143+
98144
if not data:
99-
return jsonify({'error': 'Invalid JSON'}), 400
145+
return jsonify({'error': 'Empty request body', 'request_id': request_id}), 400
100146

101147
raw_data_b64 = data.get('data')
148+
payload_size = len(raw_data_b64) if raw_data_b64 else 0
102149

103150
# 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
151+
if raw_data_b64 and payload_size > 10 * 1024 * 1024: # 10MB limit
152+
latency = time.time() - start_time
153+
audit_logger.log_signing(
154+
token_id=token_info['token_id'],
155+
operation=operation,
156+
key_used=None,
157+
data_hash=f"size:{payload_size}",
158+
success=False,
159+
client_ip=request.remote_addr,
160+
request_id=request_id,
161+
latency=latency,
162+
payload_size=payload_size,
163+
error='Payload too large'
164+
)
165+
return jsonify({'error': 'Payload too large (limit 10MB)', 'request_id': request_id}), 413
106166

107-
package_hash = data.get('package_hash')
108-
repodata_hash = data.get('repodata_hash')
167+
if not raw_data_b64:
168+
return jsonify({'error': 'Missing "data" field', 'request_id': request_id}), 400
169+
109170
key_type = data.get('key_type', 'legacy')
110171

111172
sign_target = None
112173
data_id = None
113174

114-
if raw_data_b64:
115-
try:
116-
sign_target = base64.b64decode(raw_data_b64)
117-
data_id = f"raw_data:{len(sign_target)}b"
118-
except Exception as e:
119-
return jsonify({'error': f'Invalid Base64 data: {e}'}), 400
120-
elif package_hash or repodata_hash:
121-
err_msg = (
122-
'Signing by hash is deprecated. Please provide base64 encoded "data" field instead '
123-
'(e.g., {"data": "base64_encoded_content", "key_type": "modern"})'
124-
)
125-
return jsonify({'error': err_msg}), 400
126-
else:
127-
return jsonify({'error': 'Missing "data" field (base64 encoded content required)'}), 400
175+
try:
176+
sign_target = base64.b64decode(raw_data_b64)
177+
data_id = hashlib.sha256(sign_target).hexdigest() # Properly hash the content for audit
178+
except Exception as e:
179+
return jsonify({'error': f'Invalid Base64 data: {e}', 'request_id': request_id}), 400
128180

129181
# Sign the data
130182
try:
131183
passphrase = config.get('MODERN_PASSPHRASE' if key_type == 'modern' else 'LEGACY_PASSPHRASE')
132184
signature = signing_engine.sign_data(sign_target, key_type, passphrase)
133185
key_id = signing_engine.get_key_id(key_type)
186+
key_fingerprint = signing_engine.get_key_fingerprint(key_type)
187+
188+
latency = time.time() - start_time
134189

135190
# Audit log
136191
audit_logger.log_signing(
@@ -139,27 +194,37 @@ def _handle_signing(operation):
139194
key_used=key_id,
140195
data_hash=data_id,
141196
success=True,
142-
client_ip=request.remote_addr
197+
client_ip=request.remote_addr,
198+
request_id=request_id,
199+
latency=latency,
200+
key_fingerprint=key_fingerprint,
201+
payload_size=payload_size
143202
)
144203

145204
return jsonify({
146205
'signature': signature,
147206
'key_id': key_id,
207+
'key_fingerprint': key_fingerprint,
208+
'request_id': request_id,
148209
'timestamp': datetime.now(UTC).isoformat()
149210
})
150211

151212
except Exception as e:
152-
logger.error(f"Signing failed: {e}")
213+
latency = time.time() - start_time
214+
logger.error(f"[{request_id}] Signing failed: {e}")
153215
audit_logger.log_signing(
154216
token_id=token_info['token_id'],
155217
operation=operation,
156218
key_used=key_id if 'key_id' in locals() else key_type,
157219
data_hash=data_id,
158220
success=False,
159221
client_ip=request.remote_addr,
160-
error=str(e)
222+
request_id=request_id,
223+
latency=latency,
224+
error=str(e),
225+
payload_size=payload_size
161226
)
162-
return jsonify({'error': str(e)}), 500
227+
return jsonify({'error': str(e), 'request_id': request_id}), 500
163228

164229

165230
@app.route('/api/v1/health', methods=['GET'])

server/signing_engine.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,14 @@ def get_key_id(self, key_type: str) -> str:
3030
raise ValueError(f"Unknown key type: {key_type}")
3131
return self.KEYS[key_type]
3232

33+
def get_key_fingerprint(self, key_type: str) -> str:
34+
"""Get full fingerprint for a given key type"""
35+
key_id = self.get_key_id(key_type)
36+
key_list = self.gpg.list_keys(keys=[key_id])
37+
if key_list:
38+
return key_list[0].get('fingerprint')
39+
return None
40+
3341
def list_keys(self) -> List[Dict]:
3442
"""List available signing keys"""
3543
keys = []

0 commit comments

Comments
 (0)