-
Notifications
You must be signed in to change notification settings - Fork 1k
Description
Type of issue
- Bug
- Enhancement
- Compliance
- Question
- Help wanted
Current Behavior
From my analysis, the issue appears to originate in ServerBase.cs, specifically in the method GetUserTokenPolicies, where the PolicyId is generated using a global counter:
clone.PolicyId = Utils.Format("{0}", ++m_userTokenPolicyId);
Since this counter is incremented globally, the generated UserTokenPolicyId values become dependent on the order in which endpoints (or base addresses) are processed.
In combination with the method TranslateEndpointDescriptions in ServerBase.cs, the UserTokenPolicyIds of the first configured server address are reused for subsequent addresses.
As a result, GetEndpoints returns UserTokenPolicyIds that do not match the internal configuration of the endpoint actually used by the client, causing ActivateSession to fail with BadIdentityTokenInvalid.
Expected Behavior
UserTokenPolicyIds should be stable across all endpoints of the same server instance.
For semantically identical UserTokenPolicy definitions, the same policy ID should be returned, independent of the IP address used to access the server.
Generating policy IDs using a simple counter can lead to endpoint-order-dependent behavior.
Reusing existing policy IDs for equivalent UserTokenPolicy definitions would avoid this and allow clients to reliably activate sessions using the IDs returned by GetEndpoints.
One possible approach would be to introduce a semantic equality comparison for UserTokenPolicy and reuse existing policy IDs instead of generating new ones unconditionally.
Proposed equality method on UserTokenPolicy
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return
this.TokenType == other.TokenType &&
string.Equals(this.SecurityPolicyUri, other.SecurityPolicyUri, StringComparison.Ordinal) &&
string.Equals(this.IssuedTokenType, other.IssuedTokenType, StringComparison.Ordinal) &&
string.Equals(this.IssuerEndpointUrl, other.IssuerEndpointUrl, StringComparison.Ordinal);
}
Server-wide storage of generated policies in ServerBase
private readonly IList<UserTokenPolicy> m_userTokenPolicys = new List<UserTokenPolicy>();
logic inside GetUserTokenPolicies in ServerBase
var existingPolicy =
m_userTokenPolicys.FirstOrDefault(o => o.TokenPolicyEquals(policy));
if (existingPolicy == null)
{
// Ensure each policy has a unique ID within the context of the Server
clone.PolicyId = Utils.Format("{0}", m_userTokenPolicys.Count + 1);
policies.Add(clone);
m_userTokenPolicys.Add(clone);
}
else
{
policies.Add(existingPolicy);
}
Steps To Reproduce
-
Configure an OPC UA server to listen on multiple network interfaces (not localhost),
e.g. 127.0.0.1 and 192.168.138.x (or any additional IP address assigned to the host). -
Configure a user that is allowed to authenticate using Username/Password
(e.g. test / test). -
Configure the server with the following security settings:
- SecurityModes: Sign, SignAndEncrypt
- SecurityPolicyUri: http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256
- Allowed User Token Policies:
- UserName
- Certificate
- IssuedToken
- Start server
- Connect a client to the server using the first IP address
→ Connection succeeds. - Connect the same client to the server using the second IP address
→ Connection fails during ActivateSession with the error: "Error 'BadIdentityTokenInvalid' was returned during ActivateSession"