From aa172acbb4d7a99a1ac373c9d831615d71455b90 Mon Sep 17 00:00:00 2001 From: hutcheb Date: Sun, 17 Jan 2021 06:45:46 -0500 Subject: [PATCH 1/4] Add Encryption handler for opcua server --- plc4j/integrations/opcua-server/pom.xml | 9 +- .../java/opcuaserver/KeyStoreLoader.java | 154 ------------------ .../plc4x/java/opcuaserver/OPCUAServer.java | 101 ++++++------ .../opcuaserver/backend/Plc4xNamespace.java | 19 ++- .../context/CertificateKeyPair.java | 38 +++++ .../java/opcuaserver/context/Encryption.java | 122 ++++++++++++++ 6 files changed, 234 insertions(+), 209 deletions(-) delete mode 100644 plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/KeyStoreLoader.java create mode 100644 plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/context/CertificateKeyPair.java create mode 100644 plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/context/Encryption.java diff --git a/plc4j/integrations/opcua-server/pom.xml b/plc4j/integrations/opcua-server/pom.xml index 662af42a177..ede65e245c5 100644 --- a/plc4j/integrations/opcua-server/pom.xml +++ b/plc4j/integrations/opcua-server/pom.xml @@ -183,7 +183,7 @@ runtime - + org.bouncycastle bcmail-jdk15on @@ -208,7 +208,6 @@ org.slf4j slf4j-simple - commons-io commons-io @@ -219,6 +218,11 @@ vavr + + org.apache.commons + commons-lang3 + + @@ -278,7 +282,6 @@ org.slf4j:slf4j-simple - org.bouncycastle:bcpkix-jdk15on org.bouncycastle:bcmail-jdk15on org.apache.plc4x:plc4j-driver-ab-eth diff --git a/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/KeyStoreLoader.java b/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/KeyStoreLoader.java deleted file mode 100644 index 4e4d4a7688d..00000000000 --- a/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/KeyStoreLoader.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (c) 2019 the Eclipse Milo Authors - * - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - */ - -package org.apache.plc4x.java.opcuaserver; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.security.Key; -import java.security.KeyPair; -import java.security.KeyStore; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.cert.X509Certificate; -import java.util.Arrays; -import java.util.Set; -import java.util.UUID; -import java.util.regex.Pattern; - -import com.google.common.collect.Sets; -import org.apache.plc4x.java.opcuaserver.configuration.Configuration; -import org.apache.plc4x.java.opcuaserver.configuration.PasswordConfiguration; -import org.eclipse.milo.opcua.sdk.server.util.HostnameUtil; -import org.eclipse.milo.opcua.stack.core.util.SelfSignedCertificateBuilder; -import org.eclipse.milo.opcua.stack.core.util.SelfSignedCertificateGenerator; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -class KeyStoreLoader { - - private static final Pattern IP_ADDR_PATTERN = Pattern.compile( - "^(([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.){3}([01]?\\d\\d?|2[0-4]\\d|25[0-5])$"); - - private final Logger logger = LoggerFactory.getLogger(getClass()); - - private final String certificateFileName = "plc4x-opcuaserver.pfx"; - - private X509Certificate[] serverCertificateChain; - private X509Certificate serverCertificate; - private KeyPair serverKeyPair; - - private Configuration config; - private PasswordConfiguration passwordConfig; - - public KeyStoreLoader(Configuration config, PasswordConfiguration passwordConfig, Boolean interactive) { - this.config = config; - this.passwordConfig = passwordConfig; - - File securityTempDir = new File(config.getDir(), "security"); - if (!securityTempDir.exists() && !securityTempDir.mkdirs()) { - logger.error("Unable to create directory please confirm folder permissions on " + securityTempDir.toString()); - System.exit(1); - } - logger.info("security dir: {}", securityTempDir.getAbsolutePath()); - - try { - load(securityTempDir, interactive); - } catch (Exception e) { - logger.error("Error loading the key store " + e); - System.exit(1); - } - } - - public KeyStoreLoader load(File baseDir, boolean interactive) throws Exception { - KeyStore keyStore = KeyStore.getInstance("PKCS12"); - - File serverKeyStore = baseDir.toPath().resolve(certificateFileName).toFile(); - - if (!serverKeyStore.exists()) { - if (!interactive) { - logger.info("Please re-run with the -i switch to setup the security certificate key store"); - System.exit(1); - } - - logger.info("Creating keystore at {}", serverKeyStore); - keyStore.load(null, passwordConfig.getSecurityPassword().toCharArray()); - - logger.info("Creating self signed certiciate {}", serverKeyStore); - KeyPair keyPair = SelfSignedCertificateGenerator.generateRsaKeyPair(2048); - - String applicationUri = "urn:eclipse:milo:plc4x:server" + UUID.randomUUID(); - - SelfSignedCertificateBuilder builder = new SelfSignedCertificateBuilder(keyPair) - .setCommonName(applicationUri) - .setOrganization("org.apache") - .setOrganizationalUnit("plc4x") - .setLocalityName("Wakefield") - .setStateName("MA") - .setCountryCode("US") - .setApplicationUri(applicationUri); - - // Get as many hostnames and IP addresses as we can listed in the certificate. - Set hostnames = Sets.union( - Sets.newHashSet(HostnameUtil.getHostname()), - HostnameUtil.getHostnames("0.0.0.0", false) - ); - - logger.info("using IP address/hostnames {}", hostnames.toString()); - - for (String hostname : hostnames) { - if (IP_ADDR_PATTERN.matcher(hostname).matches()) { - builder.addIpAddress(hostname); - } else { - builder.addDnsName(hostname); - } - } - - X509Certificate certificate = builder.build(); - - keyStore.setKeyEntry(config.getName(), keyPair.getPrivate(), passwordConfig.getSecurityPassword().toCharArray(), new X509Certificate[]{certificate}); - keyStore.store(new FileOutputStream(serverKeyStore), passwordConfig.getSecurityPassword().toCharArray()); - - logger.info("Self signed certificate created. Replace {} and update config file passwords if not using a signed certificate.", serverKeyStore); - - } else { - logger.info("Loading KeyStore at {}", serverKeyStore); - keyStore.load(new FileInputStream(serverKeyStore), passwordConfig.getSecurityPassword().toCharArray()); - } - - Key serverPrivateKey = keyStore.getKey(config.getName(), passwordConfig.getSecurityPassword().toCharArray()); - if (serverPrivateKey instanceof PrivateKey) { - serverCertificate = (X509Certificate) keyStore.getCertificate(config.getName()); - - serverCertificateChain = Arrays.stream(keyStore.getCertificateChain(config.getName())) - .map(X509Certificate.class::cast) - .toArray(X509Certificate[]::new); - - PublicKey serverPublicKey = serverCertificate.getPublicKey(); - serverKeyPair = new KeyPair(serverPublicKey, (PrivateKey) serverPrivateKey); - } - - return this; - } - - X509Certificate getServerCertificate() { - return serverCertificate; - } - - public X509Certificate[] getServerCertificateChain() { - return serverCertificateChain; - } - - KeyPair getServerKeyPair() { - return serverKeyPair; - } - -} diff --git a/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/OPCUAServer.java b/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/OPCUAServer.java index e8c66d1f241..2aa6bca38f1 100644 --- a/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/OPCUAServer.java +++ b/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/OPCUAServer.java @@ -1,18 +1,27 @@ /* - * Copyright (c) 2019 the Eclipse Milo Authors + * 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 * - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ + * http://www.apache.org/licenses/LICENSE-2.0 * - * SPDX-License-Identifier: EPL-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; import java.io.File; -import java.security.KeyPair; -import java.security.Security; +import java.io.FileInputStream; +import java.security.*; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.LinkedHashSet; @@ -29,6 +38,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import org.apache.plc4x.java.opcuaserver.context.CertificateKeyPair; +import org.apache.plc4x.java.opcuaserver.context.Encryption; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.eclipse.milo.opcua.sdk.server.OpcUaServer; import org.eclipse.milo.opcua.sdk.server.api.config.OpcUaServerConfig; @@ -203,17 +214,19 @@ public static void main(String[] args) throws Exception { private final OpcUaServer server; private final Plc4xNamespace plc4xNamespace; + private final String certificateFileName = "plc4x-opcuaserver.pfx"; public OPCUAServer(String[] args) throws Exception { readCommandLineArgs(args); - KeyStoreLoader loader = new KeyStoreLoader(config, passwordConfig, cmd.hasOption("interactive")); - DefaultCertificateManager certificateManager = new DefaultCertificateManager( - loader.getServerKeyPair(), - loader.getServerCertificateChain() - ); + File securityTempDir = new File(config.getDir(), "security"); + if (!securityTempDir.exists() && !securityTempDir.mkdirs()) { + logger.error("Unable to create directory please confirm folder permissions on " + securityTempDir.toString()); + System.exit(1); + } + logger.info("security dir: {}", securityTempDir.getAbsolutePath()); File pkiDir = FileSystems.getDefault().getPath(config.getDir()).resolve("pki").toFile(); DefaultTrustListManager trustListManager = new DefaultTrustListManager(pkiDir); @@ -222,12 +235,6 @@ public OPCUAServer(String[] args) throws Exception { DefaultServerCertificateValidator certificateValidator = new DefaultServerCertificateValidator(trustListManager); - KeyPair httpsKeyPair = SelfSignedCertificateGenerator.generateRsaKeyPair(2048); - - SelfSignedHttpsCertificateBuilder httpsCertificateBuilder = new SelfSignedHttpsCertificateBuilder(httpsKeyPair); - httpsCertificateBuilder.setCommonName(HostnameUtil.getHostname()); - HostnameUtil.getHostnames("0.0.0.0").forEach(httpsCertificateBuilder::addDnsName); - X509Certificate httpsCertificate = httpsCertificateBuilder.build(); UsernameIdentityValidator identityValidator = new UsernameIdentityValidator( true, @@ -240,22 +247,41 @@ public OPCUAServer(String[] args) throws Exception { } ); - X509IdentityValidator x509IdentityValidator = new X509IdentityValidator(c -> true); + KeyStore keyStore = KeyStore.getInstance("PKCS12"); + + File serverKeyStore = securityTempDir.toPath().resolve(certificateFileName).toFile(); - // If you need to use multiple certificates you'll have to be smarter than this. - X509Certificate certificate = certificateManager.getCertificates() - .stream() - .findFirst() - .orElseThrow(() -> new UaRuntimeException(StatusCodes.Bad_ConfigurationError, "no certificate found")); + X509IdentityValidator x509IdentityValidator = new X509IdentityValidator(c -> true); //Fix this + + CertificateKeyPair certificate = null; + if (!serverKeyStore.exists()) { + if (!cmd.hasOption("interactive")) { + logger.info("Please re-run with the -i switch to setup the security certificate key store"); + System.exit(1); + } + certificate = Encryption.generateCertificate(); + } else { + logger.info("Loading KeyStore at {}", serverKeyStore); + + keyStore.load(new FileInputStream(serverKeyStore), passwordConfig.getSecurityPassword().toCharArray()); + String alias = keyStore.aliases().nextElement(); + KeyPair kp = new KeyPair((PublicKey) keyStore.getCertificate(alias).getPublicKey(), + (PrivateKey) keyStore.getKey(alias, passwordConfig.getSecurityPassword().toCharArray())); + certificate = new CertificateKeyPair(kp,(X509Certificate) keyStore.getCertificate(alias)); + } - // The configured application URI must match the one in the certificate(s) String applicationUri = CertificateUtil - .getSanUri(certificate) + .getSanUri(certificate.getCertificate()) .orElseThrow(() -> new UaRuntimeException( StatusCodes.Bad_ConfigurationError, "certificate is missing the application URI")); - Set endpointConfigurations = createEndpointConfigurations(certificate); + Set endpointConfigurations = createEndpointConfigurations(certificate.getCertificate()); + + DefaultCertificateManager certificateManager = new DefaultCertificateManager( + certificate.getKeyPair(), + certificate.getCertificate() + ); OpcUaServerConfig serverConfig = OpcUaServerConfig.builder() .setApplicationUri(applicationUri) @@ -271,8 +297,6 @@ public OPCUAServer(String[] args) throws Exception { .setCertificateManager(certificateManager) .setTrustListManager(trustListManager) .setCertificateValidator(certificateValidator) - .setHttpsKeyPair(httpsKeyPair) - .setHttpsCertificate(httpsCertificate) .setIdentityValidator(new CompositeValidator(identityValidator, x509IdentityValidator)) .setProductUri("urn:eclipse:milo:plc4x:server") .build(); @@ -313,15 +337,13 @@ private Set createEndpointConfigurations(X509Certificate .setSecurityPolicy(SecurityPolicy.None) .setSecurityMode(MessageSecurityMode.None); endpointConfigurations.add(buildTcpEndpoint(noSecurityBuilder)); - endpointConfigurations.add(buildHttpsEndpoint(noSecurityBuilder)); } else { - //Always add an unsecured endpoint to localhost, this is a work around for Milo throughing an exception if it isn't here. + //Always add an unsecured endpoint to localhost, this is a work around for Milo throwing an exception if it isn't here. if (hostname.equals("127.0.0.1")) { EndpointConfiguration.Builder noSecurityBuilder = builder.copy() .setSecurityPolicy(SecurityPolicy.None) .setSecurityMode(MessageSecurityMode.None); endpointConfigurations.add(buildTcpEndpoint(noSecurityBuilder)); - endpointConfigurations.add(buildHttpsEndpoint(noSecurityBuilder)); } } @@ -332,13 +354,6 @@ private Set createEndpointConfigurations(X509Certificate .setSecurityMode(MessageSecurityMode.SignAndEncrypt)) ); - // HTTPS Basic256Sha256 / Sign (SignAndEncrypt not allowed for HTTPS) - endpointConfigurations.add(buildHttpsEndpoint( - builder.copy() - .setSecurityPolicy(SecurityPolicy.Basic256Sha256) - .setSecurityMode(MessageSecurityMode.Sign)) - ); - EndpointConfiguration.Builder discoveryBuilder = builder.copy() .setPath("/discovery") .setSecurityPolicy(SecurityPolicy.None) @@ -346,7 +361,6 @@ private Set createEndpointConfigurations(X509Certificate endpointConfigurations.add(buildTcpEndpoint(discoveryBuilder)); - endpointConfigurations.add(buildHttpsEndpoint(discoveryBuilder)); } } @@ -360,13 +374,6 @@ private EndpointConfiguration buildTcpEndpoint(EndpointConfiguration.Builder bas .build(); } - private EndpointConfiguration buildHttpsEndpoint(EndpointConfiguration.Builder base) { - return base.copy() - .setTransportProfile(TransportProfile.HTTPS_UABINARY) - .setBindPort(config.getHttpPort()) - .build(); - } - public OpcUaServer getServer() { return server; } diff --git a/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/backend/Plc4xNamespace.java b/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/backend/Plc4xNamespace.java index 536022905cc..654ac4b3690 100644 --- a/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/backend/Plc4xNamespace.java +++ b/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/backend/Plc4xNamespace.java @@ -1,11 +1,20 @@ /* - * Copyright (c) 2019 the Eclipse Milo Authors + * 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 * - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ + * http://www.apache.org/licenses/LICENSE-2.0 * - * SPDX-License-Identifier: EPL-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.backend; diff --git a/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/context/CertificateKeyPair.java b/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/context/CertificateKeyPair.java new file mode 100644 index 00000000000..0aa3a7065e9 --- /dev/null +++ b/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/context/CertificateKeyPair.java @@ -0,0 +1,38 @@ +/* + * 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 + * + * http://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.context; + +import java.security.KeyPair; +import java.security.cert.X509Certificate; + +public class CertificateKeyPair { + + private final KeyPair keyPair; + private final X509Certificate certificate; + + public CertificateKeyPair(KeyPair keyPair, X509Certificate certificate) { + this.keyPair = keyPair; + this.certificate = certificate; + } + + public KeyPair getKeyPair() { return keyPair; } + + public X509Certificate getCertificate() { return certificate; } +} diff --git a/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/context/Encryption.java b/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/context/Encryption.java new file mode 100644 index 00000000000..7fbc5ba7c51 --- /dev/null +++ b/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/context/Encryption.java @@ -0,0 +1,122 @@ +/* + * 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 + * + * http://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.context; + +import org.apache.commons.lang3.RandomUtils; +import org.bouncycastle.asn1.DERSequence; +import org.bouncycastle.asn1.x500.X500NameBuilder; +import org.bouncycastle.asn1.x500.style.BCStyle; +import org.bouncycastle.asn1.x509.Extension; +import org.bouncycastle.asn1.x509.GeneralName; +import org.bouncycastle.asn1.x509.GeneralNames; +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.cert.X509v3CertificateBuilder; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.math.BigInteger; +import java.net.InetAddress; +import java.security.*; +import java.security.cert.X509Certificate; +import java.util.Calendar; +import java.util.Date; +import java.util.Locale; + +public class Encryption { + + private static final Logger LOGGER = LoggerFactory.getLogger(Encryption.class); + private static final String APPURI = "urn:eclipse:milo:plc4x:server"; + + public static CertificateKeyPair generateCertificate() { + KeyPairGenerator kpg = null; + try { + kpg = KeyPairGenerator.getInstance("RSA"); + } catch (NoSuchAlgorithmException e) { + LOGGER.error("Security Algorithim is unsupported for certificate"); + } + kpg.initialize(2048); + KeyPair caKeys = kpg.generateKeyPair(); + KeyPair userKeys = kpg.generateKeyPair(); + + X500NameBuilder nameBuilder = new X500NameBuilder(); + + nameBuilder.addRDN(BCStyle.CN, "Apache PLC4X Driver Client"); + nameBuilder.addRDN(BCStyle.O, "Apache Software Foundation"); + nameBuilder.addRDN(BCStyle.OU, "dev"); + nameBuilder.addRDN(BCStyle.L, ""); + nameBuilder.addRDN(BCStyle.ST, "DE"); + nameBuilder.addRDN(BCStyle.C, "US"); + + BigInteger serial = new BigInteger(RandomUtils.nextBytes(40)); + + final Calendar calender = Calendar.getInstance(); + calender.add(Calendar.DATE, -1); + Date startDate = calender.getTime(); + calender.add(Calendar.DATE, 365*25); + Date expiryDate = calender.getTime(); + + KeyPairGenerator generator = null; + try { + generator = KeyPairGenerator.getInstance("RSA"); + generator.initialize(2048, new SecureRandom()); + KeyPair keyPair = generator.generateKeyPair(); + + SubjectPublicKeyInfo subjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance( + keyPair.getPublic().getEncoded() + ); + + X509v3CertificateBuilder certificateBuilder = new X509v3CertificateBuilder( + nameBuilder.build(), + serial, + startDate, + expiryDate, + Locale.ENGLISH, + nameBuilder.build(), + subjectPublicKeyInfo + ); + + GeneralName[] gnArray = new GeneralName[] {new GeneralName(GeneralName.dNSName, InetAddress.getLocalHost().getHostName()), new GeneralName(GeneralName.uniformResourceIdentifier, APPURI)}; + + + GeneralNames subjectAltNames = GeneralNames.getInstance(new DERSequence(gnArray)); + certificateBuilder.addExtension(Extension.subjectAlternativeName, false, subjectAltNames); + + ContentSigner sigGen = new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(keyPair.getPrivate()); + + X509CertificateHolder certificateHolder = certificateBuilder.build(sigGen); + + JcaX509CertificateConverter certificateConvertor = new JcaX509CertificateConverter(); + certificateConvertor.setProvider(new BouncyCastleProvider()); + + CertificateKeyPair ckp = new CertificateKeyPair(keyPair, certificateConvertor.getCertificate(certificateHolder)); + + return ckp; + + } catch (Exception e) { + LOGGER.error("Security Algorithim is unsupported for certificate"); + return null; + } + } +} From cb1f190d23f658e2354e535643dcc3b77ba7403e Mon Sep 17 00:00:00 2001 From: hutcheb Date: Tue, 19 Jan 2021 06:54:51 -0500 Subject: [PATCH 2/4] Re-Wrote OPC UA server -> Milo Interface Outstanding issue with PLC4X comms failure when adding tag that isn't valid. --- plc4j/integrations/opcua-server/pom.xml | 5 - .../plc4x/java/opcuaserver/OPCUAServer.java | 165 ++++++++---------- .../backend/Plc4xCommunication.java | 63 ++----- .../opcuaserver/backend/Plc4xNamespace.java | 104 +++-------- .../configuration/Configuration.java | 7 - .../java/opcuaserver/OpcuaPlcDriverTest.java | 75 ++++---- .../src/test/resources/config.yml | 1 - 7 files changed, 148 insertions(+), 272 deletions(-) diff --git a/plc4j/integrations/opcua-server/pom.xml b/plc4j/integrations/opcua-server/pom.xml index ede65e245c5..3313a167f83 100644 --- a/plc4j/integrations/opcua-server/pom.xml +++ b/plc4j/integrations/opcua-server/pom.xml @@ -51,11 +51,6 @@ test - - com.google.guava - guava - ${guava.version} - org.eclipse.milo diff --git a/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/OPCUAServer.java b/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/OPCUAServer.java index 2aa6bca38f1..1c6ddcbcca0 100644 --- a/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/OPCUAServer.java +++ b/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/OPCUAServer.java @@ -21,11 +21,10 @@ import java.io.File; import java.io.FileInputStream; +import java.net.InetAddress; import java.security.*; import java.security.cert.X509Certificate; -import java.util.ArrayList; import java.util.LinkedHashSet; -import java.util.List; import java.util.Set; import java.util.concurrent.CompletableFuture; @@ -46,7 +45,6 @@ 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.sdk.server.util.HostnameUtil; import org.eclipse.milo.opcua.stack.core.StatusCodes; import org.eclipse.milo.opcua.stack.core.UaRuntimeException; import org.eclipse.milo.opcua.stack.core.security.DefaultCertificateManager; @@ -58,15 +56,11 @@ 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.core.util.SelfSignedCertificateGenerator; -import org.eclipse.milo.opcua.stack.core.util.SelfSignedHttpsCertificateBuilder; import org.eclipse.milo.opcua.stack.server.EndpointConfiguration; import org.eclipse.milo.opcua.stack.server.security.DefaultServerCertificateValidator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static com.google.common.collect.Lists.newArrayList; -import org.apache.commons.lang3.RandomStringUtils; 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; @@ -205,10 +199,9 @@ private void readCommandLineArgs(String[] args) { } public static void main(String[] args) throws Exception { - OPCUAServer server = new OPCUAServer(args); - server.startup().get(); - final CompletableFuture future = new CompletableFuture<>(); - Runtime.getRuntime().addShutdownHook(new Thread(() -> future.complete(null))); + OPCUAServer serverInit = new OPCUAServer(args); + serverInit.getServer().startup().get(); + CompletableFuture future = new CompletableFuture<>(); future.get(); } @@ -220,21 +213,18 @@ public OPCUAServer(String[] args) throws Exception { readCommandLineArgs(args); - File securityTempDir = new File(config.getDir(), "security"); if (!securityTempDir.exists() && !securityTempDir.mkdirs()) { logger.error("Unable to create directory please confirm folder permissions on " + securityTempDir.toString()); System.exit(1); } - logger.info("security dir: {}", securityTempDir.getAbsolutePath()); + logger.info("Security Directory is: {}", securityTempDir.getAbsolutePath()); // File pkiDir = FileSystems.getDefault().getPath(config.getDir()).resolve("pki").toFile(); DefaultTrustListManager trustListManager = new DefaultTrustListManager(pkiDir); - logger.info("pki dir: {}", pkiDir.getAbsolutePath()); - - DefaultServerCertificateValidator certificateValidator = - new DefaultServerCertificateValidator(trustListManager); + 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); UsernameIdentityValidator identityValidator = new UsernameIdentityValidator( true, @@ -251,7 +241,7 @@ public OPCUAServer(String[] args) throws Exception { File serverKeyStore = securityTempDir.toPath().resolve(certificateFileName).toFile(); - X509IdentityValidator x509IdentityValidator = new X509IdentityValidator(c -> true); //Fix this + X509IdentityValidator x509IdentityValidator = new X509IdentityValidator(c -> true); CertificateKeyPair certificate = null; if (!serverKeyStore.exists()) { @@ -262,7 +252,6 @@ public OPCUAServer(String[] args) throws Exception { certificate = Encryption.generateCertificate(); } else { logger.info("Loading KeyStore at {}", serverKeyStore); - keyStore.load(new FileInputStream(serverKeyStore), passwordConfig.getSecurityPassword().toCharArray()); String alias = keyStore.aliases().nextElement(); KeyPair kp = new KeyPair((PublicKey) keyStore.getCertificate(alias).getPublicKey(), @@ -276,7 +265,75 @@ public OPCUAServer(String[] args) throws Exception { StatusCodes.Bad_ConfigurationError, "certificate is missing the application URI")); - Set endpointConfigurations = createEndpointConfigurations(certificate.getCertificate()); + Set endpointConfigurations = new LinkedHashSet<>(); + + String hostname = InetAddress.getLocalHost().getHostName(); + + EndpointConfiguration.Builder builder = EndpointConfiguration.newBuilder() + .setBindAddress("0.0.0.0") + .setHostname(hostname) + .setPath("/plc4x") + .setCertificate(certificate.getCertificate()) + .setBindPort(config.getTcpPort()) + .setSecurityMode(MessageSecurityMode.None) + .addTokenPolicies( + USER_TOKEN_POLICY_ANONYMOUS, + USER_TOKEN_POLICY_USERNAME, + USER_TOKEN_POLICY_X509); + + endpointConfigurations.add( + builder.copy() + .setSecurityPolicy(SecurityPolicy.Basic256Sha256) + .setSecurityMode(MessageSecurityMode.SignAndEncrypt) + .build() + ); + + endpointConfigurations.add( + builder.copy() + .setHostname("127.0.0.1") + .setSecurityPolicy(SecurityPolicy.Basic256Sha256) + .setSecurityMode(MessageSecurityMode.SignAndEncrypt) + .build() + ); + + EndpointConfiguration.Builder discoveryBuilder = builder.copy() + .setPath("/discovery") + .setSecurityPolicy(SecurityPolicy.None) + .setSecurityMode(MessageSecurityMode.None); + + endpointConfigurations.add(discoveryBuilder.build()); + + EndpointConfiguration.Builder discoveryLocalBuilder = builder.copy() + .setPath("/discovery") + .setHostname("127.0.0.1") + .setSecurityPolicy(SecurityPolicy.None) + .setSecurityMode(MessageSecurityMode.None); + + endpointConfigurations.add(discoveryLocalBuilder.build()); + + EndpointConfiguration.Builder discoveryLocalPlc4xBuilder = builder.copy() + .setPath("/plc4x/discovery") + .setHostname("127.0.0.1") + .setSecurityPolicy(SecurityPolicy.None) + .setSecurityMode(MessageSecurityMode.None); + + endpointConfigurations.add(discoveryLocalPlc4xBuilder.build()); + + if (!config.getDisableInsecureEndpoint()) { + EndpointConfiguration.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() + .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(), @@ -307,73 +364,6 @@ public OPCUAServer(String[] args) throws Exception { plc4xNamespace.startup(); } - private Set createEndpointConfigurations(X509Certificate certificate) { - Set endpointConfigurations = new LinkedHashSet<>(); - - List bindAddresses = newArrayList(); - bindAddresses.add("0.0.0.0"); - - List localAddresses = new ArrayList<>(bindAddresses); - - Set hostnames = new LinkedHashSet<>(); - hostnames.add(HostnameUtil.getHostname()); - hostnames.addAll(HostnameUtil.getHostnames("0.0.0.0")); - - for (String bindAddress : bindAddresses) { - for (String hostname : hostnames) { - EndpointConfiguration.Builder builder = EndpointConfiguration.newBuilder() - .setBindAddress(bindAddress) - .setHostname(hostname) - .setPath("/plc4x") - .setCertificate(certificate) - .addTokenPolicies( - USER_TOKEN_POLICY_ANONYMOUS, - USER_TOKEN_POLICY_USERNAME, - USER_TOKEN_POLICY_X509); - - - if (!config.getDisableInsecureEndpoint()) { - EndpointConfiguration.Builder noSecurityBuilder = builder.copy() - .setSecurityPolicy(SecurityPolicy.None) - .setSecurityMode(MessageSecurityMode.None); - endpointConfigurations.add(buildTcpEndpoint(noSecurityBuilder)); - } else { - //Always add an unsecured endpoint to localhost, this is a work around for Milo throwing an exception if it isn't here. - if (hostname.equals("127.0.0.1")) { - EndpointConfiguration.Builder noSecurityBuilder = builder.copy() - .setSecurityPolicy(SecurityPolicy.None) - .setSecurityMode(MessageSecurityMode.None); - endpointConfigurations.add(buildTcpEndpoint(noSecurityBuilder)); - } - } - - // TCP Basic256Sha256 / SignAndEncrypt - endpointConfigurations.add(buildTcpEndpoint( - builder.copy() - .setSecurityPolicy(SecurityPolicy.Basic256Sha256) - .setSecurityMode(MessageSecurityMode.SignAndEncrypt)) - ); - - EndpointConfiguration.Builder discoveryBuilder = builder.copy() - .setPath("/discovery") - .setSecurityPolicy(SecurityPolicy.None) - .setSecurityMode(MessageSecurityMode.None); - - - endpointConfigurations.add(buildTcpEndpoint(discoveryBuilder)); - } - } - - return endpointConfigurations; - } - - private EndpointConfiguration buildTcpEndpoint(EndpointConfiguration.Builder base) { - return base.copy() - .setTransportProfile(TransportProfile.TCP_UASC_UABINARY) - .setBindPort(config.getTcpPort()) - .build(); - } - public OpcUaServer getServer() { return server; } @@ -384,7 +374,6 @@ public CompletableFuture startup() { public CompletableFuture shutdown() { plc4xNamespace.shutdown(); - return server.shutdown(); } diff --git a/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/backend/Plc4xCommunication.java b/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/backend/Plc4xCommunication.java index 9e32867f49e..1c717c8e1ba 100644 --- a/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/backend/Plc4xCommunication.java +++ b/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/backend/Plc4xCommunication.java @@ -19,59 +19,15 @@ package org.apache.plc4x.java.opcuaserver.backend; - import java.lang.reflect.Array; -import java.util.List; import java.util.Arrays; -import java.util.Random; -import java.util.UUID; - -import org.eclipse.milo.opcua.sdk.core.AccessLevel; -import org.eclipse.milo.opcua.sdk.core.Reference; -import org.eclipse.milo.opcua.sdk.core.ValueRank; -import org.eclipse.milo.opcua.sdk.core.ValueRanks; -import org.eclipse.milo.opcua.sdk.server.Lifecycle; -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.DataTypeDictionaryManager; -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.model.nodes.objects.BaseEventTypeNode; -import org.eclipse.milo.opcua.sdk.server.model.nodes.objects.ServerTypeNode; -import org.eclipse.milo.opcua.sdk.server.model.nodes.variables.AnalogItemTypeNode; import org.eclipse.milo.opcua.sdk.server.nodes.filters.AttributeFilterContext; -import org.eclipse.milo.opcua.sdk.server.nodes.UaFolderNode; -import org.eclipse.milo.opcua.sdk.server.nodes.UaMethodNode; -import org.eclipse.milo.opcua.sdk.server.nodes.UaNode; -import org.eclipse.milo.opcua.sdk.server.nodes.UaObjectNode; -import org.eclipse.milo.opcua.sdk.server.nodes.UaObjectTypeNode; -import org.eclipse.milo.opcua.sdk.server.nodes.UaVariableNode; -import org.eclipse.milo.opcua.sdk.server.nodes.factories.NodeFactory; -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.AttributeId; -import org.eclipse.milo.opcua.stack.core.BuiltinDataType; import org.eclipse.milo.opcua.stack.core.Identifiers; -import org.eclipse.milo.opcua.stack.core.UaException; -import org.eclipse.milo.opcua.stack.core.types.builtin.ByteString; import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue; -import org.eclipse.milo.opcua.stack.core.types.builtin.DateTime; -import org.eclipse.milo.opcua.stack.core.types.builtin.ExtensionObject; -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.builtin.QualifiedName; import org.eclipse.milo.opcua.stack.core.types.builtin.StatusCode; import org.eclipse.milo.opcua.stack.core.types.builtin.Variant; -import org.eclipse.milo.opcua.stack.core.types.builtin.XmlElement; -import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger; -import org.eclipse.milo.opcua.stack.core.types.enumerated.StructureType; -import org.eclipse.milo.opcua.stack.core.types.structured.EnumDefinition; -import org.eclipse.milo.opcua.stack.core.types.structured.EnumDescription; -import org.eclipse.milo.opcua.stack.core.types.structured.EnumField; -import org.eclipse.milo.opcua.stack.core.types.structured.Range; -import org.eclipse.milo.opcua.stack.core.types.structured.StructureDefinition; -import org.eclipse.milo.opcua.stack.core.types.structured.StructureDescription; -import org.eclipse.milo.opcua.stack.core.types.structured.StructureField; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -81,15 +37,14 @@ import org.apache.plc4x.java.api.messages.PlcReadRequest; import org.apache.plc4x.java.api.messages.PlcReadResponse; import org.apache.plc4x.java.api.messages.PlcWriteRequest; -import org.apache.plc4x.java.api.messages.PlcWriteResponse; + import org.apache.plc4x.java.api.types.PlcResponseCode; -import org.apache.plc4x.java.api.value.PlcValue; + import org.apache.plc4x.java.utils.connectionpool.*; import org.apache.plc4x.java.api.exceptions.PlcConnectionException; -import org.apache.plc4x.java.api.exceptions.PlcRuntimeException; + import org.apache.plc4x.java.api.model.PlcField; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeUnit; @@ -98,10 +53,8 @@ import java.math.BigInteger; -import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.ubyte; -import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.uint; import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.ulong; -import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.ushort; + public class Plc4xCommunication { @@ -118,6 +71,14 @@ public Plc4xCommunication () { driverManager = new PooledPlcDriverManager(); } + public PlcDriverManager getDriverManager() { + return driverManager; + } + + public void setDriverManager(PlcDriverManager driverManager) { + this.driverManager = driverManager; + } + public PlcField getField(String tag, String connectionString) throws PlcConnectionException { return driverManager.getDriver(connectionString).prepareField(tag); } diff --git a/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/backend/Plc4xNamespace.java b/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/backend/Plc4xNamespace.java index 654ac4b3690..12e3313d95a 100644 --- a/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/backend/Plc4xNamespace.java +++ b/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/backend/Plc4xNamespace.java @@ -19,141 +19,86 @@ package org.apache.plc4x.java.opcuaserver.backend; -import org.apache.plc4x.java.opcuaserver.*; - import java.lang.reflect.Array; import java.util.Arrays; import java.util.List; -import java.util.Random; -import java.util.UUID; import org.apache.plc4x.java.opcuaserver.configuration.Configuration; import org.apache.plc4x.java.opcuaserver.configuration.DeviceConfiguration; import org.apache.plc4x.java.opcuaserver.configuration.Tag; +import org.apache.plc4x.java.utils.connectionpool.PooledPlcDriverManager; import org.eclipse.milo.opcua.sdk.core.AccessLevel; import org.eclipse.milo.opcua.sdk.core.Reference; import org.eclipse.milo.opcua.sdk.core.ValueRank; -import org.eclipse.milo.opcua.sdk.core.ValueRanks; -import org.eclipse.milo.opcua.sdk.server.Lifecycle; 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.DataTypeDictionaryManager; 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.model.nodes.objects.BaseEventTypeNode; -import org.eclipse.milo.opcua.sdk.server.model.nodes.objects.ServerTypeNode; -import org.eclipse.milo.opcua.sdk.server.model.nodes.variables.AnalogItemTypeNode; import org.eclipse.milo.opcua.sdk.server.nodes.UaFolderNode; -import org.eclipse.milo.opcua.sdk.server.nodes.UaMethodNode; -import org.eclipse.milo.opcua.sdk.server.nodes.UaNode; -import org.eclipse.milo.opcua.sdk.server.nodes.UaObjectNode; -import org.eclipse.milo.opcua.sdk.server.nodes.UaObjectTypeNode; import org.eclipse.milo.opcua.sdk.server.nodes.UaVariableNode; -import org.eclipse.milo.opcua.sdk.server.nodes.factories.NodeFactory; 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.AttributeId; -import org.eclipse.milo.opcua.stack.core.BuiltinDataType; import org.eclipse.milo.opcua.stack.core.Identifiers; -import org.eclipse.milo.opcua.stack.core.UaException; -import org.eclipse.milo.opcua.stack.core.types.builtin.ByteString; import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue; -import org.eclipse.milo.opcua.stack.core.types.builtin.DateTime; -import org.eclipse.milo.opcua.stack.core.types.builtin.ExtensionObject; 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.builtin.QualifiedName; -import org.eclipse.milo.opcua.stack.core.types.builtin.StatusCode; import org.eclipse.milo.opcua.stack.core.types.builtin.Variant; -import org.eclipse.milo.opcua.stack.core.types.builtin.XmlElement; import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger; -import org.eclipse.milo.opcua.stack.core.types.enumerated.StructureType; -import org.eclipse.milo.opcua.stack.core.types.structured.EnumDefinition; -import org.eclipse.milo.opcua.stack.core.types.structured.EnumDescription; -import org.eclipse.milo.opcua.stack.core.types.structured.EnumField; -import org.eclipse.milo.opcua.stack.core.types.structured.Range; -import org.eclipse.milo.opcua.stack.core.types.structured.StructureDefinition; -import org.eclipse.milo.opcua.stack.core.types.structured.StructureDescription; -import org.eclipse.milo.opcua.stack.core.types.structured.StructureField; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; - -import org.apache.plc4x.java.api.model.PlcField; import org.apache.plc4x.java.api.exceptions.PlcConnectionException; - -import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.ubyte; import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.uint; -import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.ulong; -import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.ushort; + public class Plc4xNamespace extends ManagedNamespaceWithLifecycle { - public static final String NAMESPACE_URI = "urn:eclipse:milo:plc4x:server"; + public static final String APPLICATIONID = "urn:eclipse:milo:plc4x:server"; private Configuration config; - private final Logger logger = LoggerFactory.getLogger(getClass()); - - private volatile Thread eventThread; - private volatile boolean keepPostingEvents = true; - - private final Random random = new Random(); - private final DataTypeDictionaryManager dictionaryManager; - private final SubscriptionModel subscriptionModel; - private Plc4xCommunication plc4xServer; public Plc4xNamespace(OpcUaServer server, Configuration c) { - super(server, NAMESPACE_URI); + super(server, APPLICATIONID); this.config = c; subscriptionModel = new SubscriptionModel(server, this); - dictionaryManager = new DataTypeDictionaryManager(getNodeContext(), NAMESPACE_URI); - + dictionaryManager = new DataTypeDictionaryManager(getNodeContext(), APPLICATIONID); plc4xServer = new Plc4xCommunication(); - getLifecycleManager().addLifecycle(dictionaryManager); getLifecycleManager().addLifecycle(subscriptionModel); - getLifecycleManager().addStartupTask(this::addNodes); } private void addNodes() { for (DeviceConfiguration c: config.getDevices()) { - createAndAddNodes(c); - } - } + NodeId folderNodeId = newNodeId(c.getName()); - private void createAndAddNodes(DeviceConfiguration c) { + UaFolderNode folderNode = new UaFolderNode( + getNodeContext(), + folderNodeId, + newQualifiedName(c.getName()), + LocalizedText.english(c.getName()) + ); - NodeId folderNodeId = newNodeId(c.getName()); + getNodeManager().addNode(folderNode); - UaFolderNode folderNode = new UaFolderNode( - getNodeContext(), - folderNodeId, - newQualifiedName(c.getName()), - LocalizedText.english(c.getName()) - ); + folderNode.addReference(new Reference( + folderNode.getNodeId(), + Identifiers.Organizes, + Identifiers.ObjectsFolder.expanded(), + false + )); - getNodeManager().addNode(folderNode); - - // Make sure our new folder shows up under the server's Objects folder. - folderNode.addReference(new Reference( - folderNode.getNodeId(), - Identifiers.Organizes, - Identifiers.ObjectsFolder.expanded(), - false - )); - - addDynamicNodes(folderNode, c); + addConfiguredNodes(folderNode, c); + } } - private void addDynamicNodes(UaFolderNode rootNode, DeviceConfiguration c) { + private void addConfiguredNodes(UaFolderNode rootNode, DeviceConfiguration c) { final List tags = c.getTags(); final String connectionString = c.getConnectionString(); for (int i = 0; i < tags.size(); i++) { @@ -238,6 +183,11 @@ private void addDynamicNodes(UaFolderNode rootNode, DeviceConfiguration c) { public void onDataItemsCreated(List dataItems) { for (DataItem item : dataItems) { plc4xServer.addField(item); + + if (plc4xServer.getDriverManager() == null) { + plc4xServer.removeField(item); + plc4xServer.setDriverManager(new PooledPlcDriverManager()); + } } subscriptionModel.onDataItemsCreated(dataItems); diff --git a/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/configuration/Configuration.java b/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/configuration/Configuration.java index 854e8c02ef1..88efb2d64a2 100644 --- a/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/configuration/Configuration.java +++ b/plc4j/integrations/opcua-server/src/main/java/org/apache/plc4x/java/opcuaserver/configuration/Configuration.java @@ -47,9 +47,6 @@ public class Configuration { @JsonProperty private Integer tcpPort = 12686; - @JsonProperty - private Integer httpPort = 8443; - public Configuration() { } @@ -73,10 +70,6 @@ public Integer getTcpPort() { return tcpPort; } - public Integer getHttpPort() { - return httpPort; - } - public List getDevices() { return devices; } diff --git a/plc4j/integrations/opcua-server/src/test/java/org/apache/plc4x/java/opcuaserver/OpcuaPlcDriverTest.java b/plc4j/integrations/opcua-server/src/test/java/org/apache/plc4x/java/opcuaserver/OpcuaPlcDriverTest.java index 256f35f7ad9..71f0f05eed9 100644 --- a/plc4j/integrations/opcua-server/src/test/java/org/apache/plc4x/java/opcuaserver/OpcuaPlcDriverTest.java +++ b/plc4j/integrations/opcua-server/src/test/java/org/apache/plc4x/java/opcuaserver/OpcuaPlcDriverTest.java @@ -27,51 +27,46 @@ Licensed to the Apache Software Foundation (ASF) under one import org.apache.plc4x.java.api.messages.PlcWriteRequest; import org.apache.plc4x.java.api.messages.PlcWriteResponse; import org.apache.plc4x.java.api.types.PlcResponseCode; -import org.apache.plc4x.java.opcua.connection.OpcuaTcpPlcConnection; import org.junit.jupiter.api.*; import java.math.BigInteger; -import java.util.HashSet; -import java.util.Set; + import org.apache.commons.io.FileUtils; import java.io.File; -import static org.apache.plc4x.java.opcua.OpcuaPlcDriver.INET_ADDRESS_PATTERN; -import static org.apache.plc4x.java.opcua.OpcuaPlcDriver.OPCUA_URI_PATTERN; -import static org.apache.plc4x.java.opcuaserver.UtilsTest.assertMatching; import static org.assertj.core.api.Assertions.fail; /** */ public class OpcuaPlcDriverTest { // Read only variables of milo example server of version 3.6 - private static final String BOOL_IDENTIFIER_READ_WRITE = "ns=2;s=Simulated_OPC_BOOL"; - private static final String BYTE_IDENTIFIER_READ_WRITE = "ns=2;s=Simulated_OPC_BYTE"; - private static final String DOUBLE_IDENTIFIER_READ_WRITE = "ns=2;s=Simulated_OPC_LREAL"; - private static final String FLOAT_IDENTIFIER_READ_WRITE = "ns=2;s=Simulated_OPC_REAL"; - private static final String INT16_IDENTIFIER_READ_WRITE = "ns=2;s=Simulated_OPC_INT"; - private static final String INT32_IDENTIFIER_READ_WRITE = "ns=2;s=Simulated_OPC_DINT"; - private static final String INT64_IDENTIFIER_READ_WRITE = "ns=2;s=Simulated_OPC_LINT"; - private static final String INTEGER_IDENTIFIER_READ_WRITE = "ns=2;s=Simulated_OPC_DINT"; - private static final String SBYTE_IDENTIFIER_READ_WRITE = "ns=2;s=Simulated_OPC_USINT"; - private static final String STRING_IDENTIFIER_READ_WRITE = "ns=2;s=Simulated_OPC_STRING"; - private static final String UINT16_IDENTIFIER_READ_WRITE = "ns=2;s=Simulated_OPC_UINT"; - private static final String UINT32_IDENTIFIER_READ_WRITE = "ns=2;s=Simulated_OPC_UDINT"; - private static final String UINT64_IDENTIFIER_READ_WRITE = "ns=2;s=Simulated_OPC_ULINT"; - private static final String UINTEGER_IDENTIFIER_READ_WRITE = "ns=2;s=Simulated_OPC_UDINT"; - private static final String DOES_NOT_EXIST_IDENTIFIER_READ_WRITE = "ns=2;i=12512623"; + private static final String BOOL_IDENTIFIER_READ_WRITE = "ns=1;s=Simulated_BOOL"; + private static final String BYTE_IDENTIFIER_READ_WRITE = "ns=1;s=Simulated_BYTE"; + private static final String DOUBLE_IDENTIFIER_READ_WRITE = "ns=1;s=Simulated_LREAL"; + private static final String FLOAT_IDENTIFIER_READ_WRITE = "ns=1;s=Simulated_REAL"; + private static final String INT16_IDENTIFIER_READ_WRITE = "ns=1;s=Simulated_INT"; + private static final String INT32_IDENTIFIER_READ_WRITE = "ns=1;s=Simulated_DINT"; + private static final String INT64_IDENTIFIER_READ_WRITE = "ns=1;s=Simulated_LINT"; + private static final String INTEGER_IDENTIFIER_READ_WRITE = "ns=1;s=Simulated_DINT"; + private static final String SBYTE_IDENTIFIER_READ_WRITE = "ns=1;s=Simulated_USINT"; + private static final String STRING_IDENTIFIER_READ_WRITE = "ns=1;s=Simulated_STRING"; + private static final String UINT16_IDENTIFIER_READ_WRITE = "ns=1;s=Simulated_UINT"; + private static final String UINT32_IDENTIFIER_READ_WRITE = "ns=1;s=Simulated_UDINT"; + private static final String UINT64_IDENTIFIER_READ_WRITE = "ns=1;s=Simulated_ULINT"; + private static final String UINTEGER_IDENTIFIER_READ_WRITE = "ns=1;s=Simulated_UDINT"; + private static final String DOES_NOT_EXIST_IDENTIFIER_READ_WRITE = "ns=1;i=12512623"; // At the moment not used in PLC4X or in the OPC UA driver - private static final String BYTE_STRING_IDENTIFIER_READ_WRITE = "ns=2;s=HelloWorld/ScalarTypes/ByteString"; - private static final String DATE_TIME_READ_WRITE = "ns=2;s=HelloWorld/ScalarTypes/DateTime"; - private static final String DURATION_READ_WRITE = "ns=2;s=HelloWorld/ScalarTypes/Duration"; - private static final String GUID_READ_WRITE = "ns=2;s=HelloWorld/ScalarTypes/Guid"; - private static final String LOCALISED_READ_WRITE = "ns=2;s=HelloWorld/ScalarTypes/LocalizedText"; - private static final String NODE_ID_READ_WRITE = "ns=2;s=HelloWorld/ScalarTypes/NodeId"; - private static final String QUALIFIED_NAM_READ_WRITE = "ns=2;s=HelloWorld/ScalarTypes/QualifiedName"; - private static final String UTC_TIME_READ_WRITE = "ns=2;s=HelloWorld/ScalarTypes/UtcTime"; - private static final String VARIANT_READ_WRITE = "ns=2;s=HelloWorld/ScalarTypes/Variant"; - private static final String XML_ELEMENT_READ_WRITE = "ns=2;s=HelloWorld/ScalarTypes/XmlElement"; + private static final String BYTE_STRING_IDENTIFIER_READ_WRITE = "ns=1;s=HelloWorld/ScalarTypes/ByteString"; + private static final String DATE_TIME_READ_WRITE = "ns=1;s=HelloWorld/ScalarTypes/DateTime"; + private static final String DURATION_READ_WRITE = "ns=1;s=HelloWorld/ScalarTypes/Duration"; + private static final String GUID_READ_WRITE = "ns=1;s=HelloWorld/ScalarTypes/Guid"; + private static final String LOCALISED_READ_WRITE = "ns=1;s=HelloWorld/ScalarTypes/LocalizedText"; + private static final String NODE_ID_READ_WRITE = "ns=1;s=HelloWorld/ScalarTypes/NodeId"; + private static final String QUALIFIED_NAM_READ_WRITE = "ns=1;s=HelloWorld/ScalarTypes/QualifiedName"; + private static final String UTC_TIME_READ_WRITE = "ns=1;s=HelloWorld/ScalarTypes/UtcTime"; + private static final String VARIANT_READ_WRITE = "ns=1;s=HelloWorld/ScalarTypes/Variant"; + private static final String XML_ELEMENT_READ_WRITE = "ns=1;s=HelloWorld/ScalarTypes/XmlElement"; // Address of local milo server private String miloLocalAddress = "127.0.0.1:12673/plc4x"; //Tcp pattern of OPC UA @@ -113,7 +108,6 @@ public static void tearDown() { @Test public void connectionNoParams(){ - connectionStringValidSet.forEach(connectionAddress -> { String connectionString = connectionAddress; try { @@ -153,8 +147,8 @@ public void connectionWithDiscoveryParam(){ } @Test - public void readVariables() { - try { + public void readVariables() throws Exception{ + PlcConnection opcuaConnection = new PlcDriverManager().getConnection(tcpConnectionAddress); assert opcuaConnection.isConnected(); @@ -198,14 +192,12 @@ public void readVariables() { opcuaConnection.close(); assert !opcuaConnection.isConnected(); - } catch (Exception e) { - fail("Exception during readVariables Test EXCEPTION: " + e.getMessage()); - } + } @Test - public void writeVariables() { - try { + public void writeVariables() throws Exception { + PlcConnection opcuaConnection = new PlcDriverManager().getConnection(tcpConnectionAddress); assert opcuaConnection.isConnected(); @@ -218,7 +210,7 @@ public void writeVariables() { builder.addItem("Int32", INT32_IDENTIFIER_READ_WRITE, 42); builder.addItem("Int64", INT64_IDENTIFIER_READ_WRITE, 42L); builder.addItem("Integer", INTEGER_IDENTIFIER_READ_WRITE, 42); - builder.addItem("SByte", SBYTE_IDENTIFIER_READ_WRITE + ":SINT", -100); + builder.addItem("SByte", SBYTE_IDENTIFIER_READ_WRITE + ":USINT", 100); builder.addItem("String", STRING_IDENTIFIER_READ_WRITE, "Helllo Toddy!"); builder.addItem("UInt16", UINT16_IDENTIFIER_READ_WRITE + ":UINT", 65535); builder.addItem("UInt32", UINT32_IDENTIFIER_READ_WRITE + ":UDINT", 100); @@ -248,9 +240,6 @@ public void writeVariables() { opcuaConnection.close(); assert !opcuaConnection.isConnected(); - } catch (Exception e) { - fail("Exception during writeVariables Test EXCEPTION: " + e.getMessage()); - } } } diff --git a/plc4j/integrations/opcua-server/src/test/resources/config.yml b/plc4j/integrations/opcua-server/src/test/resources/config.yml index d0b0cd983de..c60002e7e96 100644 --- a/plc4j/integrations/opcua-server/src/test/resources/config.yml +++ b/plc4j/integrations/opcua-server/src/test/resources/config.yml @@ -21,7 +21,6 @@ dir: "target/test-tmp/" name: Plc4x.OPC.UA.Server disableInsecureEndpoint: true tcpPort: 12673 -httpPort: 8445 devices: - name: "Simulated Device" connectionString: "simulated://127.0.0.1" From 184fafbd365c3d219cb718312995b736d1c6f548 Mon Sep 17 00:00:00 2001 From: hutcheb Date: Wed, 20 Jan 2021 05:43:13 -0500 Subject: [PATCH 3/4] Finialized OPCUA server and updated Kafka connector Fixed opcua server issue when polling errors occur. Now polling doesn't stop for other tags. Updated kafka conector based on Rankesh's comment about adding jitter to the poll return time. --- plc4j/integrations/apache-kafka/pom.xml | 5 + .../plc4x/kafka/Plc4xSinkConnector.java | 1 - .../org/apache/plc4x/kafka/Plc4xSinkTask.java | 2 - .../plc4x/kafka/Plc4xSourceConnector.java | 3 - .../apache/plc4x/kafka/Plc4xSourceTask.java | 5 +- .../backend/Plc4xCommunication.java | 170 ++++++++++-------- .../opcuaserver/backend/Plc4xNamespace.java | 2 +- 7 files changed, 108 insertions(+), 80 deletions(-) diff --git a/plc4j/integrations/apache-kafka/pom.xml b/plc4j/integrations/apache-kafka/pom.xml index 7dc6405ab50..d836c4979fc 100644 --- a/plc4j/integrations/apache-kafka/pom.xml +++ b/plc4j/integrations/apache-kafka/pom.xml @@ -266,6 +266,11 @@ runtime + + org.apache.commons + commons-lang3 + +