You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
#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:
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:
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
Give UaSCBinaryChannel and its subclasses a registry, sourced from the transport/channel construction path, defaulting to SecurityPolicies.Default.
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.
Follow with SessionSecurityPolicyHelper, ClientIdentityProviderExtensions and the remaining single-use sites.
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.
Background
#4232 turned the security policy set into an injectable service.
ISecurityPolicyRegistrycarries the lookup, default-URI, certificate-type and cryptography surface, the sealedSecurityPoliciesclass implements it and owns its own policy snapshot, andAddSecurityPolicy/AddSecurityPolicyRegistryregister 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.Defaultfallback singleton. This issue tracks closing that gap.Why it matters
Two applications hosted in one process get one registry's behaviour today.
AddSecurityPolicycomposes 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.Defaultreferences acrosssrc/. They fall into three groups.1. Already injectable — nothing to do. These take an optional
ISecurityPolicyRegistryand only fall back toDefaultwhen none is passed:IssuedIdentityTokenHandler,UserNameIdentityTokenHandler,X509IdentityTokenHandler(including the X509 handler's private clone constructor)CryptoProviderAuditor2. The actual work. Types with an instance and a construction path that could carry a registry:
src/Opc.Ua.Client/Session/Session.cssrc/Opc.Ua.Client/Identity/ClientIdentityProviderExtensions.cssrc/Opc.Ua.Server/Session/SessionSecurityPolicyHelper.cssrc/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Asymmetric.cssrc/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Symmetric.cssrc/Opc.Ua.Core.Diagnostics/.../OfflineSecureChannel.cssrc/Opc.Ua.Client/Identity/.../X509ClientIdentityProvider.cssrc/Opc.Ua.Client/CoreClientUtils.cssrc/Opc.Ua.PubSub/.../OpcUaSecurityKeyServiceClient.csSessionis 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.
UaSCBinaryChannelresolves 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.SupportedSecurityPoliciesand the endpoint set it feeds are the clearest case — they run during configuration load, before DI exists.Defaultis exactly what that fallback is for.Proposed approach
UaSCBinaryChanneland its subclasses a registry, sourced from the transport/channel construction path, defaulting toSecurityPolicies.Default.Sessionone, 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.SessionSecurityPolicyHelper,ClientIdentityProviderExtensionsand the remaining single-use sites.AddSecurityPolicyand asserts a channel opened by that application can negotiate it — the assertion that would fail today.Each step keeps the
?? SecurityPolicies.Defaultfallback, so nothing changes for a caller that configures nothing and the steps can land independently.Notes
PubSubSecurityPolicyRegistrygot the same treatment in Pluggable symmetric cryptography, an awaitable asymmetric path, registrable security policies, and PubSub crypto offboarding (#4206, #4207, #4208, #4210) #4232 (interface, sealed implementation,Defaultsingleton) and has the same gap, so it belongs in the same sweep.