66import os
77import sys
88import json
9+ import uuid
10+ import time
911import logging
12+ import hashlib
1013from datetime import datetime , UTC
1114from flask import Flask , request , jsonify
1215from pathlib import Path
@@ -74,63 +77,115 @@ def load_config(path):
7477
7578def _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' ])
0 commit comments