Skip to content

feat: Add brute-force protection and asyncua User class integration#85

Merged
marconetsf merged 2 commits into
RTOP-100-OPC-UAfrom
improve/brute-force-protection
Jan 22, 2026
Merged

feat: Add brute-force protection and asyncua User class integration#85
marconetsf merged 2 commits into
RTOP-100-OPC-UAfrom
improve/brute-force-protection

Conversation

@marconetsf
Copy link
Copy Markdown
Contributor

Summary

  • Implement rate limiting for authentication attempts to protect against brute-force attacks
  • Update user_manager.py to return asyncua's native User class for better library integration
  • Add comprehensive unit tests for the OpenPLCUserManager class
  • Add documentation for authentication implementation and security mode analysis

Changes

Brute-Force Protection (user_manager.py)

  • Added RateLimiter class with configurable settings:
    • max_attempts: 5 (default) failed attempts before lockout
    • lockout_duration_seconds: 300 (5 minutes) lockout period
    • attempt_window_seconds: 60 (1 minute) window for counting attempts
  • Rate limiting tracks by username (user:{username}) or certificate fingerprint (cert:{fingerprint})
  • Successful authentication resets the attempt counter
  • Anonymous authentication is not rate limited

asyncua User Class Integration

  • Now returns asyncua.crypto.permission_rules.User objects instead of SimpleNamespace
  • Preserves openplc_role attribute for permission callbacks
  • Cleaner integration with asyncua's authorization system

Unit Tests (test_user_manager.py)

  • 32 new tests covering:
    • RateLimiter functionality (9 tests)
    • OpenPLCUserManager initialization (3 tests)
    • Password authentication (4 tests)
    • Anonymous authentication (2 tests)
    • Rate limiting integration (2 tests)
    • Auth method detection (4 tests)
    • Role mappings (3 tests)
    • Profile matching (2 tests)
    • Rate limit identifier generation (3 tests)

Documentation

  • OPCUA_AUTHENTICATION_REVIEW.md: Comparison with asyncua 1.1.8 patterns
  • OPCUA_SECURITY_MODE_INSUFFICIENT_ANALYSIS.md: Analysis of BadSecurityModeInsufficient error

Test plan

  • All 32 unit tests pass (pytest tests/pytest/plugins/opcua/test_user_manager.py)
  • Manual testing with OPC-UA client (UAExpert)
  • Verify rate limiting blocks after max attempts
  • Verify successful auth resets the counter

🤖 Generated with Claude Code

@marconetsf marconetsf requested a review from Copilot January 22, 2026 12:39
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds brute-force protection to OPC-UA authentication and improves integration with the asyncua library by returning native User objects. It includes comprehensive unit tests and documentation analyzing authentication patterns and security modes.

Changes:

  • Implemented RateLimiter class with configurable lockout settings (5 attempts, 5-minute lockout by default)
  • Updated OpenPLCUserManager to return asyncua's native User class instead of SimpleNamespace
  • Added 32 unit tests covering rate limiting, authentication methods, and role mappings

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
tests/pytest/plugins/opcua/test_user_manager.py New comprehensive test suite for user manager functionality including rate limiting, authentication methods, and role mappings
docs/opcua/OPCUA_SECURITY_MODE_INSUFFICIENT_ANALYSIS.md Documentation explaining BadSecurityModeInsufficient error and security configuration options
docs/opcua/OPCUA_AUTHENTICATION_REVIEW.md Analysis comparing implementation with asyncua 1.1.8 patterns
core/src/drivers/plugins/python/opcua/user_manager.py Added rate limiting infrastructure and updated to return asyncua User objects

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +367 to +369
def _authenticate_password(
self, username: str, password: str
) -> tuple[Optional[User], Optional[str]]:
Copy link

Copilot AI Jan 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The return type uses the modern tuple[...] syntax which requires Python 3.9+. For better compatibility with older Python versions, consider using Tuple[Optional[User], Optional[str]] from the typing module (already imported on line 15) or verify that Python 3.9+ is the minimum supported version.

Copilot uses AI. Check for mistakes.
return User(role=asyncua_role, name=username), openplc_role

def _authenticate_certificate(self, certificate: Any) -> Optional[Any]:
def _authenticate_certificate(self, certificate: Any) -> tuple[Optional[User], Optional[str]]:
Copy link

Copilot AI Jan 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The return type uses the modern tuple[...] syntax which requires Python 3.9+. For better compatibility with older Python versions, consider using Tuple[Optional[User], Optional[str]] from the typing module (already imported on line 15) or verify that Python 3.9+ is the minimum supported version.

Copilot uses AI. Check for mistakes.
return User(role=asyncua_role, name=f"cert:{cert_id}"), openplc_role

def _authenticate_anonymous(self, profile: Any) -> Optional[Any]:
def _authenticate_anonymous(self, profile: Any) -> tuple[Optional[User], Optional[str]]:
Copy link

Copilot AI Jan 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The return type uses the modern tuple[...] syntax which requires Python 3.9+. For better compatibility with older Python versions, consider using Tuple[Optional[User], Optional[str]] from the typing module (already imported on line 15) or verify that Python 3.9+ is the minimum supported version.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

@marconetsf marconetsf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Review Summary

This PR implements a well-designed brute-force protection mechanism for OPC-UA authentication with the following highlights:

Strengths:

  • Clean separation of concerns (RateLimiter, RateLimitConfig, AuthAttemptTracker classes)
  • Proper asyncua integration (returns native User class instead of SimpleNamespace)
  • Comprehensive test coverage (32 tests, all passing)
  • Good documentation added for authentication patterns and security analysis

Test Results: 32/32 tests pass

Pre-commit: Black, isort, ruff pass. Minor pylint warnings (see issues comment).

Overall:APPROVED

See full review: docs/pr-reviews/PR_85_REVIEW.md

@marconetsf
Copy link
Copy Markdown
Contributor Author

Minor Issues Found (Non-blocking)

These are suggestions for improvement - not blocking merge.


1. Unnecessary parentheses (pylint C0325)

  • File: core/src/drivers/plugins/python/opcua/user_manager.py:329
  • Issue: success=(user is not None) has unnecessary parentheses
  • Suggestion:
self.rate_limiter.record_attempt(rate_limit_id, success=user is not None)

2. Constant naming style (pylint C0103)

  • File: core/src/drivers/plugins/python/opcua/user_manager.py:24,26
  • Issue: _bcrypt_available should be _BCRYPT_AVAILABLE per UPPER_CASE naming
  • Note: This is a pre-existing pattern in the file, optional to change

Suggestions (Optional Improvements)

3. Thread safety consideration

  • The RateLimiter._trackers dict is not thread-safe
  • If OPC-UA server handles concurrent connections, consider using threading.Lock
  • May be acceptable if asyncua serializes authentication calls

4. Memory cleanup scheduling

  • cleanup_expired() is implemented but never called automatically
  • Consider calling it periodically or after each authentication attempt
  • Current implementation won't leak significant memory in practice

Full review: docs/pr-reviews/PR_85_REVIEW.md

@marconetsf marconetsf merged commit 090fbad into RTOP-100-OPC-UA Jan 22, 2026
@marconetsf marconetsf deleted the improve/brute-force-protection branch January 22, 2026 17:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants