From d4126d04888240278f03cef9fb14ff45498d2d7c Mon Sep 17 00:00:00 2001 From: Jooyoung Jung Date: Fri, 5 Jun 2026 13:42:44 +0900 Subject: [PATCH] feat(opcua-server): upgrade Eclipse Milo from 0.6.16 to 1.1.8 The opcua-server tool (plc4j/tools/opcua-server) moves from Eclipse Milo 0.6.16 to 1.1.8. Milo has published no 0.6.x release since 0.6.16; its releases since then are on the 1.x line. Milo 1.x reorganised the server SDK, certificate handling and transport bootstrap, so this is a port, not a version bump. - pom.xml: the five 0.6.16 artifacts (dictionary-manager, sdk-server, stack-server, stack-core, sdk-core) are replaced by milo-sdk-server and milo-transport 1.1.8; milo-sdk-core and milo-stack-core are transitive. - OPCUAServer: bootstrap rewritten for 1.x (FileBasedTrustListManager, FileBasedCertificateQuarantine, KeyStoreCertificateStore, RsaSha256CertificateFactory, DefaultApplicationGroup, DefaultServerCertificateValidator, DefaultCertificateManager) and bound through OpcTcpServerTransport. EndpointConfiguration becomes EndpointConfig. UsernameIdentityValidator constructor now takes only the username/password check. - Existing security directories keep working: the trust list keeps the 0.6 layout (pki/issuers instead of the pki/issuer default of FileBasedTrustListManager; rejected certificates directly in pki/rejected instead of the pki/rejected/certs used by Milo's example server), and the server certificate is taken from the keystore entry that is loaded (the first alias of an existing keystore, as in 0.6, or plc4x-certificate-alias for a new one), so Milo 1.x does not write a second server-rsa-sha256 entry into the keystore. - Plc4xNamespace and Plc4xCommunication: Identifiers becomes NodeIds; DataItem, MonitoredItem and ManagedNamespaceWithLifecycle move to their 1.x packages; Plc4xCommunication.getValue takes an AttributeFilterContext; the DataTypeDictionaryManager is removed (no types were registered with it), so its empty dictionary node is no longer published. - Behaviour change: anonymous access becomes opt-in (in 0.6 anonymous clients were granted read and write access to every configured tag). Milo 1.x dropped the anonymous flag from UsernameIdentityValidator, requiring a separate AnonymousIdentityValidator. allowAnonymousAccess (default false) adds it and the anonymous token policy together; otherwise the server offers username and X.509 tokens only. config.yml lists the key; a new ConfigurationTest checks the default and turning it on. - Milo's predefined username and X.509 user token policies now name Basic256Sha256 instead of Basic256. Closes #700 Signed-off-by: Jooyoung Jung --- plc4j/tools/opcua-server/pom.xml | 27 +--- .../plc4x/java/opcuaserver/OPCUAServer.java | 126 ++++++++++++++---- .../backend/Plc4xCommunication.java | 32 ++--- .../opcuaserver/backend/Plc4xNamespace.java | 22 ++- .../configuration/Configuration.java | 7 + .../src/main/resources/config.yml | 2 + .../configuration/ConfigurationTest.java | 47 +++++++ 7 files changed, 186 insertions(+), 77 deletions(-) create mode 100644 plc4j/tools/opcua-server/src/test/java/org/apache/plc4x/java/opcuaserver/configuration/ConfigurationTest.java diff --git a/plc4j/tools/opcua-server/pom.xml b/plc4j/tools/opcua-server/pom.xml index 5c857efb8..fa5572689 100644 --- a/plc4j/tools/opcua-server/pom.xml +++ b/plc4j/tools/opcua-server/pom.xml @@ -35,31 +35,18 @@ - + org.eclipse.milo - dictionary-manager - 0.6.16 + milo-sdk-server + 1.1.8 org.eclipse.milo - sdk-server - 0.6.16 - - - org.eclipse.milo - stack-server - 0.6.16 - - - org.eclipse.milo - stack-core - 0.6.16 - - - org.eclipse.milo - sdk-core - 0.6.16 + milo-transport + 1.1.8 diff --git a/plc4j/tools/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/OPCUAServer.java b/plc4j/tools/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/OPCUAServer.java index ad88b5b77..18bd4c016 100644 --- a/plc4j/tools/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/OPCUAServer.java +++ b/plc4j/tools/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/OPCUAServer.java @@ -27,6 +27,7 @@ import java.util.Set; import java.util.concurrent.CompletableFuture; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.FileSystems; @@ -37,30 +38,39 @@ import org.apache.plc4x.java.opcuaserver.context.CertificateKeyPair; import org.apache.plc4x.java.opcuaserver.context.CertificateGenerator; import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.eclipse.milo.opcua.sdk.server.EndpointConfig; import org.eclipse.milo.opcua.sdk.server.OpcUaServer; -import org.eclipse.milo.opcua.sdk.server.api.config.OpcUaServerConfig; +import org.eclipse.milo.opcua.sdk.server.OpcUaServerConfig; +import org.eclipse.milo.opcua.sdk.server.identity.AnonymousIdentityValidator; import org.eclipse.milo.opcua.sdk.server.identity.CompositeValidator; import org.eclipse.milo.opcua.sdk.server.identity.UsernameIdentityValidator; import org.eclipse.milo.opcua.sdk.server.identity.X509IdentityValidator; +import org.eclipse.milo.opcua.stack.core.NodeIds; import org.eclipse.milo.opcua.stack.core.StatusCodes; import org.eclipse.milo.opcua.stack.core.UaRuntimeException; +import org.eclipse.milo.opcua.stack.core.security.DefaultApplicationGroup; import org.eclipse.milo.opcua.stack.core.security.DefaultCertificateManager; -import org.eclipse.milo.opcua.stack.core.security.DefaultTrustListManager; +import org.eclipse.milo.opcua.stack.core.security.DefaultServerCertificateValidator; +import org.eclipse.milo.opcua.stack.core.security.FileBasedCertificateQuarantine; +import org.eclipse.milo.opcua.stack.core.security.FileBasedTrustListManager; +import org.eclipse.milo.opcua.stack.core.security.KeyStoreCertificateStore; +import org.eclipse.milo.opcua.stack.core.security.RsaSha256CertificateFactory; import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy; import org.eclipse.milo.opcua.stack.core.transport.TransportProfile; import org.eclipse.milo.opcua.stack.core.types.builtin.DateTime; import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText; +import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId; import org.eclipse.milo.opcua.stack.core.types.enumerated.MessageSecurityMode; import org.eclipse.milo.opcua.stack.core.types.structured.BuildInfo; import org.eclipse.milo.opcua.stack.core.util.CertificateUtil; -import org.eclipse.milo.opcua.stack.server.EndpointConfiguration; -import org.eclipse.milo.opcua.stack.server.security.DefaultServerCertificateValidator; +import org.eclipse.milo.opcua.stack.transport.server.tcp.OpcTcpServerTransport; +import org.eclipse.milo.opcua.stack.transport.server.tcp.OpcTcpServerTransportConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static org.eclipse.milo.opcua.sdk.server.api.config.OpcUaServerConfig.USER_TOKEN_POLICY_ANONYMOUS; -import static org.eclipse.milo.opcua.sdk.server.api.config.OpcUaServerConfig.USER_TOKEN_POLICY_USERNAME; -import static org.eclipse.milo.opcua.sdk.server.api.config.OpcUaServerConfig.USER_TOKEN_POLICY_X509; +import static org.eclipse.milo.opcua.sdk.server.OpcUaServerConfig.USER_TOKEN_POLICY_ANONYMOUS; +import static org.eclipse.milo.opcua.sdk.server.OpcUaServerConfig.USER_TOKEN_POLICY_USERNAME; +import static org.eclipse.milo.opcua.sdk.server.OpcUaServerConfig.USER_TOKEN_POLICY_X509; import org.apache.commons.cli.*; @@ -210,6 +220,7 @@ public static void main(String[] args) throws Exception { private final OpcUaServer server; private final Plc4xNamespace plc4xNamespace; private final String certificateFileName = "plc4x-opcuaserver.pfx"; + private final String certificateAlias = "plc4x-certificate-alias"; public OPCUAServer(String[] args) throws Exception { @@ -222,14 +233,27 @@ public OPCUAServer(String[] args) throws Exception { } logger.info("Security Directory is: {}", securityTempDir.getAbsolutePath()); // + // 1.x cert/transport bootstrap follows Milo's ExampleServer pattern. File pkiDir = FileSystems.getDefault().getPath(config.getDir()).resolve("pki").toFile(); - DefaultTrustListManager trustListManager = new DefaultTrustListManager(pkiDir); + // Keep the directory layout of the Milo 0.6 trust list ("issuers", rejected certificates directly in + // "rejected") instead of the 1.x default ("issuer", "rejected/certs"), so existing installations keep + // their issuer certificates and CRLs. + Path issuerCertsDir = Files.createDirectories(pkiDir.toPath().resolve("issuers").resolve("certs")); + Path issuerCrlDir = Files.createDirectories(pkiDir.toPath().resolve("issuers").resolve("crl")); + Path trustedCertsDir = Files.createDirectories(pkiDir.toPath().resolve("trusted").resolve("certs")); + Path trustedCrlDir = Files.createDirectories(pkiDir.toPath().resolve("trusted").resolve("crl")); + FileBasedTrustListManager trustListManager = + new FileBasedTrustListManager(issuerCertsDir, issuerCrlDir, trustedCertsDir, trustedCrlDir); + trustListManager.initialize(); logger.info("Certificate directory is: {}, Please move certificates from the reject dir to the trusted directory to allow encrypted access", pkiDir.getAbsolutePath()); - DefaultServerCertificateValidator certificateValidator = new DefaultServerCertificateValidator(trustListManager); + FileBasedCertificateQuarantine certificateQuarantine = + FileBasedCertificateQuarantine.create(pkiDir.toPath().resolve("rejected")); + + DefaultServerCertificateValidator certificateValidator = + new DefaultServerCertificateValidator(trustListManager, certificateQuarantine); UsernameIdentityValidator identityValidator = new UsernameIdentityValidator( - true, authChallenge -> { boolean check = passwordConfig.checkPassword(authChallenge.getUsername(), authChallenge.getPassword()); if (!check) { @@ -254,7 +278,7 @@ public OPCUAServer(String[] args) throws Exception { certificate = CertificateGenerator.generateCertificate(); logger.info("Creating new KeyStore at {}", serverKeyStore); keyStore.load(null, passwordConfig.getSecurityPassword().toCharArray()); - keyStore.setKeyEntry("plc4x-certificate-alias", certificate.getKeyPair().getPrivate(), passwordConfig.getSecurityPassword().toCharArray(), new X509Certificate[] { certificate.getCertificate() }); + keyStore.setKeyEntry(certificateAlias, certificate.getKeyPair().getPrivate(), passwordConfig.getSecurityPassword().toCharArray(), new X509Certificate[] { certificate.getCertificate() }); keyStore.store(new FileOutputStream(serverKeyStore), passwordConfig.getSecurityPassword().toCharArray()); } else { logger.info("Loading KeyStore at {}", serverKeyStore); @@ -271,22 +295,27 @@ public OPCUAServer(String[] args) throws Exception { StatusCodes.Bad_ConfigurationError, "certificate is missing the application URI")); - Set endpointConfigurations = new LinkedHashSet<>(); + Set endpointConfigurations = new LinkedHashSet<>(); String hostname = InetAddress.getLocalHost().getHostName(); - EndpointConfiguration.Builder builder = EndpointConfiguration.newBuilder() + EndpointConfig.Builder builder = EndpointConfig.newBuilder() .setBindAddress("0.0.0.0") .setHostname(hostname) .setPath("/plc4x") .setCertificate(certificate.getCertificate()) .setBindPort(config.getTcpPort()) + .setTransportProfile(TransportProfile.TCP_UASC_UABINARY) .setSecurityMode(MessageSecurityMode.None) .addTokenPolicies( - USER_TOKEN_POLICY_ANONYMOUS, USER_TOKEN_POLICY_USERNAME, USER_TOKEN_POLICY_X509); + // Anonymous clients can read and write every configured tag, so they are only offered when asked for. + if (config.getAllowAnonymousAccess()) { + builder.addTokenPolicies(USER_TOKEN_POLICY_ANONYMOUS); + } + endpointConfigurations.add( builder.copy() .setSecurityPolicy(SecurityPolicy.Basic256Sha256) @@ -302,14 +331,14 @@ public OPCUAServer(String[] args) throws Exception { .build() ); - EndpointConfiguration.Builder discoveryBuilder = builder.copy() + EndpointConfig.Builder discoveryBuilder = builder.copy() .setPath("/discovery") .setSecurityPolicy(SecurityPolicy.None) .setSecurityMode(MessageSecurityMode.None); endpointConfigurations.add(discoveryBuilder.build()); - EndpointConfiguration.Builder discoveryLocalBuilder = builder.copy() + EndpointConfig.Builder discoveryLocalBuilder = builder.copy() .setPath("/discovery") .setHostname("127.0.0.1") .setSecurityPolicy(SecurityPolicy.None) @@ -317,7 +346,7 @@ public OPCUAServer(String[] args) throws Exception { endpointConfigurations.add(discoveryLocalBuilder.build()); - EndpointConfiguration.Builder discoveryLocalPlc4xBuilder = builder.copy() + EndpointConfig.Builder discoveryLocalPlc4xBuilder = builder.copy() .setPath("/plc4x/discovery") .setHostname("127.0.0.1") .setSecurityPolicy(SecurityPolicy.None) @@ -326,26 +355,64 @@ public OPCUAServer(String[] args) throws Exception { endpointConfigurations.add(discoveryLocalPlc4xBuilder.build()); if (!config.getDisableInsecureEndpoint()) { - EndpointConfiguration.Builder noSecurityBuilder = builder.copy() + EndpointConfig.Builder noSecurityBuilder = builder.copy() .setSecurityPolicy(SecurityPolicy.None) .setTransportProfile(TransportProfile.TCP_UASC_UABINARY); endpointConfigurations.add(noSecurityBuilder.build()); } //Always add an unsecured endpoint to localhost, this is a work around for Milo throwing an exception if it isn't here. - EndpointConfiguration.Builder noSecurityBuilder = builder.copy() + EndpointConfig.Builder noSecurityBuilder = builder.copy() .setSecurityPolicy(SecurityPolicy.None) .setHostname("127.0.0.1") .setTransportProfile(TransportProfile.TCP_UASC_UABINARY) .setSecurityMode(MessageSecurityMode.None); endpointConfigurations.add(noSecurityBuilder.build()); - DefaultCertificateManager certificateManager = new DefaultCertificateManager( - certificate.getKeyPair(), - Arrays.stream(keyStore.getCertificateChain(keyStore.getCertificateAlias(certificate.getCertificate())))// Added so that existing certificates are loaded on startup + // In Milo 1.x the application's own certificate/key live in a CertificateStore, and the + // CertificateManager is assembled from a quarantine + an application group. We reuse the + // key pair and chain already loaded from the PKCS#12 keystore above. + final KeyPair serverKeyPair = certificate.getKeyPair(); + final String serverCertificateAlias = keyStore.getCertificateAlias(certificate.getCertificate()); + final X509Certificate[] serverCertificateChain = + Arrays.stream(keyStore.getCertificateChain(serverCertificateAlias)) .map(X509Certificate.class::cast) - .toArray(X509Certificate[]::new) - ); + .toArray(X509Certificate[]::new); + + KeyStoreCertificateStore certificateStore = new KeyStoreCertificateStore( + new KeyStoreCertificateStore.Settings( + serverKeyStore.toPath(), + () -> passwordConfig.getSecurityPassword().toCharArray(), + alias -> passwordConfig.getSecurityPassword().toCharArray())) { + // Milo 1.x looks the server certificate up as "server-rsa-sha256". Point it at the entry loaded + // above instead (the first alias of an existing keystore, as before, or certificateAlias for a + // new one), so an existing keystore is used as is instead of gaining a second entry. + @Override + protected String getAlias(NodeId certificateTypeId) { + return NodeIds.RsaSha256ApplicationCertificateType.equals(certificateTypeId) + ? serverCertificateAlias + : super.getAlias(certificateTypeId); + } + }; + certificateStore.initialize(); + + RsaSha256CertificateFactory certificateFactory = new RsaSha256CertificateFactory() { + @Override + protected KeyPair createRsaSha256KeyPair() { + return serverKeyPair; + } + + @Override + protected X509Certificate[] createRsaSha256CertificateChain(KeyPair keyPair) { + return serverCertificateChain; + } + }; + + DefaultApplicationGroup defaultApplicationGroup = DefaultApplicationGroup.createAndInitialize( + trustListManager, certificateStore, certificateFactory, certificateValidator); + + DefaultCertificateManager certificateManager = + new DefaultCertificateManager(certificateQuarantine, defaultApplicationGroup); OpcUaServerConfig serverConfig = OpcUaServerConfig.builder() .setApplicationUri(applicationUri) @@ -359,13 +426,16 @@ public OPCUAServer(String[] args) throws Exception { OpcUaServer.SDK_VERSION, "", DateTime.now())) .setCertificateManager(certificateManager) - .setTrustListManager(trustListManager) - .setCertificateValidator(certificateValidator) - .setIdentityValidator(new CompositeValidator(identityValidator, x509IdentityValidator)) + .setIdentityValidator(config.getAllowAnonymousAccess() + ? new CompositeValidator(AnonymousIdentityValidator.INSTANCE, identityValidator, x509IdentityValidator) + : new CompositeValidator(identityValidator, x509IdentityValidator)) .setProductUri("urn:eclipse:milo:plc4x:server") .build(); - server = new OpcUaServer(serverConfig); + server = new OpcUaServer(serverConfig, transportProfile -> { + OpcTcpServerTransportConfig transportConfig = OpcTcpServerTransportConfig.newBuilder().build(); + return new OpcTcpServerTransport(transportConfig); + }); plc4xNamespace = new Plc4xNamespace(server, config); plc4xNamespace.startup(); diff --git a/plc4j/tools/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/backend/Plc4xCommunication.java b/plc4j/tools/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/backend/Plc4xCommunication.java index 76cf0553a..e225b9242 100644 --- a/plc4j/tools/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/backend/Plc4xCommunication.java +++ b/plc4j/tools/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/backend/Plc4xCommunication.java @@ -29,9 +29,9 @@ import org.apache.plc4x.java.api.types.PlcValueType; import org.apache.plc4x.java.utils.cache.PlcConnectionCache; import org.eclipse.milo.opcua.sdk.server.AbstractLifecycle; -import org.eclipse.milo.opcua.sdk.server.api.DataItem; +import org.eclipse.milo.opcua.sdk.server.items.DataItem; import org.eclipse.milo.opcua.sdk.server.nodes.filters.AttributeFilterContext; -import org.eclipse.milo.opcua.stack.core.Identifiers; +import org.eclipse.milo.opcua.stack.core.NodeIds; import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue; import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId; import org.eclipse.milo.opcua.stack.core.types.builtin.StatusCode; @@ -123,42 +123,42 @@ public void removeTag(DataItem item) { public static NodeId getNodeId(PlcValueType plcValueType) { switch (plcValueType) { case BOOL: - return Identifiers.Boolean; + return NodeIds.Boolean; case BYTE: case USINT: - return Identifiers.Byte; + return NodeIds.Byte; case SINT: - return Identifiers.SByte; + return NodeIds.SByte; case INT: - return Identifiers.Int16; + return NodeIds.Int16; case WORD: case UINT: - return Identifiers.UInt16; + return NodeIds.UInt16; case DINT: - return Identifiers.Int32; + return NodeIds.Int32; case DWORD: case UDINT: - return Identifiers.UInt32; + return NodeIds.UInt32; case LINT: - return Identifiers.Int64; + return NodeIds.Int64; case ULINT: case LWORD: - return Identifiers.UInt64; + return NodeIds.UInt64; case REAL: - return Identifiers.Float; + return NodeIds.Float; case LREAL: - return Identifiers.Double; + return NodeIds.Double; case CHAR: case WCHAR: case STRING: case WSTRING: - return Identifiers.String; + return NodeIds.String; default: - return Identifiers.BaseDataType; + return NodeIds.BaseDataType; } } - public DataValue getValue(AttributeFilterContext.GetAttributeContext ctx, String tag, String connectionString) { + public DataValue getValue(AttributeFilterContext ctx, String tag, String connectionString) { PlcConnection connection = null; try { diff --git a/plc4j/tools/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/backend/Plc4xNamespace.java b/plc4j/tools/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/backend/Plc4xNamespace.java index 9185dbada..5eadedd3e 100644 --- a/plc4j/tools/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/backend/Plc4xNamespace.java +++ b/plc4j/tools/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/backend/Plc4xNamespace.java @@ -27,15 +27,14 @@ import org.eclipse.milo.opcua.sdk.core.Reference; import org.eclipse.milo.opcua.sdk.core.ValueRank; import org.eclipse.milo.opcua.sdk.server.OpcUaServer; -import org.eclipse.milo.opcua.sdk.server.api.DataItem; -import org.eclipse.milo.opcua.sdk.server.api.ManagedNamespaceWithLifecycle; -import org.eclipse.milo.opcua.sdk.server.api.MonitoredItem; -import org.eclipse.milo.opcua.sdk.server.dtd.DataTypeDictionaryManager; +import org.eclipse.milo.opcua.sdk.server.ManagedNamespaceWithLifecycle; +import org.eclipse.milo.opcua.sdk.server.items.DataItem; +import org.eclipse.milo.opcua.sdk.server.items.MonitoredItem; import org.eclipse.milo.opcua.sdk.server.nodes.UaFolderNode; import org.eclipse.milo.opcua.sdk.server.nodes.UaVariableNode; import org.eclipse.milo.opcua.sdk.server.nodes.filters.AttributeFilters; import org.eclipse.milo.opcua.sdk.server.util.SubscriptionModel; -import org.eclipse.milo.opcua.stack.core.Identifiers; +import org.eclipse.milo.opcua.stack.core.NodeIds; import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue; import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText; import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId; @@ -57,7 +56,6 @@ public class Plc4xNamespace extends ManagedNamespaceWithLifecycle { private final Configuration config; private final Logger logger = LoggerFactory.getLogger(getClass()); - private final DataTypeDictionaryManager dictionaryManager; private final SubscriptionModel subscriptionModel; private final Plc4xCommunication plc4xServer; @@ -65,9 +63,7 @@ public Plc4xNamespace(OpcUaServer server, Configuration c) { super(server, APPLICATIONID); this.config = c; subscriptionModel = new SubscriptionModel(server, this); - dictionaryManager = new DataTypeDictionaryManager(getNodeContext(), APPLICATIONID); plc4xServer = new Plc4xCommunication(); - getLifecycleManager().addLifecycle(dictionaryManager); getLifecycleManager().addLifecycle(subscriptionModel); getLifecycleManager().addLifecycle(plc4xServer); getLifecycleManager().addStartupTask(this::addNodes); @@ -88,8 +84,8 @@ private void addNodes() { folderNode.addReference(new Reference( folderNode.getNodeId(), - Identifiers.Organizes, - Identifiers.ObjectsFolder.expanded(), + NodeIds.Organizes, + NodeIds.ObjectsFolder.expanded(), false )); @@ -106,7 +102,7 @@ private void addConfiguredNodes(UaFolderNode rootNode, DeviceConfiguration c) { final String tag = item.getAddress(); Class datatype = null; - NodeId typeId = Identifiers.String; + NodeId typeId = NodeIds.String; UaVariableNode node = null; Variant variant = null; try { @@ -124,7 +120,7 @@ private void addConfiguredNodes(UaFolderNode rootNode, DeviceConfiguration c) { .setBrowseName(newQualifiedName(name)) .setDisplayName(LocalizedText.english(name)) .setDataType(typeId) - .setTypeDefinition(Identifiers.BaseDataVariableType) + .setTypeDefinition(NodeIds.BaseDataVariableType) .setValueRank(ValueRank.OneDimension.getValue()) .setArrayDimensions(new UInteger[]{uint(length)}) .build(); @@ -142,7 +138,7 @@ private void addConfiguredNodes(UaFolderNode rootNode, DeviceConfiguration c) { .setBrowseName(newQualifiedName(name)) .setDisplayName(LocalizedText.english(name)) .setDataType(typeId) - .setTypeDefinition(Identifiers.BaseDataVariableType) + .setTypeDefinition(NodeIds.BaseDataVariableType) .build(); variant = new Variant(0); } diff --git a/plc4j/tools/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/configuration/Configuration.java b/plc4j/tools/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/configuration/Configuration.java index 2059b926c..b2808dc2e 100644 --- a/plc4j/tools/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/configuration/Configuration.java +++ b/plc4j/tools/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/configuration/Configuration.java @@ -40,6 +40,9 @@ public class Configuration { @JsonProperty private final Boolean disableInsecureEndpoint = true; + @JsonProperty + private final Boolean allowAnonymousAccess = false; + @JsonProperty private List devices; @@ -61,6 +64,10 @@ public boolean getDisableInsecureEndpoint() { return disableInsecureEndpoint; } + public boolean getAllowAnonymousAccess() { + return allowAnonymousAccess; + } + public String getDir() { return dir; } diff --git a/plc4j/tools/opcua-server/src/main/resources/config.yml b/plc4j/tools/opcua-server/src/main/resources/config.yml index c17293819..068e15e1d 100644 --- a/plc4j/tools/opcua-server/src/main/resources/config.yml +++ b/plc4j/tools/opcua-server/src/main/resources/config.yml @@ -21,6 +21,8 @@ version: 0.8 #dir: "./security" name: Plc4x.OPC.UA.Server disableInsecureEndpoint: true +# true lets clients connect without credentials and read and write every tag below. +allowAnonymousAccess: false tcpPort: 12687 devices: - name: "Simulated Device" diff --git a/plc4j/tools/opcua-server/src/test/java/org/apache/plc4x/java/opcuaserver/configuration/ConfigurationTest.java b/plc4j/tools/opcua-server/src/test/java/org/apache/plc4x/java/opcuaserver/configuration/ConfigurationTest.java new file mode 100644 index 000000000..eac11809f --- /dev/null +++ b/plc4j/tools/opcua-server/src/test/java/org/apache/plc4x/java/opcuaserver/configuration/ConfigurationTest.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.plc4x.java.opcuaserver.configuration; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import org.junit.jupiter.api.Test; + +public class ConfigurationTest { + + private final ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); + + @Test + public void anonymousAccessIsOffWhenNotConfigured() throws Exception { + Configuration config = mapper.readValue("dir: \"target/test-tmp/\"\n", Configuration.class); + + assertFalse(config.getAllowAnonymousAccess()); + } + + @Test + public void anonymousAccessCanBeTurnedOn() throws Exception { + Configuration config = mapper.readValue( + "dir: \"target/test-tmp/\"\nallowAnonymousAccess: true\n", Configuration.class); + + assertTrue(config.getAllowAnonymousAccess()); + } + +}