Files
vitor-aignosi ecb055eefb SIENTIAPDE-1973
SIENTIAPDE-1973: Implement per-connector OPC client certificates and remove legacy read engine.
This update overhauls certificate management, transitioning from a single platform certificate to individual client certificates defined in the slot configuration. The CertProvider now downloads certificates from MinIO based on certificate_id, applying dynamic security policies (mode, policy) from the slot. The legacy READ_ENGINE=legacy option and OpcManager have been entirely removed, standardizing on the ServerReader with strict slot v2 enforcement. Write command resolution (ServerConfigResolver) now consistently uses connectors joined with opc_certificates. The CERT_BUCKET environment variable is no longer used.
2026-10-29 12:39:21 -03:00

93 lines
2.7 KiB
Python

from unittest.mock import AsyncMock, MagicMock
import pytest
from asyncua import ua
from asyncua.crypto.security_policies import (
SecurityPolicyAes128Sha256RsaOaep,
SecurityPolicyBasic256Sha256,
)
from ingestor.connection.security import (
SESSION_TIMEOUT_MS,
UnsupportedSecurityError,
apply_security,
is_secured,
)
CERTIFICATE = {'certificate_id': 1, 'application_uri': 'urn:sientia:opc-ingestor'}
def _client():
client = MagicMock()
client.set_security = AsyncMock()
return client
@pytest.mark.asyncio
async def test_apply_security_uses_mode_and_policy_and_channel_lifetime():
client = _client()
await apply_security(
client, {'mode': 'SignAndEncrypt', 'policy': 'Basic256Sha256'}, CERTIFICATE, 'c', 'k'
)
assert client.application_uri == 'urn:sientia:opc-ingestor'
assert client.session_timeout == SESSION_TIMEOUT_MS
assert client.secure_channel_timeout == int(SESSION_TIMEOUT_MS * 1.25)
args, kwargs = client.set_security.call_args
assert args[0] is SecurityPolicyBasic256Sha256
assert kwargs == {
'certificate': 'c',
'private_key': 'k',
'server_certificate': None,
'mode': ua.MessageSecurityMode.SignAndEncrypt,
}
@pytest.mark.asyncio
async def test_apply_security_sign_with_another_policy():
client = _client()
await apply_security(
client, {'mode': 'Sign', 'policy': 'Aes128Sha256RsaOaep'}, CERTIFICATE, 'c', 'k'
)
args, kwargs = client.set_security.call_args
assert args[0] is SecurityPolicyAes128Sha256RsaOaep
assert kwargs['mode'] == ua.MessageSecurityMode.Sign
@pytest.mark.asyncio
@pytest.mark.parametrize(
'security',
[
{'mode': 'Encrypt', 'policy': 'Basic256Sha256'},
{'mode': 'Sign', 'policy': 'Basic999'},
{'mode': 'Sign', 'policy': 'None'},
{},
],
)
async def test_apply_security_rejects_unknown_mode_or_policy(security):
client = _client()
with pytest.raises(UnsupportedSecurityError):
await apply_security(client, security, CERTIFICATE, 'c', 'k')
client.set_security.assert_not_awaited()
@pytest.mark.parametrize(
('server_cfg', 'secured'),
[
({'security': {'mode': 'SignAndEncrypt'}, 'certificate': CERTIFICATE}, True),
({'security': {'mode': 'Sign'}, 'certificate': CERTIFICATE}, True),
# No certificate: no security (D5), whatever the mode
({'security': {'mode': 'SignAndEncrypt'}, 'certificate': None}, False),
({'security': {'mode': 'SignAndEncrypt'}}, False),
# mode None: no security even with a certificate
({'security': {'mode': 'None'}, 'certificate': CERTIFICATE}, False),
({}, False),
],
)
def test_is_secured(server_cfg, secured):
assert is_secured(server_cfg) is secured