Skip to content

Inject ISecurityPolicyRegistry into Session and the secure channel instead of falling back to the default singleton #4250

Description

@marcschier

Background

#4232 turned the security policy set into an injectable service. ISecurityPolicyRegistry carries the lookup, default-URI, certificate-type and cryptography surface, the sealed SecurityPolicies class implements it and owns its own policy snapshot, and AddSecurityPolicy / AddSecurityPolicyRegistry register it in the container.

That made the policy set injectable. It did not make the stack inject it. The abstraction exists and is registered, but no production path resolves it, so every call still lands on the SecurityPolicies.Default fallback singleton. This issue tracks closing that gap.

Why it matters

Two applications hosted in one process get one registry's behaviour today. AddSecurityPolicy composes into a container-owned registry, and #4232 added tests proving that registry is isolated — but nothing reads it, so a policy registered by application A is neither visible to A's own channels nor to B's. The isolation is correct and unused.

Until this is done, a deployment that registers a custom security policy through DI is silently served the built-in set on every path that matters.

Current state

69 SecurityPolicies.Default references across src/. They fall into three groups.

1. Already injectable — nothing to do. These take an optional ISecurityPolicyRegistry and only fall back to Default when none is passed:

  • IssuedIdentityTokenHandler, UserNameIdentityTokenHandler, X509IdentityTokenHandler (including the X509 handler's private clone constructor)
  • CryptoProviderAuditor

2. The actual work. Types with an instance and a construction path that could carry a registry:

File Uses
src/Opc.Ua.Client/Session/Session.cs 18
src/Opc.Ua.Client/Identity/ClientIdentityProviderExtensions.cs 3
src/Opc.Ua.Server/Session/SessionSecurityPolicyHelper.cs 3
src/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Asymmetric.cs 2
src/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Symmetric.cs 2
src/Opc.Ua.Core.Diagnostics/.../OfflineSecureChannel.cs 1
src/Opc.Ua.Client/Identity/.../X509ClientIdentityProvider.cs 1
src/Opc.Ua.Client/CoreClientUtils.cs 1
src/Opc.Ua.PubSub/.../OpcUaSecurityKeyServiceClient.cs 1

Session is the centre of gravity: threading a registry through it reaches the session factory and the channel construction path, which is why #4232 deliberately stopped short rather than burying it in an already large change.

The channel is the other half. UaSCBinaryChannel resolves policies per handshake, so a channel needs to be constructed against the registry its application configured — that is the point at which a registered policy actually becomes reachable by a peer.

3. Legitimately static — leave alone. No instance to inject into, and several run before any container exists:

CryptoUtils, Nonce, EncryptedSecret, Audit, CertificateIdentifier, SecurityConfiguration, ApplicationConfiguration, ApplicationConfigurationBuilder, ConfiguredEndpoints, CryptoCompliance, OpcUaCryptoBuilderExtensions.

SecurityConfiguration.SupportedSecurityPolicies and the endpoint set it feeds are the clearest case — they run during configuration load, before DI exists. Default is exactly what that fallback is for.

Proposed approach

  1. Give UaSCBinaryChannel and its subclasses a registry, sourced from the transport/channel construction path, defaulting to SecurityPolicies.Default.
  2. Give Session one, sourced from the session factory, and pass it to the identity token handlers it creates — those already accept it, so this is the step that makes the existing injection points load-bearing.
  3. Follow with SessionSecurityPolicyHelper, ClientIdentityProviderExtensions and the remaining single-use sites.
  4. Add an integration test that registers a custom policy through AddSecurityPolicy and asserts a channel opened by that application can negotiate it — the assertion that would fail today.

Each step keeps the ?? SecurityPolicies.Default fallback, so nothing changes for a caller that configures nothing and the steps can land independently.

Notes

Metadata

Metadata

Labels

enhancementAPI or feature enhancement

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions