Skip to content

[GDS] Allow Global Discovery Server to Work with Custom Certificate Groups#3589

Merged
marcschier merged 4 commits intomasterfrom
copilot/extend-certificate-group-support
Mar 4, 2026
Merged

[GDS] Allow Global Discovery Server to Work with Custom Certificate Groups#3589
marcschier merged 4 commits intomasterfrom
copilot/extend-certificate-group-support

Conversation

Copy link
Contributor

Copilot AI commented Feb 28, 2026

The GDS ApplicationsNodeManager only supported exactly three certificate groups (Application, Https, UserToken) matched by well-known certificate types. Any additional group configuration threw NotImplementedException, making custom or extended group topologies impossible.

Proposed changes

ApplicationsNodeManager.csSetCertificateGroupNodes

  • Replaced the certificate-type-based matching with CertificateGroupConfiguration.Id-based matching:
    • "Default" or "DefaultApplicationGroup" → assigns the existing DefaultApplicationGroup predefined node
    • "DefaultHttpsGroup" → assigns the existing DefaultHttpsGroup predefined node
    • "DefaultUserTokenGroup" → assigns the existing DefaultUserTokenGroup predefined node
    • Any other Id → dynamically creates a new CertificateGroupState node under the CertificateGroups folder, wiring up the TrustList handle via the existing post-branch setup
  • Fixed a NodeId assignment bug: NodeId.Null is passed to Create(..., assignNodeIds: true) so the framework generates a fresh NodeId for the node; certificateGroup.Id is then read back from customGroupNode.NodeId after Create returns, ensuring the key in m_certificateGroups matches the actual node in the address space.
  • Existing behavior for configurations using the predefined group IDs is unchanged (backward-compatible).
  • Certificate types must still be well-known OPC UA type names (e.g. RsaSha256ApplicationCertificateType).

GlobalDiscoveryTestServer.cs / Common.cs

  • Added optional additionalCertGroups parameter to StartServerAsync and TestUtils.StartGDSAsync to inject custom certificate group configurations into the loaded server config for integration testing.

CustomCertificateGroupIntegrationTest.cs (new)

  • CustomCertificateGroupAppearsInGetCertificateGroupsAsync: starts a GDS server with a "MyCustomGroup" certificate group, connects as admin, registers an application, and verifies GetCertificateGroupsAsync returns 2 groups with a working GetTrustListAsync for each.
  • CustomCertificateGroupNodeExistsInAddressSpaceAsync: additionally reads the custom group node from the server address space via ReadNodeAsync and asserts its BrowseName equals "MyCustomGroup".

CertificateGroupTests.cs

  • Added test: unknown certificate type string still throws NotImplementedException.

Example config for a custom group

<CertificateGroupConfiguration>
  <Id>MyCustomGroup</Id>
  <CertificateTypes>
    <ua:String>RsaSha256ApplicationCertificateType</ua:String>
  </CertificateTypes>
  <SubjectName>CN=My Custom GDS CA, O=MyOrg</SubjectName>
  <BaseStorePath>%LocalApplicationData%/OPC/GDS/CA/custom</BaseStorePath>
</CertificateGroupConfiguration>

Related Issues

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • Enhancement (non-breaking change which adds functionality)
  • Test enhancement (non-breaking change to increase test coverage)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected, requires version increase of Nuget packages)
  • Documentation Update (if none of the other choices apply)

Checklist

  • I have read the CONTRIBUTING doc.
  • I have signed the CLA.
  • I ran tests locally with my changes, all passed.
  • I fixed all failing tests in the CI pipelines.
  • I fixed all introduced issues with CodeQL and LGTM.
  • I have added tests that prove my fix is effective or that my feature works and increased code coverage.
  • I have added necessary documentation (if appropriate).
  • Any dependent changes have been merged and published in downstream modules.

Further comments

The custom node is created using CertificateGroupState.Create(SystemContext, NodeId.Null, browseName, displayName, assignNodeIds: true) so the framework assigns fresh NodeIds to the root node and all child nodes (including TrustList). certificateGroup.Id is set from customGroupNode.NodeId after Create returns, ensuring the dictionary key used by GetCertificateGroupsAsync matches the actual node registered in PredefinedNodes. The TrustList handle is set by the existing code path at the end of SetCertificateGroupNodes, requiring no duplication.

Original prompt

This section details on the original issue you should resolve

<issue_title>[GDS] Allow Global Discovery Server to Work with custom Certificate Groups</issue_title>
<issue_description>Currently the ApplicationNodeManager of the GDS only uses Certificate Groups from configuration If the Group Matches a Well known Node Id, based in certificate types. The current behaviour needs to be extended, so it is possible to add custom Certificate Groups. The NodeManager in this Case needs to create the nodes and assign new NodeIds, while still being compatible with existing configurations without behavioural Change.</issue_description>

Comments on the Issue (you are @copilot in this section)


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

@CLAassistant
Copy link

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

…ation

