Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 37 additions & 5 deletions dicom_server/core/dicom_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,46 @@ def __init__(

def handle_assoc(self, event):
try:
assoc = event.assoc
calling_ae = assoc.requestor.ae_title.strip()
called_ae = assoc.requestor.requested_ae_title.strip()
ip_addr = assoc.requestor.address

# CALLED AE validation
if called_ae not in ALLOWED_AE_TITLES:
try:
self.event_collector.record_rejected_assoc(
ip_addr, calling_ae, called_ae, "INVALID_CALLED_AE"
)
except Exception:
pass

assoc.reject(result=0x01, source=0x01, reason=0x07)
return

# CALLING AE validation
if calling_ae not in ALLOWED_AE_TITLES:
try:
self.event_collector.record_rejected_assoc(
ip_addr, calling_ae, called_ae, "INVALID_CALLING_AE"
)
except Exception:
pass

assoc.reject(result=0x01, source=0x01, reason=0x03)
return

# Only valid associations reach here
version_name = (
str(event.assoc.requestor.implementation_version_name)
if event.assoc.requestor.implementation_version_name
str(assoc.requestor.implementation_version_name)
if assoc.requestor.implementation_version_name
else "N/A"
)
ip = str(event.assoc.requestor.address)
port = event.assoc.requestor.port
ip = str(ip_addr)
port = assoc.requestor.port
self.event_collector.session_started(ip, port, version_name)
except Exception as e:

except Exception:
self.exceptions_logger.exception(
"Unexpected error while handling association"
)
Expand Down Expand Up @@ -311,3 +341,5 @@ def get_matching_instances(self, event, instances):
]

return matching


21 changes: 21 additions & 0 deletions dicom_server/core/dicom_session_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,24 @@


class SessionCollector(ISessionCollector):
@inject
def record_rejected_assoc(self, ip, calling_ae, called_ae, reason,
redis: IRedisService = None):

if not redis:
return
try:
event = {
"timestamp": datetime.utcnow().isoformat(),
"ip": str(ip),
"calling_ae": calling_ae.decode(errors="ignore") if isinstance(calling_ae, bytes) else str(calling_ae),
"called_ae": called_ae.decode(errors="ignore") if isinstance(called_ae, bytes) else str(called_ae),
"reason": reason,
}
redis.add_security_event(event)
except Exception:
self.exceptions_logger.exception("Failed to record rejected association to Redis")

@inject
def __init__(
self,
Expand Down Expand Up @@ -176,3 +194,6 @@ def session_locked(self):

def set_session_id(self, s_id):
self.session_info[sk.SESSION_ID.key] = s_id



6 changes: 6 additions & 0 deletions dicom_server/core/redis_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@


class RedisClient(IRedisService):
def add_security_event(self, event):
key = "dicom:events:assoc_rejected"
payload = json.dumps(event)
self.redis_client.rpush(key, payload)
self.redis_client.ltrim(key, -10000, -1)

def __init__(self, app_logger, exceptions_logger, redis_client):

Expand Down Expand Up @@ -88,3 +93,4 @@ def update_files_integrity_state(self, changed_files):
self.exceptions_logger.exception(
"Unexpected error while adding integrity check identifier"
)

5 changes: 5 additions & 0 deletions dicom_server/services/dicom_session_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@

class ISessionCollector(ABC):

@abstractmethod
def record_rejected_assoc(self, ip, calling_ae, called_ae, reason):
pass

@abstractmethod
def session_started(self, ip, port, version_name) -> None:
"""Start the DICOM session on association request recieved"""
Expand Down Expand Up @@ -37,3 +41,4 @@ def session_locked(self) -> bool:
def set_session_id(self, session_id) -> None:
"""Set an identifier to the current session"""
pass