Co-authored-by: romanett <7413710+romanett@users.noreply.github.com>
Copilot AI changed the title [WIP] Add support for custom certificate groups in GDS [GDS] Allow Global Discovery Server to Work with Custom Certificate Groups Feb 28, 2026
@romanett
Copy link
Contributor

@copilot dont allow custom Certificate types instead If the CertificateGroup.Id in the config does not math Default create a new one instead of choosing the Predefined ones

…odes

Co-authored-by: romanett <7413710+romanett@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Feb 28, 2026

@copilot dont allow custom Certificate types instead If the CertificateGroup.Id in the config does not math Default create a new one instead of choosing the Predefined ones

Done in a7c1eea. SetCertificateGroupNodes now uses CertificateGroupConfiguration.Id to select the node:

  • "Default" or "DefaultApplicationGroup" → DefaultApplicationGroup predefined node
  • "DefaultHttpsGroup" → DefaultHttpsGroup predefined node
  • "DefaultUserTokenGroup" → DefaultUserTokenGroup predefined node
  • Any other Id → dynamically creates a new CertificateGroupState node under the CertificateGroups folder

The CertificateGroup.cs change (custom NodeId cert types) has been reverted — certificate types must still be well-known OPC UA type names.

@romanett
Copy link
Contributor

@copilot please add an integration Test that verifies a custom certificate group is read from configuration correctly and works in the address space

Co-authored-by: romanett <7413710+romanett@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Feb 28, 2026

@copilot please add an integration Test that verifies a custom certificate group is read from configuration correctly and works in the address space

Done in 0176ba8. Added CustomCertificateGroupIntegrationTest with two tests:

  1. CustomCertificateGroupAppearsInGetCertificateGroupsAsync — starts a GDS server with a "MyCustomGroup" certificate group injected into the configuration, connects as admin, registers an application, and verifies GetCertificateGroupsAsync returns exactly 2 groups with working GetTrustListAsync for each.

  2. CustomCertificateGroupNodeExistsInAddressSpaceAsync — additionally calls ReadNodeAsync on the custom group's NodeId and verifies the node exists in the address space with BrowseName "MyCustomGroup".

Also fixed a bug discovered during the integration testing: Create(..., assignNodeIds: true) was reassigning the node's own NodeId, causing a mismatch with certificateGroup.Id. The fix passes NodeId.Null to Create and reads back customGroupNode.NodeId into certificateGroup.Id after creation.

@romanett romanett requested review from marcschier and mrsuciu March 2, 2026 11:56
var customGroupNode = new Ua.CertificateGroupState(certGroupsFolder);
customGroupNode.Create(
SystemContext,
NodeId.Null,
Copy link

@Tyrrx Tyrrx Mar 4, 2026

Choose a reason for hiding this comment

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

@romanett Is it possible to define this in the config also? I do not entirely understand how to node id generation works but in my case I need an implementation of a custom group which is already defined on a server (Siemens device). So it would be best if I could just specify the NodeId of the group using the string representation in the config.

Copy link
Contributor

Choose a reason for hiding this comment

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

This is Just the GDS Server (own Adress space)

Your PR is still needed, this is Just so your changes can be properly tested.

Or did i misunderstand you?

Copy link

Choose a reason for hiding this comment

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

I thought the GDS more or less mirrors the server's certificate groups for both pull and push management. So for instance when my server has a custom certificate group with id 200 with some custom namespace uri, the GDS also has that in the end. When I start CSR I would have sent the NodeId of the server to the GDS to specify which group handles the CSR and as far as I understood it the server then has a mapping for that nodeId to handle it (additionally to the default groups). Same for the trustlist: I ask the server which groups it has and then use that group ID to query the defined trust list from the GDS. Or is that wrong?

Copy link

@Tyrrx Tyrrx Mar 4, 2026

Choose a reason for hiding this comment

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

Okay I found out why I had a wrong expectation. I thought that I could just pass the same certificate group id to both the server client and the gds client but they expect different ids. Does that mean that a push management instance need to know about the mappings in general?

Copy link
Contributor

Choose a reason for hiding this comment

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

I will try to find out what the expectation is here. But hardocoding node ids in config seems wrong to me.
https://reference.opcfoundation.org/GDS/v105/docs/7.4#_Ref408341025

https://reference.opcfoundation.org/GDS/v105/docs/7.7

Copy link
Contributor

Choose a reason for hiding this comment

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

Ahh I found it, i think with 1.05.6 the CreateEndpoint workflow was added:
https://reference.opcfoundation.org/GDS/v105/docs/7.7.5#_Ref195595671

The name of the new record can be any value which is unique within the configuration and the CertificateGroups Object on the ApplicationConfiguration instance. It is used to create the BrowseName for the new CertificateGroup Object.

@marcschier marcschier merged commit 341bbb7 into master Mar 4, 2026
141 of 142 checks passed
@romanett romanett deleted the copilot/extend-certificate-group-support branch March 5, 2026 07:46
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.

[GDS] Allow Global Discovery Server to Work with custom Certificate Groups

5 participants