This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/master by this push: new 13fc914 CAMEL-14344 camel-milo - Upgrade to newer milo version (#3539) 13fc914 is described below commit 13fc9143ef6dfee9d8e1d297fd305c2a644594d4 Author: JiriOndrusek <jondr...@redhat.com> AuthorDate: Wed Feb 5 04:37:20 2020 +0100 CAMEL-14344 camel-milo - Upgrade to newer milo version (#3539) --- components/camel-milo/pom.xml | 6 + .../src/main/docs/milo-client-component.adoc | 8 +- .../src/main/docs/milo-server-component.adoc | 8 +- .../milo/client/MiloClientConfiguration.java | 4 +- .../component/milo/client/MiloClientConsumer.java | 1 - .../milo/client/internal/SubscriptionManager.java | 22 +- .../component/milo/server/MiloServerComponent.java | 297 ++++++++++++++------- .../component/milo/server/internal/CallMethod.java | 73 +++++ .../milo/server/internal/CamelNamespace.java | 163 +++-------- .../milo/server/internal/CamelServerItem.java | 7 +- .../component/milo/AbstractMiloServerTest.java | 27 +- .../milo/MonitorItemMultiConnectionsCertTest.java | 12 +- .../milo/MonitorItemMultiConnectionsTest.java | 6 +- .../camel/component/milo/MonitorItemTest.java | 6 +- .../camel/component/milo/WriteClientTest.java | 4 +- .../camel/component/milo/call/CallClientTest.java | 135 +++++++--- .../apache/camel/component/milo/call/MockCall.java | 71 ----- .../camel/component/milo/call/MockCallMethod.java | 77 ++++++ .../component/milo/call/MockCamelNamespace.java | 141 ++++++++++ .../camel/component/milo/call/MockNamespace.java | 176 ------------ .../server/ServerSetCertificateManagerTest.java | 2 +- .../camel-milo/src/test/resources/ca/cacert.pem | 32 +++ .../camel-milo/src/test/resources/cert/Makefile | 16 -- .../camel-milo/src/test/resources/cert/cert.ini | 13 - .../camel-milo/src/test/resources/cert/cert.p12 | Bin 2461 -> 0 bytes .../src/test/resources/cert/certificate.crt | 20 -- .../src/test/resources/cert/certificate.der | Bin 856 -> 0 bytes .../src/test/resources/cert/privateKey.key | 28 -- components/camel-milo/src/test/resources/keystore | Bin 0 -> 3990 bytes .../camel-milo/src/test/resources/openssl-ca.cnf | 80 ++++++ .../src/test/resources/openssl-server.cnf | 39 +++ components/camel-milo/src/test/resources/run.sh | 21 ++ .../modules/ROOT/pages/milo-client-component.adoc | 9 +- .../modules/ROOT/pages/milo-server-component.adoc | 6 +- .../modules/ROOT/pages/camel-3x-upgrade-guide.adoc | 21 ++ parent/pom.xml | 5 +- .../karaf/features/src/main/resources/features.xml | 23 +- .../apache/camel/itest/karaf/CamelMiloTest.java | 28 ++ 38 files changed, 951 insertions(+), 636 deletions(-) diff --git a/components/camel-milo/pom.xml b/components/camel-milo/pom.xml index 807ea5f..3149be6 100644 --- a/components/camel-milo/pom.xml +++ b/components/camel-milo/pom.xml @@ -55,6 +55,12 @@ <version>${milo-version}</version> </dependency> + <dependency> + <groupId>com.google.guava</groupId> + <artifactId>guava</artifactId> + <version>${milo-guava-version}</version> + </dependency> + <!-- testing --> <dependency> <groupId>org.apache.camel</groupId> diff --git a/components/camel-milo/src/main/docs/milo-client-component.adoc b/components/camel-milo/src/main/docs/milo-client-component.adoc index 705750a..d30640b 100644 --- a/components/camel-milo/src/main/docs/milo-client-component.adoc +++ b/components/camel-milo/src/main/docs/milo-client-component.adoc @@ -10,6 +10,8 @@ The Milo Client component provides access to OPC UA servers using the http://eclipse.org/milo[Eclipse Milo™] implementation. +*Java 9+*: This component requires Java 9+ at runtime. + Maven users will need to add the following dependency to their `pom.xml` for this component: @@ -52,13 +54,13 @@ The URI syntax of the endpoint is: [source] ------------------------ -milo-client:tcp://[user:password@]host:port/path/to/service?node=RAW(nsu=urn:foo:bar;s=item-1) +milo-client:opc.tcp://[user:password@]host:port/path/to/service?node=RAW(nsu=urn:foo:bar;s=item-1) ------------------------ If the server does not use a path, then it is possible to simply omit it: ------------------------ -milo-client:tcp://[user:password@]host:port?node=RAW(nsu=urn:foo:bar;s=item-1) +milo-client:opc.tcp://[user:password@]host:port?node=RAW(nsu=urn:foo:bar;s=item-1) ------------------------ If no user credentials are provided the client will switch to anonymous mode. @@ -239,7 +241,7 @@ As the values generated by the syntax cannot be transparently encoded into a URI However Camel allows to wrap the actual value inside `RAW(…)`, which makes escaping unnecessary. For example: ------------------------ -milo-client:tcp://user:password@localhost:12345?node=RAW(nsu=http://foo.bar;s=foo/bar) +milo-client:opc.tcp://user:password@localhost:12345?node=RAW(nsu=http://foo.bar;s=foo/bar) ------------------------ === Method ID diff --git a/components/camel-milo/src/main/docs/milo-server-component.adoc b/components/camel-milo/src/main/docs/milo-server-component.adoc index d70a8ae..bf62602 100644 --- a/components/camel-milo/src/main/docs/milo-server-component.adoc +++ b/components/camel-milo/src/main/docs/milo-server-component.adoc @@ -10,7 +10,7 @@ The Milo Server component provides an OPC UA server using the http://eclipse.org/milo[Eclipse Milo™] implementation. -*Java 8*: This component requires Java 8 at runtime. +*Java 9+*: This component requires Java 9+ at runtime. Maven users will need to add the following dependency to their `pom.xml` for this component: @@ -30,7 +30,7 @@ Value write requests from OPC UA Client will trigger messages which are sent int // component options: START -The OPC UA Server component supports 22 options, which are listed below. +The OPC UA Server component supports 20 options, which are listed below. @@ -39,12 +39,10 @@ The OPC UA Server component supports 22 options, which are listed below. | Name | Description | Default | Type | *namespaceUri* (common) | The URI of the namespace, defaults to urn:org:apache:camel | | String | *applicationName* (common) | The application name | | String +| *path* (common) | The path to be appended to the end of the endpoint url. (doesn't need to start with '/') | | String | *applicationUri* (common) | The application URI | | String | *productUri* (common) | The product URI | | String | *bindPort* (common) | The TCP port the server binds to | | int -| *strictEndpointUrlsEnabled* (common) | Set whether strict endpoint URLs are enforced | false | boolean -| *serverName* (common) | Server name | | String -| *hostname* (common) | Server hostname | | String | *securityPolicies* (common) | Security policies | | Set | *securityPoliciesById* (common) | Security policies by URI or name | | Collection | *userAuthenticationCredentials* (common) | Set user password combinations in the form of user1:pwd1,user2:pwd2 Usernames and passwords will be URL decoded | | String diff --git a/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/MiloClientConfiguration.java b/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/MiloClientConfiguration.java index a532657..687a88a 100644 --- a/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/MiloClientConfiguration.java +++ b/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/MiloClientConfiguration.java @@ -353,12 +353,12 @@ public class MiloClientConfiguration implements Cloneable { String adding = null; try { - adding = SecurityPolicy.fromUri(policy).getSecurityPolicyUri(); + adding = SecurityPolicy.fromUri(policy).getUri(); } catch (Exception e) { } if (adding == null) { try { - adding = SecurityPolicy.valueOf(policy).getSecurityPolicyUri(); + adding = SecurityPolicy.valueOf(policy).getUri(); } catch (Exception e) { } } diff --git a/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/MiloClientConsumer.java b/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/MiloClientConsumer.java index 515fa4b..03023da 100644 --- a/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/MiloClientConsumer.java +++ b/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/MiloClientConsumer.java @@ -28,7 +28,6 @@ import org.eclipse.milo.opcua.stack.core.types.builtin.ExpandedNodeId; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import static java.util.Objects.requireNonNull; public class MiloClientConsumer extends DefaultConsumer { diff --git a/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/internal/SubscriptionManager.java b/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/internal/SubscriptionManager.java index 0a13dbd..786854f 100644 --- a/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/internal/SubscriptionManager.java +++ b/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/internal/SubscriptionManager.java @@ -45,7 +45,7 @@ import org.eclipse.milo.opcua.sdk.client.api.identity.UsernameProvider; import org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaMonitoredItem; import org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaSubscription; import org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaSubscriptionManager.SubscriptionListener; -import org.eclipse.milo.opcua.stack.client.UaTcpStackClient; +import org.eclipse.milo.opcua.stack.client.DiscoveryClient; import org.eclipse.milo.opcua.stack.core.AttributeId; import org.eclipse.milo.opcua.stack.core.Identifiers; import org.eclipse.milo.opcua.stack.core.StatusCodes; @@ -427,10 +427,18 @@ public class SubscriptionManager { // eval enpoint - final String discoveryUri = getEndpointDiscoveryUri(); + String discoveryUri = getEndpointDiscoveryUri(); + + final URI uri = URI.create(getEndpointDiscoveryUri()); + + //milo library doesn't allow user info as a part of the uri, it has to be removed before sending to milo + final String user = uri.getUserInfo(); + if (user != null && !user.isEmpty()) { + discoveryUri = discoveryUri.replaceFirst(user + "@", ""); + } LOG.debug("Discovering endpoints from: {}", discoveryUri); - final EndpointDescription endpoint = UaTcpStackClient.getEndpoints(discoveryUri).thenApply(endpoints -> { + final EndpointDescription endpoint = DiscoveryClient.getEndpoints(discoveryUri).thenApply(endpoints -> { if (LOG.isDebugEnabled()) { LOG.debug("Found enpoints:"); for (final EndpointDescription ep : endpoints) { @@ -447,13 +455,9 @@ public class SubscriptionManager { LOG.debug("Selected endpoint: {}", endpoint); - final URI uri = URI.create(getEndpointDiscoveryUri()); - // set identity providers - final List<IdentityProvider> providers = new LinkedList<>(); - final String user = uri.getUserInfo(); if (user != null && !user.isEmpty()) { final String[] creds = user.split(":", 2); if (creds != null && creds.length == 2) { @@ -470,7 +474,7 @@ public class SubscriptionManager { // create client - final OpcUaClient client = new OpcUaClient(cfg.build()); + final OpcUaClient client = OpcUaClient.create(cfg.build()); client.connect().get(); try { @@ -530,7 +534,7 @@ public class SubscriptionManager { } } - private EndpointDescription findEndpoint(final EndpointDescription[] endpoints) throws URISyntaxException { + private EndpointDescription findEndpoint(final List<EndpointDescription> endpoints) throws URISyntaxException { final Predicate<String> allowed; final Set<String> uris = this.configuration.getAllowedSecurityPolicies(); diff --git a/components/camel-milo/src/main/java/org/apache/camel/component/milo/server/MiloServerComponent.java b/components/camel-milo/src/main/java/org/apache/camel/component/milo/server/MiloServerComponent.java index ccbbebf..3e8ce5a 100644 --- a/components/camel-milo/src/main/java/org/apache/camel/component/milo/server/MiloServerComponent.java +++ b/components/camel-milo/src/main/java/org/apache/camel/component/milo/server/MiloServerComponent.java @@ -23,12 +23,12 @@ import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.security.KeyPair; import java.security.cert.X509Certificate; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; +import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -47,23 +47,28 @@ import org.eclipse.milo.opcua.sdk.server.api.config.OpcUaServerConfigBuilder; import org.eclipse.milo.opcua.sdk.server.identity.AnonymousIdentityValidator; import org.eclipse.milo.opcua.sdk.server.identity.IdentityValidator; import org.eclipse.milo.opcua.sdk.server.identity.UsernameIdentityValidator; +import org.eclipse.milo.opcua.sdk.server.util.HostnameUtil; import org.eclipse.milo.opcua.stack.core.StatusCodes; import org.eclipse.milo.opcua.stack.core.UaException; -import org.eclipse.milo.opcua.stack.core.application.CertificateManager; -import org.eclipse.milo.opcua.stack.core.application.CertificateValidator; -import org.eclipse.milo.opcua.stack.core.application.DefaultCertificateManager; -import org.eclipse.milo.opcua.stack.core.application.DefaultCertificateValidator; +import org.eclipse.milo.opcua.stack.core.security.CertificateManager; +import org.eclipse.milo.opcua.stack.core.security.CertificateValidator; +import org.eclipse.milo.opcua.stack.core.security.DefaultCertificateManager; +import org.eclipse.milo.opcua.stack.core.security.DefaultCertificateValidator; +import org.eclipse.milo.opcua.stack.core.security.DefaultTrustListManager; 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.LocalizedText; +import org.eclipse.milo.opcua.stack.core.types.enumerated.MessageSecurityMode; import org.eclipse.milo.opcua.stack.core.types.enumerated.UserTokenType; import org.eclipse.milo.opcua.stack.core.types.structured.BuildInfo; import org.eclipse.milo.opcua.stack.core.types.structured.UserTokenPolicy; +import org.eclipse.milo.opcua.stack.server.EndpointConfiguration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - -import static java.util.Collections.singletonList; 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; /** * OPC UA Server based component @@ -75,54 +80,25 @@ public class MiloServerComponent extends DefaultComponent { private static final Logger LOG = LoggerFactory.getLogger(MiloServerComponent.class); private static final String URL_CHARSET = "UTF-8"; - private static final OpcUaServerConfig DEFAULT_SERVER_CONFIG; - - static { - final OpcUaServerConfigBuilder cfg = OpcUaServerConfig.builder(); - - cfg.setCertificateManager(new DefaultCertificateManager()); - cfg.setCertificateValidator(DenyAllCertificateValidator.INSTANCE); - cfg.setSecurityPolicies(EnumSet.allOf(SecurityPolicy.class)); - cfg.setApplicationName(LocalizedText.english("Apache Camel Milo Server")); - cfg.setApplicationUri("urn:org:apache:camel:milo:server"); - cfg.setProductUri("urn:org:apache:camel:milo"); - - if (Boolean.getBoolean("org.apache.camel.milo.server.default.enableAnonymous")) { - cfg.setUserTokenPolicies(singletonList(USER_TOKEN_POLICY_ANONYMOUS)); - cfg.setIdentityValidator(AnonymousIdentityValidator.INSTANCE); - } - - DEFAULT_SERVER_CONFIG = cfg.build(); - } - private static final class DenyAllCertificateValidator implements CertificateValidator { - public static final CertificateValidator INSTANCE = new DenyAllCertificateValidator(); - - private DenyAllCertificateValidator() { - } - - @Override - public void validate(final X509Certificate certificate) throws UaException { - throw new UaException(StatusCodes.Bad_CertificateUseNotAllowed); - } - - @Override - public void verifyTrustChain(List<X509Certificate> certificateChain) throws UaException { - throw new UaException(StatusCodes.Bad_CertificateUseNotAllowed); - } - } + private int port; private String namespaceUri = DEFAULT_NAMESPACE_URI; - private final OpcUaServerConfigBuilder serverConfig; + private OpcUaServerConfigBuilder opcServerConfig; private OpcUaServer server; + private CamelNamespace namespace; private final Map<String, MiloServerEndpoint> endpoints = new HashMap<>(); private Boolean enableAnonymousAuthentication; + private CertificateManager certificateManager; + + private Set<SecurityPolicy> securityPolicies; + private Map<String, String> userMap; private String usernameSecurityPolicyUri = OpcUaServerConfig.USER_TOKEN_POLICY_USERNAME.getSecurityPolicyUri(); @@ -133,19 +109,33 @@ public class MiloServerComponent extends DefaultComponent { private final List<Runnable> runOnStop = new LinkedList<>(); + private X509Certificate certificate; + + private String productUri; + + private String applicationUri; + + private String applicationName; + + private String path; + + private BuildInfo buildInfo; + public MiloServerComponent() { - this(DEFAULT_SERVER_CONFIG); + this.opcServerConfig = null; } public MiloServerComponent(final OpcUaServerConfig serverConfig) { - this.serverConfig = OpcUaServerConfig.copy(serverConfig != null ? serverConfig : DEFAULT_SERVER_CONFIG); + this.opcServerConfig = OpcUaServerConfig.copy(serverConfig); + } @Override protected void doStart() throws Exception { this.server = new OpcUaServer(buildServerConfig()); - this.namespace = this.server.getNamespaceManager().registerAndAdd(this.namespaceUri, index -> new CamelNamespace(index, this.namespaceUri, this.server)); + this.namespace = new CamelNamespace(this.namespaceUri, this.server); + this.namespace.startup(); super.doStart(); this.server.startup(); @@ -157,6 +147,7 @@ public class MiloServerComponent extends DefaultComponent { * @return the new server configuration, never returns {@code null} */ private OpcUaServerConfig buildServerConfig() { + OpcUaServerConfigBuilder serverConfig = this.opcServerConfig != null ? this.opcServerConfig : createDefaultConfiguration(); if (this.userMap != null || this.enableAnonymousAuthentication != null) { // set identity validator @@ -170,7 +161,7 @@ public class MiloServerComponent extends DefaultComponent { } return pwd.equals(challenge.getPassword()); }); - this.serverConfig.setIdentityValidator(identityValidator); + serverConfig.setIdentityValidator(identityValidator); // add token policies @@ -181,11 +172,9 @@ public class MiloServerComponent extends DefaultComponent { if (userMap != null) { tokenPolicies.add(getUsernamePolicy()); } - this.serverConfig.setUserTokenPolicies(tokenPolicies); - } - - if (this.bindAddresses != null) { - this.serverConfig.setBindAddresses(new ArrayList<>(this.bindAddresses)); + serverConfig.setEndpoints(createEndpointConfigurations(tokenPolicies)); + } else { + serverConfig.setEndpoints(createEndpointConfigurations(null, securityPolicies)); } if (this.certificateValidator != null) { @@ -201,12 +190,151 @@ public class MiloServerComponent extends DefaultComponent { } }); } - this.serverConfig.setCertificateValidator(validator); + serverConfig.setCertificateValidator(validator); } // build final configuration + return serverConfig.build(); + } + + private OpcUaServerConfigBuilder createDefaultConfiguration() { + final OpcUaServerConfigBuilder cfg = OpcUaServerConfig.builder(); + + cfg.setCertificateManager(new DefaultCertificateManager()); + cfg.setCertificateValidator(DenyAllCertificateValidator.INSTANCE); + cfg.setEndpoints(createEndpointConfigurations(null)); + cfg.setApplicationName(LocalizedText.english(applicationName == null ? "Apache Camel Milo Server" : applicationName)); + cfg.setApplicationUri("urn:org:apache:camel:milo:server"); + cfg.setProductUri("urn:org:apache:camel:milo"); + cfg.setCertificateManager(certificateManager); + if (productUri != null) { + cfg.setProductUri(productUri); + } + if (applicationUri != null) { + cfg.setApplicationUri(applicationUri); + } + if (buildInfo != null) { + cfg.setBuildInfo(buildInfo); + } + + if (Boolean.getBoolean("org.apache.camel.milo.server.default.enableAnonymous")) { + cfg.setIdentityValidator(AnonymousIdentityValidator.INSTANCE); + } + + return cfg; + } + + private Set<EndpointConfiguration> createEndpointConfigurations(List<UserTokenPolicy> userTokenPolicies) { + return createEndpointConfigurations(userTokenPolicies, this.securityPolicies); + } + + private Set<EndpointConfiguration> createEndpointConfigurations(List<UserTokenPolicy> userTokenPolicies, Set<SecurityPolicy> securityPolicies) { + Set<EndpointConfiguration> endpointConfigurations = new LinkedHashSet<>(); + + //if address is not defined, return empty set + if (bindAddresses == null) { + return Collections.emptySet(); + } + + for (String bindAddress : bindAddresses) { + Set<String> hostnames = new LinkedHashSet<>(); + hostnames.add(HostnameUtil.getHostname()); + hostnames.addAll(HostnameUtil.getHostnames(bindAddress)); + + boolean anonymous = (this.enableAnonymousAuthentication != null && this.enableAnonymousAuthentication) + || Boolean.getBoolean("org.apache.camel.milo.server.default.enableAnonymous"); + + UserTokenPolicy[] tokenPolicies = + userTokenPolicies != null ? userTokenPolicies.toArray(new UserTokenPolicy[userTokenPolicies.size()]) + : anonymous + ? new UserTokenPolicy[] {USER_TOKEN_POLICY_ANONYMOUS, USER_TOKEN_POLICY_USERNAME, USER_TOKEN_POLICY_X509} + : new UserTokenPolicy[] {USER_TOKEN_POLICY_USERNAME, USER_TOKEN_POLICY_X509}; + + for (String hostname : hostnames) { + EndpointConfiguration.Builder builder = EndpointConfiguration.newBuilder() + .setBindAddress(bindAddress) + .setHostname(hostname) + .setCertificate(certificate) + .setPath(this.path == null ? "" : this.path) + .addTokenPolicies(tokenPolicies); + + + if (securityPolicies == null || securityPolicies.contains(SecurityPolicy.None)) { + EndpointConfiguration.Builder noSecurityBuilder = builder.copy() + .setSecurityPolicy(SecurityPolicy.None) + .setSecurityMode(MessageSecurityMode.None); + + endpointConfigurations.add(buildTcpEndpoint(noSecurityBuilder)); + endpointConfigurations.add(buildHttpsEndpoint(noSecurityBuilder)); + } else if (securityPolicies.contains(SecurityPolicy.Basic256Sha256)) { + + // TCP Basic256Sha256 / SignAndEncrypt + endpointConfigurations.add(buildTcpEndpoint( + builder.copy() + .setSecurityPolicy(SecurityPolicy.Basic256Sha256) + .setSecurityMode(MessageSecurityMode.SignAndEncrypt)) + ); + } else if (securityPolicies.contains(SecurityPolicy.Basic256Sha256)) { + // HTTPS Basic256Sha256 / Sign (SignAndEncrypt not allowed for HTTPS) + endpointConfigurations.add(buildHttpsEndpoint( + builder.copy() + .setSecurityPolicy(SecurityPolicy.Basic256Sha256) + .setSecurityMode(MessageSecurityMode.Sign)) + ); + } + + /* + * It's good practice to provide a discovery-specific endpoint with no security. + * It's required practice if all regular endpoints have security configured. + * + * Usage of the "/discovery" suffix is defined by OPC UA Part 6: + * + * Each OPC UA Server Application implements the Discovery Service Set. If the OPC UA Server requires a + * different address for this Endpoint it shall create the address by appending the path "/discovery" to + * its base address. + */ + EndpointConfiguration.Builder discoveryBuilder = builder.copy() + .setPath("/discovery") + .setSecurityPolicy(SecurityPolicy.None) + .setSecurityMode(MessageSecurityMode.None); + + endpointConfigurations.add(buildTcpEndpoint(discoveryBuilder)); + endpointConfigurations.add(buildHttpsEndpoint(discoveryBuilder)); + } + } + + return endpointConfigurations; + } - return this.serverConfig.build(); + private EndpointConfiguration buildTcpEndpoint(EndpointConfiguration.Builder base) { + return base.copy() + .setTransportProfile(TransportProfile.TCP_UASC_UABINARY) + .setBindPort(this.port) + .build(); + } + + private EndpointConfiguration buildHttpsEndpoint(EndpointConfiguration.Builder base) { + return base.copy() + .setTransportProfile(TransportProfile.HTTPS_UABINARY) + .setBindPort(this.port) + .build(); + } + + private static final class DenyAllCertificateValidator implements CertificateValidator { + public static final CertificateValidator INSTANCE = new DenyAllCertificateValidator(); + + private DenyAllCertificateValidator() { + } + + @Override + public void validate(final X509Certificate certificate) throws UaException { + throw new UaException(StatusCodes.Bad_CertificateUseNotAllowed); + } + + @Override + public void verifyTrustChain(List<X509Certificate> certificateChain) throws UaException { + throw new UaException(StatusCodes.Bad_CertificateUseNotAllowed); + } } /** @@ -271,7 +399,15 @@ public class MiloServerComponent extends DefaultComponent { */ public void setApplicationName(final String applicationName) { Objects.requireNonNull(applicationName); - this.serverConfig.setApplicationName(LocalizedText.english(applicationName)); + this.applicationName = applicationName; + } + + /** + * The path to be appended to the end of the endpoint url. (doesn't need to start with '/') + */ + public void setPath(final String path) { + Objects.requireNonNull(path); + this.path = path; } /** @@ -279,7 +415,7 @@ public class MiloServerComponent extends DefaultComponent { */ public void setApplicationUri(final String applicationUri) { Objects.requireNonNull(applicationUri); - this.serverConfig.setApplicationUri(applicationUri); + this.applicationUri = applicationUri; } /** @@ -287,35 +423,14 @@ public class MiloServerComponent extends DefaultComponent { */ public void setProductUri(final String productUri) { Objects.requireNonNull(productUri); - this.serverConfig.setProductUri(productUri); + this.productUri = productUri; } /** * The TCP port the server binds to */ public void setBindPort(final int port) { - this.serverConfig.setBindPort(port); - } - - /** - * Set whether strict endpoint URLs are enforced - */ - public void setStrictEndpointUrlsEnabled(final boolean strictEndpointUrlsEnforced) { - this.serverConfig.setStrictEndpointUrlsEnabled(strictEndpointUrlsEnforced); - } - - /** - * Server name - */ - public void setServerName(final String serverName) { - this.serverConfig.setServerName(serverName); - } - - /** - * Server hostname - */ - public void setHostname(final String hostname) { - this.serverConfig.setServerName(hostname); + this.port = port; } /** @@ -323,9 +438,9 @@ public class MiloServerComponent extends DefaultComponent { */ public void setSecurityPolicies(final Set<SecurityPolicy> securityPolicies) { if (securityPolicies == null || securityPolicies.isEmpty()) { - this.serverConfig.setSecurityPolicies(EnumSet.noneOf(SecurityPolicy.class)); + this.securityPolicies = EnumSet.noneOf(SecurityPolicy.class); } else { - this.serverConfig.setSecurityPolicies(EnumSet.copyOf(securityPolicies)); + this.securityPolicies = EnumSet.copyOf(securityPolicies); } } @@ -342,7 +457,7 @@ public class MiloServerComponent extends DefaultComponent { } } - this.serverConfig.setSecurityPolicies(policies); + this.securityPolicies = policies; } /** @@ -392,7 +507,7 @@ public class MiloServerComponent extends DefaultComponent { * Set the {@link UserTokenPolicy} used when */ public void setUsernameSecurityPolicyUri(final SecurityPolicy usernameSecurityPolicy) { - this.usernameSecurityPolicyUri = usernameSecurityPolicy.getSecurityPolicyUri(); + this.usernameSecurityPolicyUri = usernameSecurityPolicy.getUri(); } /** @@ -417,7 +532,7 @@ public class MiloServerComponent extends DefaultComponent { * Server build info */ public void setBuildInfo(final BuildInfo buildInfo) { - this.serverConfig.setBuildInfo(buildInfo); + this.buildInfo = buildInfo; } /** @@ -440,6 +555,7 @@ public class MiloServerComponent extends DefaultComponent { * Server certificate */ public void setServerCertificate(final KeyPair keyPair, final X509Certificate certificate) { + this.certificate = certificate; setCertificateManager(new DefaultCertificateManager(keyPair, certificate)); } @@ -447,11 +563,7 @@ public class MiloServerComponent extends DefaultComponent { * Server certificate manager */ public void setCertificateManager(final CertificateManager certificateManager) { - if (certificateManager != null) { - this.serverConfig.setCertificateManager(certificateManager); - } else { - this.serverConfig.setCertificateManager(new DefaultCertificateManager()); - } + this.certificateManager = certificateManager != null ? certificateManager : new DefaultCertificateManager(); } /** @@ -465,6 +577,11 @@ public class MiloServerComponent extends DefaultComponent { * Validator for client certificates using default file based approach */ public void setDefaultCertificateValidator(final File certificatesBaseDir) { - this.certificateValidator = () -> new DefaultCertificateValidator(certificatesBaseDir); + try { + DefaultTrustListManager trustListManager = new DefaultTrustListManager(certificatesBaseDir); + this.certificateValidator = () -> new DefaultCertificateValidator(trustListManager); + } catch (IOException e) { + LOG.error("Failed to construct certificateValidator.", e); + } } } diff --git a/components/camel-milo/src/main/java/org/apache/camel/component/milo/server/internal/CallMethod.java b/components/camel-milo/src/main/java/org/apache/camel/component/milo/server/internal/CallMethod.java new file mode 100644 index 0000000..c190280 --- /dev/null +++ b/components/camel-milo/src/main/java/org/apache/camel/component/milo/server/internal/CallMethod.java @@ -0,0 +1,73 @@ +/* + * 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.camel.component.milo.server.internal; + +import org.eclipse.milo.opcua.sdk.core.ValueRanks; +import org.eclipse.milo.opcua.sdk.server.api.methods.AbstractMethodInvocationHandler; +import org.eclipse.milo.opcua.sdk.server.nodes.UaMethodNode; +import org.eclipse.milo.opcua.stack.core.Identifiers; +import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText; +import org.eclipse.milo.opcua.stack.core.types.builtin.Variant; +import org.eclipse.milo.opcua.stack.core.types.structured.Argument; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CallMethod extends AbstractMethodInvocationHandler { + + public static final Argument IN = new Argument( + "in", + Identifiers.String, + ValueRanks.Scalar, + null, + new LocalizedText("A value.") + ); + + public static final Argument OUT = new Argument( + "out", + Identifiers.String, + ValueRanks.Scalar, + null, + new LocalizedText("A value.") + ); + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + public CallMethod(UaMethodNode node) { + super(node); + } + + @Override + public Argument[] getInputArguments() { + return new Argument[]{IN}; + } + + @Override + public Argument[] getOutputArguments() { + return new Argument[]{OUT}; + } + + @Override + protected Variant[] invoke(InvocationContext invocationContext, Variant[] inputValues) { + logger.debug("Invoking sqrt() method of objectId={}", invocationContext.getObjectId()); + + String in = (String) inputValues[0].getValue(); + + return new Variant[]{new Variant("out-" + in)}; + } + +} diff --git a/components/camel-milo/src/main/java/org/apache/camel/component/milo/server/internal/CamelNamespace.java b/components/camel-milo/src/main/java/org/apache/camel/component/milo/server/internal/CamelNamespace.java index 7fdd2a9..7a166cf 100644 --- a/components/camel-milo/src/main/java/org/apache/camel/component/milo/server/internal/CamelNamespace.java +++ b/components/camel-milo/src/main/java/org/apache/camel/component/milo/server/internal/CamelNamespace.java @@ -19,157 +19,78 @@ package org.apache.camel.component.milo.server.internal; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.CompletableFuture; -import com.google.common.collect.Lists; -import org.apache.camel.component.milo.client.MiloClientConsumer; import org.eclipse.milo.opcua.sdk.core.Reference; import org.eclipse.milo.opcua.sdk.server.OpcUaServer; -import org.eclipse.milo.opcua.sdk.server.api.AccessContext; import org.eclipse.milo.opcua.sdk.server.api.DataItem; +import org.eclipse.milo.opcua.sdk.server.api.ManagedNamespace; import org.eclipse.milo.opcua.sdk.server.api.MonitoredItem; -import org.eclipse.milo.opcua.sdk.server.api.Namespace; -import org.eclipse.milo.opcua.sdk.server.api.ServerNodeMap; -import org.eclipse.milo.opcua.sdk.server.nodes.AttributeContext; -import org.eclipse.milo.opcua.sdk.server.nodes.ServerNode; import org.eclipse.milo.opcua.sdk.server.nodes.UaFolderNode; import org.eclipse.milo.opcua.sdk.server.nodes.UaObjectNode; import org.eclipse.milo.opcua.sdk.server.util.SubscriptionModel; import org.eclipse.milo.opcua.stack.core.Identifiers; -import org.eclipse.milo.opcua.stack.core.StatusCodes; -import org.eclipse.milo.opcua.stack.core.UaException; -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; 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.unsigned.UShort; -import org.eclipse.milo.opcua.stack.core.types.enumerated.NodeClass; -import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn; -import org.eclipse.milo.opcua.stack.core.types.structured.ReadValueId; -import org.eclipse.milo.opcua.stack.core.types.structured.WriteValue; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -public class CamelNamespace implements Namespace { +public class CamelNamespace extends ManagedNamespace { - private static final Logger LOG = LoggerFactory.getLogger(MiloClientConsumer.class); - - private final UShort namespaceIndex; - - private final String namespaceUri; - - private final ServerNodeMap nodeManager; private final SubscriptionModel subscriptionModel; - private final UaFolderNode folder; - private final UaObjectNode itemsObject; + private UaObjectNode itemsObject; + private UaFolderNode folder; + private final Map<String, CamelServerItem> itemMap = new HashMap<>(); - public CamelNamespace(final UShort namespaceIndex, final String namespaceUri, final OpcUaServer server) { - this.namespaceIndex = namespaceIndex; - this.namespaceUri = namespaceUri; + public CamelNamespace(final String namespaceUri, final OpcUaServer server) { + super(server, namespaceUri); - this.nodeManager = server.getNodeMap(); this.subscriptionModel = new SubscriptionModel(server, this); - - // create structure - - { - final NodeId nodeId = new NodeId(namespaceIndex, "camel"); - final QualifiedName name = new QualifiedName(namespaceIndex, "camel"); - final LocalizedText displayName = LocalizedText.english("Camel"); - - this.folder = new UaFolderNode(this.nodeManager, nodeId, name, displayName); - this.nodeManager.addNode(this.folder); - } - - { - final NodeId nodeId = new NodeId(namespaceIndex, "items"); - final QualifiedName name = new QualifiedName(namespaceIndex, "items"); - final LocalizedText displayName = LocalizedText.english("Items"); - this.itemsObject = new UaObjectNode(this.nodeManager, nodeId, name, displayName); - this.folder.addComponent(this.itemsObject); - } - - // register reference to structure - - try { - server.getUaNamespace().addReference(Identifiers.ObjectsFolder, Identifiers.Organizes, true, this.folder.getNodeId().expanded(), NodeClass.Object); - } catch (final UaException e) { - throw new RuntimeException("Failed to register folder", e); - } } @Override - public UShort getNamespaceIndex() { - return this.namespaceIndex; - } - - @Override - public String getNamespaceUri() { - return this.namespaceUri; - } - - @Override - public CompletableFuture<List<Reference>> browse(final AccessContext context, final NodeId nodeId) { - final ServerNode node = this.nodeManager.get(nodeId); - - if (node != null) { - return CompletableFuture.completedFuture(node.getReferences()); - } else { - final CompletableFuture<List<Reference>> f = new CompletableFuture<>(); - f.completeExceptionally(new UaException(StatusCodes.Bad_NodeIdUnknown)); - return f; - } - } - - @Override - public void read(final ReadContext context, final Double maxAge, final TimestampsToReturn timestamps, final List<ReadValueId> readValueIds) { - final List<DataValue> results = Lists.newArrayListWithCapacity(readValueIds.size()); - - for (final ReadValueId id : readValueIds) { - final ServerNode node = this.nodeManager.get(id.getNodeId()); - - final DataValue value; - - if (node != null) { - value = node.readAttribute(new AttributeContext(context), id.getAttributeId(), timestamps, id.getIndexRange(), null); - } else { - value = new DataValue(StatusCodes.Bad_NodeIdUnknown); - } + protected void onStartup() { + super.onStartup(); + // create structure - results.add(value); - } + final NodeId nodeId = newNodeId("camel"); + final QualifiedName name = newQualifiedName("camel"); + final LocalizedText displayName = LocalizedText.english("Camel"); - context.complete(results); - } + this.folder = new UaFolderNode(getNodeContext(), nodeId, name, displayName); + getNodeManager().addNode(this.folder); - @Override - public void write(final WriteContext context, final List<WriteValue> writeValues) { - final List<StatusCode> results = Lists.newArrayListWithCapacity(writeValues.size()); + final NodeId nodeId2 = newNodeId("items"); + final QualifiedName name2 = newQualifiedName("items"); + final LocalizedText displayName2 = LocalizedText.english("Items"); - for (final WriteValue writeValue : writeValues) { - try { - final ServerNode node = this.nodeManager.getNode(writeValue.getNodeId()).orElseThrow(() -> new UaException(StatusCodes.Bad_NodeIdUnknown)); + this.itemsObject = UaObjectNode.builder(getNodeContext()) + .setNodeId(nodeId2) + .setBrowseName(name2) + .setDisplayName(displayName2) + .setTypeDefinition(Identifiers.FolderType) + .build(); + this.folder.addComponent(this.itemsObject); + this.itemsObject.addComponent(this.folder); + this.getNodeManager().addNode(this.itemsObject); - node.writeAttribute(new AttributeContext(context), writeValue.getAttributeId(), writeValue.getValue(), writeValue.getIndexRange()); - if (LOG.isTraceEnabled()) { - final Variant variant = writeValue.getValue().getValue(); - final Object o = variant != null ? variant.getValue() : null; - LOG.trace("Wrote value={} to attributeId={} of {}", o, writeValue.getAttributeId(), writeValue.getNodeId()); - } - - results.add(StatusCode.GOOD); - } catch (final UaException e) { - results.add(e.getStatusCode()); - } - } + // register reference to structure - context.complete(results); + folder.addReference(new Reference( + folder.getNodeId(), + Identifiers.Organizes, + Identifiers.ObjectsFolder.expanded(), + false + )); + + itemsObject.addReference(new Reference( + nodeId, + Identifiers.HasComponent, + Identifiers.ObjectNode.expanded(), + Reference.Direction.INVERSE + )); } @Override @@ -196,7 +117,7 @@ public class CamelNamespace implements Namespace { synchronized (this) { CamelServerItem item = this.itemMap.get(itemId); if (item == null) { - item = new CamelServerItem(itemId, this.nodeManager, this.namespaceIndex, this.itemsObject); + item = new CamelServerItem(itemId, getNodeContext(), getNamespaceIndex(), this.itemsObject); this.itemMap.put(itemId, item); } return item; diff --git a/components/camel-milo/src/main/java/org/apache/camel/component/milo/server/internal/CamelServerItem.java b/components/camel-milo/src/main/java/org/apache/camel/component/milo/server/internal/CamelServerItem.java index c1db1eb..fa74c02 100644 --- a/components/camel-milo/src/main/java/org/apache/camel/component/milo/server/internal/CamelServerItem.java +++ b/components/camel-milo/src/main/java/org/apache/camel/component/milo/server/internal/CamelServerItem.java @@ -23,7 +23,7 @@ import java.util.concurrent.CopyOnWriteArraySet; import java.util.function.Consumer; import org.eclipse.milo.opcua.sdk.core.AccessLevel; -import org.eclipse.milo.opcua.sdk.server.api.ServerNodeMap; +import org.eclipse.milo.opcua.sdk.server.nodes.UaNodeContext; import org.eclipse.milo.opcua.sdk.server.nodes.UaObjectNode; import org.eclipse.milo.opcua.sdk.server.nodes.UaVariableNode; import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue; @@ -49,7 +49,7 @@ public class CamelServerItem { private final Set<Consumer<DataValue>> listeners = new CopyOnWriteArraySet<>(); private DataValue value = new DataValue(StatusCode.BAD); - public CamelServerItem(final String itemId, final ServerNodeMap nodeManager, final UShort namespaceIndex, final UaObjectNode baseNode) { + public CamelServerItem(final String itemId, final UaNodeContext nodeContext, final UShort namespaceIndex, final UaObjectNode baseNode) { this.itemId = itemId; this.baseNode = baseNode; @@ -60,7 +60,7 @@ public class CamelServerItem { // create variable node - this.item = new UaVariableNode(nodeManager, nodeId, qname, displayName) { + this.item = new UaVariableNode(nodeContext, nodeId, qname, displayName) { @Override public synchronized DataValue getValue() { @@ -79,6 +79,7 @@ public class CamelServerItem { this.item.setUserAccessLevel(ubyte(AccessLevel.getMask(AccessLevel.READ_WRITE))); baseNode.addComponent(this.item); + nodeContext.getNodeManager().addNode(this.item); } public void dispose() { diff --git a/components/camel-milo/src/test/java/org/apache/camel/component/milo/AbstractMiloServerTest.java b/components/camel-milo/src/test/java/org/apache/camel/component/milo/AbstractMiloServerTest.java index 35902b0..ab12493 100644 --- a/components/camel-milo/src/test/java/org/apache/camel/component/milo/AbstractMiloServerTest.java +++ b/components/camel-milo/src/test/java/org/apache/camel/component/milo/AbstractMiloServerTest.java @@ -27,6 +27,7 @@ import org.apache.camel.test.AvailablePortFinder; import org.apache.camel.test.junit4.CamelTestSupport; import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy; import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue; +import org.junit.Assume; public abstract class AbstractMiloServerTest extends CamelTestSupport { @@ -34,6 +35,7 @@ public abstract class AbstractMiloServerTest extends CamelTestSupport { @Override protected void doPreSetup() throws Exception { + Assume.assumeTrue("Requires java 9+", isJavaVersionSatisfied(9)); super.doPreSetup(); this.serverPort = AvailablePortFinder.getNextAvailable(); } @@ -112,9 +114,10 @@ public abstract class AbstractMiloServerTest extends CamelTestSupport { try { final KeyStoreLoader loader = new KeyStoreLoader(); - loader.setUrl("file:src/test/resources/cert/cert.p12"); - loader.setKeyStorePassword("pwd1"); - loader.setKeyPassword("pwd1"); + loader.setUrl("file:src/test/resources/keystore"); + loader.setKeyStorePassword("testtest"); + + loader.setKeyPassword("test"); return loader.load(); } catch (final GeneralSecurityException | IOException e) { throw new RuntimeException(e); @@ -122,4 +125,22 @@ public abstract class AbstractMiloServerTest extends CamelTestSupport { } + /** + * Return true, if java version (defined by method getRequiredJavaVersion()) is satisfied. + * Works for java versions 9+ + */ + boolean isJavaVersionSatisfied(int requiredVersion) { + String version = System.getProperty("java.version"); + if (!version.startsWith("1.")) { + int dot = version.indexOf("."); + if (dot != -1) { + version = version.substring(0, dot); + } + if (Integer.parseInt(version) >= requiredVersion) { + return true; + } + } + return false; + } + } diff --git a/components/camel-milo/src/test/java/org/apache/camel/component/milo/MonitorItemMultiConnectionsCertTest.java b/components/camel-milo/src/test/java/org/apache/camel/component/milo/MonitorItemMultiConnectionsCertTest.java index 1c10522..5b3bcfcf 100644 --- a/components/camel-milo/src/test/java/org/apache/camel/component/milo/MonitorItemMultiConnectionsCertTest.java +++ b/components/camel-milo/src/test/java/org/apache/camel/component/milo/MonitorItemMultiConnectionsCertTest.java @@ -43,18 +43,18 @@ public class MonitorItemMultiConnectionsCertTest extends AbstractMiloServerTest private static final String MILO_SERVER_ITEM_1 = "milo-server:myitem1"; // with key - private static final String MILO_CLIENT_ITEM_C1_1 = "milo-client:tcp://foo:bar@localhost:@@port@@?node=" + private static final String MILO_CLIENT_ITEM_C1_1 = "milo-client:opc.tcp://foo:bar@localhost:@@port@@?node=" + NodeIds.nodeValue(MiloServerComponent.DEFAULT_NAMESPACE_URI, "items-myitem1") - + "&keyStoreUrl=file:src/test/resources/cert/cert.p12&keyStorePassword=pwd1&keyPassword=pwd1" + + "&keyStoreUrl=file:src/test/resources/keystore&keyStorePassword=testtest&keyPassword=test&keyAlias=test" + "&discoveryEndpointSuffix=/discovery&overrideHost=true"; // with wrong password - private static final String MILO_CLIENT_ITEM_C2_1 = "milo-client:tcp://foo:bar2@localhost:@@port@@?node=" + private static final String MILO_CLIENT_ITEM_C2_1 = "milo-client:opc.tcp://foo:bar2@localhost:@@port@@?node=" + NodeIds.nodeValue(MiloServerComponent.DEFAULT_NAMESPACE_URI, "items-myitem1") + "&discoveryEndpointSuffix=/discovery&overrideHost=true"; // without key, clientId=1 - private static final String MILO_CLIENT_ITEM_C3_1 = "milo-client:tcp://foo:bar@localhost:@@port@@?clientId=1&node=" + private static final String MILO_CLIENT_ITEM_C3_1 = "milo-client:opc.tcp://foo:bar@localhost:@@port@@?clientId=1&node=" + NodeIds.nodeValue(MiloServerComponent.DEFAULT_NAMESPACE_URI, "items-myitem1") + "&discoveryEndpointSuffix=/discovery&overrideHost=true"; @@ -79,10 +79,10 @@ public class MonitorItemMultiConnectionsCertTest extends AbstractMiloServerTest super.configureMiloServer(server); final Path baseDir = Paths.get("target/testing/cert/default"); - final Path trusted = baseDir.resolve("trusted"); + final Path trusted = baseDir.resolve("trusted/certs"); Files.createDirectories(trusted); - Files.copy(Paths.get("src/test/resources/cert/certificate.der"), trusted.resolve("certificate.der"), REPLACE_EXISTING); + Files.copy(Paths.get("src/test/resources/ca/cacert.pem"), trusted.resolve("cacert.pem"), REPLACE_EXISTING); server.setServerCertificate(loadDefaultTestKey()); server.setDefaultCertificateValidator(baseDir.toFile()); diff --git a/components/camel-milo/src/test/java/org/apache/camel/component/milo/MonitorItemMultiConnectionsTest.java b/components/camel-milo/src/test/java/org/apache/camel/component/milo/MonitorItemMultiConnectionsTest.java index dcccaf9..116cf98 100644 --- a/components/camel-milo/src/test/java/org/apache/camel/component/milo/MonitorItemMultiConnectionsTest.java +++ b/components/camel-milo/src/test/java/org/apache/camel/component/milo/MonitorItemMultiConnectionsTest.java @@ -34,13 +34,13 @@ public class MonitorItemMultiConnectionsTest extends AbstractMiloServerTest { private static final String MILO_SERVER_ITEM_1 = "milo-server:myitem1"; - private static final String MILO_CLIENT_ITEM_C1_1 = "milo-client:tcp://foo:bar@localhost:@@port@@?node=" + private static final String MILO_CLIENT_ITEM_C1_1 = "milo-client:opc.tcp://foo:bar@localhost:@@port@@?node=" + NodeIds.nodeValue(MiloServerComponent.DEFAULT_NAMESPACE_URI, "items-myitem1") + "&overrideHost=true"; - private static final String MILO_CLIENT_ITEM_C2_1 = "milo-client:tcp://foo:bar2@localhost:@@port@@?node=" + private static final String MILO_CLIENT_ITEM_C2_1 = "milo-client:opc.tcp://foo:bar2@localhost:@@port@@?node=" + NodeIds.nodeValue(MiloServerComponent.DEFAULT_NAMESPACE_URI, "items-myitem1") + "&overrideHost=true"; - private static final String MILO_CLIENT_ITEM_C3_1 = "milo-client:tcp://foo2:bar@localhost:@@port@@?node=" + private static final String MILO_CLIENT_ITEM_C3_1 = "milo-client:opc.tcp://foo2:bar@localhost:@@port@@?node=" + NodeIds.nodeValue(MiloServerComponent.DEFAULT_NAMESPACE_URI, "items-myitem1") + "&overrideHost=true"; private static final String MOCK_TEST_1 = "mock:test1"; diff --git a/components/camel-milo/src/test/java/org/apache/camel/component/milo/MonitorItemTest.java b/components/camel-milo/src/test/java/org/apache/camel/component/milo/MonitorItemTest.java index b4d9b3e..3b3ba6d 100644 --- a/components/camel-milo/src/test/java/org/apache/camel/component/milo/MonitorItemTest.java +++ b/components/camel-milo/src/test/java/org/apache/camel/component/milo/MonitorItemTest.java @@ -34,9 +34,9 @@ public class MonitorItemTest extends AbstractMiloServerTest { private static final String MILO_SERVER_ITEM_1 = "milo-server:myitem1"; - private static final String MILO_CLIENT_ITEM_C1_1 = "milo-client:tcp://foo:bar@localhost:@@port@@?node=" - + NodeIds.nodeValue(MiloServerComponent.DEFAULT_NAMESPACE_URI, "items-myitem1") - + "&allowedSecurityPolicies=None&overrideHost=true"; + private static final String MILO_CLIENT_ITEM_C1_1 = "milo-client:opc.tcp://foo:bar@localhost:@@port@@?node=" + + NodeIds.nodeValue(MiloServerComponent.DEFAULT_NAMESPACE_URI, "items-myitem1") + + "&allowedSecurityPolicies=None&overrideHost=true"; private static final String MOCK_TEST_1 = "mock:test1"; diff --git a/components/camel-milo/src/test/java/org/apache/camel/component/milo/WriteClientTest.java b/components/camel-milo/src/test/java/org/apache/camel/component/milo/WriteClientTest.java index 43214b3..9a0942c 100644 --- a/components/camel-milo/src/test/java/org/apache/camel/component/milo/WriteClientTest.java +++ b/components/camel-milo/src/test/java/org/apache/camel/component/milo/WriteClientTest.java @@ -41,8 +41,8 @@ public class WriteClientTest extends AbstractMiloServerTest { private static final String MILO_SERVER_ITEM_1 = "milo-server:myitem1"; private static final String MILO_SERVER_ITEM_2 = "milo-server:myitem2"; - private static final String MILO_CLIENT_BASE_C1 = "milo-client:tcp://foo:bar@localhost:@@port@@"; - private static final String MILO_CLIENT_BASE_C2 = "milo-client:tcp://foo2:bar2@localhost:@@port@@"; + private static final String MILO_CLIENT_BASE_C1 = "milo-client:opc.tcp://foo:bar@localhost:@@port@@"; + private static final String MILO_CLIENT_BASE_C2 = "milo-client:opc.tcp://foo2:bar2@localhost:@@port@@"; private static final String MILO_CLIENT_ITEM_C1_1 = MILO_CLIENT_BASE_C1 + "?node=" + nodeValue(MiloServerComponent.DEFAULT_NAMESPACE_URI, "items-myitem1") + "&overrideHost=true"; diff --git a/components/camel-milo/src/test/java/org/apache/camel/component/milo/call/CallClientTest.java b/components/camel-milo/src/test/java/org/apache/camel/component/milo/call/CallClientTest.java index 56ef9ee..db70850 100644 --- a/components/camel-milo/src/test/java/org/apache/camel/component/milo/call/CallClientTest.java +++ b/components/camel-milo/src/test/java/org/apache/camel/component/milo/call/CallClientTest.java @@ -18,29 +18,34 @@ package org.apache.camel.component.milo.call; import java.util.Arrays; import java.util.EnumSet; -import java.util.LinkedList; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Set; import org.apache.camel.Produce; import org.apache.camel.ProducerTemplate; import org.apache.camel.RoutesBuilder; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.milo.AbstractMiloServerTest; -import org.apache.camel.component.milo.call.MockCall.Call1; +import org.apache.camel.component.milo.NodeIds; 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.api.config.OpcUaServerConfigBuilder; -import org.eclipse.milo.opcua.sdk.server.identity.AnonymousIdentityValidator; -import org.eclipse.milo.opcua.sdk.server.nodes.UaMethodNode; -import org.eclipse.milo.opcua.stack.core.application.DefaultCertificateManager; -import org.eclipse.milo.opcua.stack.core.application.InsecureCertificateValidator; +import org.eclipse.milo.opcua.sdk.server.util.HostnameUtil; +import org.eclipse.milo.opcua.stack.core.security.DefaultCertificateManager; +import org.eclipse.milo.opcua.stack.core.security.InsecureCertificateValidator; import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy; -import org.junit.After; +import org.eclipse.milo.opcua.stack.core.transport.TransportProfile; +import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText; +import org.eclipse.milo.opcua.stack.core.types.enumerated.MessageSecurityMode; +import org.eclipse.milo.opcua.stack.core.types.structured.UserTokenPolicy; +import org.eclipse.milo.opcua.stack.server.EndpointConfiguration; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import static org.apache.camel.component.milo.NodeIds.nodeValue; +import static org.eclipse.milo.opcua.sdk.server.api.config.OpcUaServerConfig.USER_TOKEN_POLICY_ANONYMOUS; /** * Unit tests for calling from the client side @@ -49,17 +54,27 @@ public class CallClientTest extends AbstractMiloServerTest { private static final String DIRECT_START_1 = "direct:start1"; - private static final String MILO_CLIENT_BASE_C1 = "milo-client:tcp://localhost:@@port@@"; + private static final String MILO_CLIENT_BASE_C1 = "milo-client:opc.tcp://localhost:@@port@@"; - private static final String MILO_CLIENT_ITEM_C1_1 = MILO_CLIENT_BASE_C1 + "?node=" + nodeValue(MockNamespace.URI, MockNamespace.FOLDER_ID) + "&method=" - + nodeValue(MockNamespace.URI, "id1") + "&overrideHost=true"; - - @Produce(DIRECT_START_1) - protected ProducerTemplate producer1; + private static final String MILO_CLIENT_ITEM_C1_1 = MILO_CLIENT_BASE_C1 + "?node=" + NodeIds.nodeValue(MockCamelNamespace.URI, MockCamelNamespace.FOLDER_ID) + + "&method=" + nodeValue(MockCamelNamespace.URI, MockCamelNamespace.CALL_ID) + "&overrideHost=true"; private OpcUaServer server; + private MockCamelNamespace namespace; + private MockCallMethod callMethod; - private Call1 call1; + @Produce(DIRECT_START_1) + private ProducerTemplate producer1; + + @Override + protected RoutesBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from(DIRECT_START_1).to(resolve(MILO_CLIENT_ITEM_C1_1)); + } + }; + } @Override protected boolean isAddServer() { @@ -68,43 +83,75 @@ public class CallClientTest extends AbstractMiloServerTest { @Before public void start() throws Exception { - final OpcUaServerConfigBuilder config = new OpcUaServerConfigBuilder(); - config.setBindAddresses(Arrays.asList("localhost")); - config.setBindPort(getServerPort()); - config.setIdentityValidator(AnonymousIdentityValidator.INSTANCE); - config.setUserTokenPolicies(Arrays.asList(OpcUaServerConfig.USER_TOKEN_POLICY_ANONYMOUS)); - config.setSecurityPolicies(EnumSet.of(SecurityPolicy.None)); - config.setCertificateManager(new DefaultCertificateManager()); - config.setCertificateValidator(new InsecureCertificateValidator()); + final OpcUaServerConfigBuilder cfg = OpcUaServerConfig.builder(); + + cfg.setCertificateManager(new DefaultCertificateManager()); + cfg.setEndpoints(createEndpointConfigurations(Arrays.asList(OpcUaServerConfig.USER_TOKEN_POLICY_ANONYMOUS), EnumSet.of(SecurityPolicy.None))); + cfg.setApplicationName(LocalizedText.english("Apache Camel Milo Server")); + cfg.setApplicationUri("urn:mock:namespace"); + cfg.setProductUri("urn:org:apache:camel:milo"); + cfg.setCertificateManager(new DefaultCertificateManager()); + cfg.setCertificateValidator(new InsecureCertificateValidator()); + + this.server = new OpcUaServer(cfg.build()); - this.server = new OpcUaServer(config.build()); + this.namespace = new MockCamelNamespace(this.server, node -> callMethod = new MockCallMethod(node)); + this.namespace.startup(); + this.server.startup().get(); + } - this.call1 = new MockCall.Call1(); + private Set<EndpointConfiguration> createEndpointConfigurations(List<UserTokenPolicy> userTokenPolicies, Set<SecurityPolicy> securityPolicies) { + Set<EndpointConfiguration> endpointConfigurations = new LinkedHashSet<>(); - this.server.getNamespaceManager().registerAndAdd(MockNamespace.URI, index -> { + String bindAddress = "0.0.0.0"; + Set<String> hostnames = new LinkedHashSet<>(); + hostnames.add(HostnameUtil.getHostname()); + hostnames.addAll(HostnameUtil.getHostnames(bindAddress)); - final List<UaMethodNode> methods = new LinkedList<>(); - methods.add(MockCall.fromNode(index, this.server.getNodeMap(), "id1", "name1", this.call1)); + UserTokenPolicy[] tokenPolicies = new UserTokenPolicy[] {USER_TOKEN_POLICY_ANONYMOUS}; - return new MockNamespace(index, this.server, methods); - }); + for (String hostname : hostnames) { + EndpointConfiguration.Builder builder = EndpointConfiguration.newBuilder() + .setBindAddress(bindAddress) + .setHostname(hostname) + .setCertificate(() -> null) + .addTokenPolicies(tokenPolicies); - this.server.startup().get(); - } - @After - public void stop() { - this.server.shutdown(); - } + if (securityPolicies == null || securityPolicies.contains(SecurityPolicy.None)) { + EndpointConfiguration.Builder noSecurityBuilder = builder.copy() + .setSecurityPolicy(SecurityPolicy.None) + .setSecurityMode(MessageSecurityMode.None); - @Override - protected RoutesBuilder createRouteBuilder() throws Exception { - return new RouteBuilder() { - @Override - public void configure() throws Exception { - from(DIRECT_START_1).to(resolve(MILO_CLIENT_ITEM_C1_1)); + endpointConfigurations.add(buildTcpEndpoint(noSecurityBuilder)); } - }; + /* + * It's good practice to provide a discovery-specific endpoint with no security. + * It's required practice if all regular endpoints have security configured. + * + * Usage of the "/discovery" suffix is defined by OPC UA Part 6: + * + * Each OPC UA Server Application implements the Discovery Service Set. If the OPC UA Server requires a + * different address for this Endpoint it shall create the address by appending the path "/discovery" to + * its base address. + */ + + 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(getServerPort()) + .build(); } @Test @@ -115,8 +162,8 @@ public class CallClientTest extends AbstractMiloServerTest { doCall(this.producer1, "bar"); // assert - - Assert.assertArrayEquals(new Object[] {"foo", "bar"}, this.call1.calls.toArray()); + Assert.assertNotNull(this.callMethod); + Assert.assertArrayEquals(new Object[] {"out-foo", "out-bar"}, this.callMethod.getCalls().toArray()); } private static void doCall(final ProducerTemplate producerTemplate, final Object input) { diff --git a/components/camel-milo/src/test/java/org/apache/camel/component/milo/call/MockCall.java b/components/camel-milo/src/test/java/org/apache/camel/component/milo/call/MockCall.java deleted file mode 100644 index 51408c2..0000000 --- a/components/camel-milo/src/test/java/org/apache/camel/component/milo/call/MockCall.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * 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.camel.component.milo.call; - -import java.util.LinkedList; -import java.util.List; - -import org.eclipse.milo.opcua.sdk.server.annotations.UaInputArgument; -import org.eclipse.milo.opcua.sdk.server.annotations.UaMethod; -import org.eclipse.milo.opcua.sdk.server.annotations.UaOutputArgument; -import org.eclipse.milo.opcua.sdk.server.api.ServerNodeMap; -import org.eclipse.milo.opcua.sdk.server.nodes.UaMethodNode; -import org.eclipse.milo.opcua.sdk.server.util.AnnotationBasedInvocationHandler; -import org.eclipse.milo.opcua.sdk.server.util.AnnotationBasedInvocationHandler.InvocationContext; -import org.eclipse.milo.opcua.sdk.server.util.AnnotationBasedInvocationHandler.Out; -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.unsigned.UInteger; -import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UShort; - -import static org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText.english; - -public final class MockCall { - - private MockCall() { - } - - // in: str1[String], out: out1[String] - public static class Call1 { - - public List<String> calls = new LinkedList<>(); - - @UaMethod - public void call(final InvocationContext context, @UaInputArgument(name = "in1") - final String in1, @UaOutputArgument(name = "out1") - final Out<String> out1) { - this.calls.add(in1); - out1.set("out-" + in1); - } - } - - public static UaMethodNode fromNode(final UShort index, final ServerNodeMap nodeMap, final String nodeId, final String name, final Object methodObject) { - - try { - final UaMethodNode method = new UaMethodNode(nodeMap, new NodeId(index, nodeId), new QualifiedName(index, name), english(name), english(nodeId), UInteger.MIN, - UInteger.MIN, true, true); - - final AnnotationBasedInvocationHandler handler = AnnotationBasedInvocationHandler.fromAnnotatedObject(nodeMap, methodObject); - method.setInputArguments(handler.getInputArguments()); - method.setOutputArguments(handler.getOutputArguments()); - method.setInvocationHandler(handler); - return method; - } catch (Exception e) { - throw new RuntimeException(e); - } - } -} diff --git a/components/camel-milo/src/test/java/org/apache/camel/component/milo/call/MockCallMethod.java b/components/camel-milo/src/test/java/org/apache/camel/component/milo/call/MockCallMethod.java new file mode 100644 index 0000000..fe52c8c --- /dev/null +++ b/components/camel-milo/src/test/java/org/apache/camel/component/milo/call/MockCallMethod.java @@ -0,0 +1,77 @@ +/* + * 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.camel.component.milo.call; + +import java.util.LinkedList; +import java.util.List; + +import org.eclipse.milo.opcua.sdk.core.ValueRanks; +import org.eclipse.milo.opcua.sdk.server.api.methods.AbstractMethodInvocationHandler; +import org.eclipse.milo.opcua.sdk.server.nodes.UaMethodNode; +import org.eclipse.milo.opcua.stack.core.Identifiers; +import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText; +import org.eclipse.milo.opcua.stack.core.types.builtin.Variant; +import org.eclipse.milo.opcua.stack.core.types.structured.Argument; + +public class MockCallMethod extends AbstractMethodInvocationHandler { + + public static final Argument IN = new Argument( + "in", + Identifiers.String, + ValueRanks.Scalar, + null, + new LocalizedText("A value.") + ); + + public static final Argument OUT = new Argument( + "out", + Identifiers.String, + ValueRanks.Scalar, + null, + new LocalizedText("A value.") + ); + + public List<String> calls = new LinkedList<>(); + + public MockCallMethod(UaMethodNode node) { + super(node); + } + + @Override + public Argument[] getInputArguments() { + return new Argument[]{IN}; + } + + @Override + public Argument[] getOutputArguments() { + return new Argument[]{OUT}; + } + + @Override + protected Variant[] invoke(InvocationContext invocationContext, Variant[] inputValues) { + String in = (String) inputValues[0].getValue(); + + calls.add("out-" + in); + + return new Variant[]{new Variant("out-" + in)}; + } + + public List<String> getCalls() { + return calls; + } +} diff --git a/components/camel-milo/src/test/java/org/apache/camel/component/milo/call/MockCamelNamespace.java b/components/camel-milo/src/test/java/org/apache/camel/component/milo/call/MockCamelNamespace.java new file mode 100644 index 0000000..3197485 --- /dev/null +++ b/components/camel-milo/src/test/java/org/apache/camel/component/milo/call/MockCamelNamespace.java @@ -0,0 +1,141 @@ +/* + * 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.camel.component.milo.call; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +import org.apache.camel.component.milo.client.MiloClientConsumer; +import org.apache.camel.component.milo.server.internal.CamelServerItem; +import org.eclipse.milo.opcua.sdk.core.Reference; +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.ManagedNamespace; +import org.eclipse.milo.opcua.sdk.server.api.MonitoredItem; +import org.eclipse.milo.opcua.sdk.server.api.methods.AbstractMethodInvocationHandler; +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.util.SubscriptionModel; +import org.eclipse.milo.opcua.stack.core.Identifiers; +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.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class MockCamelNamespace extends ManagedNamespace { + + public static final String URI = "urn:org:apache:camel:mock"; + public static final int FOLDER_ID = 1; + public static final int CALL_ID = 2; + + private static final Logger LOG = LoggerFactory.getLogger(MiloClientConsumer.class); + + private final SubscriptionModel subscriptionModel; + + private final Function<UaMethodNode, AbstractMethodInvocationHandler> callMethodCreator; + + private UaFolderNode folder; + + private final Map<String, CamelServerItem> itemMap = new HashMap<>(); + + public MockCamelNamespace(final OpcUaServer server, Function<UaMethodNode, AbstractMethodInvocationHandler> callMethodCreator) { + super(server, URI); + + this.subscriptionModel = new SubscriptionModel(server, this); + this.callMethodCreator = callMethodCreator; + } + + @Override + protected void onStartup() { + super.onStartup(); + // create structure + + final NodeId nodeId = newNodeId(FOLDER_ID); + final QualifiedName name = newQualifiedName("camel"); + final LocalizedText displayName = LocalizedText.english("Camel"); + + this.folder = new UaFolderNode(getNodeContext(), nodeId, name, displayName); + getNodeManager().addNode(this.folder); + + // register reference to structure + + folder.addReference(new Reference( + folder.getNodeId(), + Identifiers.Organizes, + Identifiers.ObjectsFolder.expanded(), + false + )); + + addCallMethod(folder); + } + + private void addCallMethod(UaFolderNode folderNode) { + UaMethodNode methodNode = UaMethodNode.builder(getNodeContext()) + .setNodeId(new NodeId(getNamespaceIndex(), CALL_ID)) + .setBrowseName(newQualifiedName("call")) + .setDisplayName(new LocalizedText(null, "call")) + .setDescription( + LocalizedText.english("Returns the \"out-\"+entry parameter")) + .build(); + + AbstractMethodInvocationHandler callMethod = callMethodCreator.apply(methodNode); + methodNode.setProperty(UaMethodNode.InputArguments, callMethod.getInputArguments()); + methodNode.setProperty(UaMethodNode.OutputArguments, callMethod.getOutputArguments()); + methodNode.setInvocationHandler(callMethod); + + getNodeManager().addNode(methodNode); + + methodNode.addReference(new Reference( + methodNode.getNodeId(), + Identifiers.HasComponent, + folderNode.getNodeId().expanded(), + false + )); + + methodNode.addReference(new Reference( + methodNode.getNodeId(), + Identifiers.HasComponent, + folderNode.getNodeId().expanded(), + folderNode.getNodeClass(), + false + )); + } + + + @Override + public void onDataItemsCreated(final List<DataItem> dataItems) { + this.subscriptionModel.onDataItemsCreated(dataItems); + } + + @Override + public void onDataItemsModified(final List<DataItem> dataItems) { + this.subscriptionModel.onDataItemsModified(dataItems); + } + + @Override + public void onDataItemsDeleted(final List<DataItem> dataItems) { + this.subscriptionModel.onDataItemsDeleted(dataItems); + } + + @Override + public void onMonitoringModeChanged(final List<MonitoredItem> monitoredItems) { + this.subscriptionModel.onMonitoringModeChanged(monitoredItems); + } +} diff --git a/components/camel-milo/src/test/java/org/apache/camel/component/milo/call/MockNamespace.java b/components/camel-milo/src/test/java/org/apache/camel/component/milo/call/MockNamespace.java deleted file mode 100644 index 1acd8de..0000000 --- a/components/camel-milo/src/test/java/org/apache/camel/component/milo/call/MockNamespace.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * 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.camel.component.milo.call; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; - -import org.eclipse.milo.opcua.sdk.core.Reference; -import org.eclipse.milo.opcua.sdk.server.OpcUaServer; -import org.eclipse.milo.opcua.sdk.server.api.AccessContext; -import org.eclipse.milo.opcua.sdk.server.api.DataItem; -import org.eclipse.milo.opcua.sdk.server.api.MethodInvocationHandler; -import org.eclipse.milo.opcua.sdk.server.api.MonitoredItem; -import org.eclipse.milo.opcua.sdk.server.api.Namespace; -import org.eclipse.milo.opcua.sdk.server.api.ServerNodeMap; -import org.eclipse.milo.opcua.sdk.server.model.nodes.objects.FolderNode; -import org.eclipse.milo.opcua.sdk.server.nodes.AttributeContext; -import org.eclipse.milo.opcua.sdk.server.nodes.ServerNode; -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.util.SubscriptionModel; -import org.eclipse.milo.opcua.stack.core.Identifiers; -import org.eclipse.milo.opcua.stack.core.StatusCodes; -import org.eclipse.milo.opcua.stack.core.UaException; -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; -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.unsigned.UShort; -import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn; -import org.eclipse.milo.opcua.stack.core.types.structured.ReadValueId; -import org.eclipse.milo.opcua.stack.core.types.structured.WriteValue; - -import static java.util.stream.Collectors.toList; - -public class MockNamespace implements Namespace { - - public static final int FOLDER_ID = 1; - - public static final String URI = "urn:mock:namespace"; - - private final UShort index; - - private final ServerNodeMap nodeMap; - private final SubscriptionModel subscriptionModel; - - public MockNamespace(final UShort index, final OpcUaServer server, List<UaMethodNode> methods) { - this.index = index; - this.nodeMap = server.getNodeMap(); - this.subscriptionModel = new SubscriptionModel(server, this); - - registerItems(methods); - } - - private void registerItems(List<UaMethodNode> methods) { - - // create a folder - - final UaFolderNode folder = new UaFolderNode(this.nodeMap, new NodeId(this.index, FOLDER_ID), new QualifiedName(this.index, "FooBarFolder"), - LocalizedText.english("Foo Bar Folder")); - - // add our folder to the objects folder - - this.nodeMap.getNode(Identifiers.ObjectsFolder).ifPresent(node -> { - ((FolderNode)node).addComponent(folder); - }); - - // add method calls - - methods.forEach(folder::addComponent); - } - - // default method implementations follow - - @Override - public void read(final ReadContext context, final Double maxAge, final TimestampsToReturn timestamps, final List<ReadValueId> readValueIds) { - - final List<DataValue> results = new ArrayList<>(readValueIds.size()); - - for (final ReadValueId id : readValueIds) { - final ServerNode node = this.nodeMap.get(id.getNodeId()); - - final DataValue value = node != null ? node.readAttribute(new AttributeContext(context), id.getAttributeId()) : new DataValue(StatusCodes.Bad_NodeIdUnknown); - - results.add(value); - } - - // report back with result - - context.complete(results); - } - - @Override - public void write(final WriteContext context, final List<WriteValue> writeValues) { - - final List<StatusCode> results = writeValues.stream().map(value -> { - if (this.nodeMap.containsKey(value.getNodeId())) { - return new StatusCode(StatusCodes.Bad_NotWritable); - } else { - return new StatusCode(StatusCodes.Bad_NodeIdUnknown); - } - }).collect(toList()); - - // report back with result - - context.complete(results); - } - - @Override - public CompletableFuture<List<Reference>> browse(final AccessContext context, final NodeId nodeId) { - final ServerNode node = this.nodeMap.get(nodeId); - - if (node != null) { - return CompletableFuture.completedFuture(node.getReferences()); - } else { - final CompletableFuture<List<Reference>> f = new CompletableFuture<>(); - f.completeExceptionally(new UaException(StatusCodes.Bad_NodeIdUnknown)); - return f; - } - } - - @Override - public Optional<MethodInvocationHandler> getInvocationHandler(final NodeId methodId) { - return Optional.ofNullable(this.nodeMap.get(methodId)).filter(n -> n instanceof UaMethodNode).flatMap(n -> { - final UaMethodNode m = (UaMethodNode)n; - return m.getInvocationHandler(); - }); - } - - @Override - public void onDataItemsCreated(final List<DataItem> dataItems) { - this.subscriptionModel.onDataItemsCreated(dataItems); - } - - @Override - public void onDataItemsModified(final List<DataItem> dataItems) { - this.subscriptionModel.onDataItemsModified(dataItems); - } - - @Override - public void onDataItemsDeleted(final List<DataItem> dataItems) { - this.subscriptionModel.onDataItemsDeleted(dataItems); - } - - @Override - public void onMonitoringModeChanged(final List<MonitoredItem> monitoredItems) { - this.subscriptionModel.onMonitoringModeChanged(monitoredItems); - } - - @Override - public UShort getNamespaceIndex() { - return this.index; - } - - @Override - public String getNamespaceUri() { - return URI; - } -} diff --git a/components/camel-milo/src/test/java/org/apache/camel/component/milo/server/ServerSetCertificateManagerTest.java b/components/camel-milo/src/test/java/org/apache/camel/component/milo/server/ServerSetCertificateManagerTest.java index fe58263..f4ea7f4 100644 --- a/components/camel-milo/src/test/java/org/apache/camel/component/milo/server/ServerSetCertificateManagerTest.java +++ b/components/camel-milo/src/test/java/org/apache/camel/component/milo/server/ServerSetCertificateManagerTest.java @@ -38,7 +38,7 @@ public class ServerSetCertificateManagerTest extends AbstractMiloServerTest { final Path trusted = baseDir.resolve("trusted"); Files.createDirectories(trusted); - Files.copy(Paths.get("src/test/resources/cert/certificate.der"), trusted.resolve("certificate.der"), REPLACE_EXISTING); + Files.copy(Paths.get("src/test/resources/ca/cacert.pem"), trusted.resolve("cacert.pem"), REPLACE_EXISTING); server.setServerCertificate(loadDefaultTestKey()); server.setDefaultCertificateValidator(baseDir.toFile()); diff --git a/components/camel-milo/src/test/resources/ca/cacert.pem b/components/camel-milo/src/test/resources/ca/cacert.pem new file mode 100644 index 0000000..f23452c --- /dev/null +++ b/components/camel-milo/src/test/resources/ca/cacert.pem @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFeTCCA2GgAwIBAgIJAIoc1wRX1g4hMA0GCSqGSIb3DQEBCwUAMEsxCzAJBgNV +BAYTAlJIMQswCQYDVQQIDAJSSDELMAkGA1UEBwwCUkgxEDAOBgNVBAoMB1Rlc3Qg +Q0ExEDAOBgNVBAMMB1Rlc3QgQ0EwHhcNMjAwMTI4MTYxNTA0WhcNMjAwMjI3MTYx +NTA0WjBLMQswCQYDVQQGEwJSSDELMAkGA1UECAwCUkgxCzAJBgNVBAcMAlJIMRAw +DgYDVQQKDAdUZXN0IENBMRAwDgYDVQQDDAdUZXN0IENBMIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEAvk1N3IMWVqJIlMuFkD2XiPiEPk5m7ToriB0ThNyY +IUKVayEoFOyU82ciycERJHVgo64BxQ3e4BqBakyarI4ssbiI6hm8onWzjCBPy5U+ +RQxK/oDoK3vn1uCQRKbdjkJvbbmbTVlWrpTGmNBflw+OlTGSswcWxc3q0KrKYmm0 +eGcvL6ZNy+ciTPXRyE7ccHR87yP+CIs8l4O4j5QavcIYH6PSHtMU6XUFIZGpj9vI +6PbKu2MPK1+QRL+zevf5Q3l5tdjHavh0NDW8QcCa/te7qc7gQvYttFzYMuzvNqeZ +OL3iCWJVKG5Atxo9JvSsb0IbDXqnGQrRqYoHwk64bT2Hs3G5pwBLSoO+HrKqfaS3 +c5KINGrfCVcfrKIHyA3l/RLIhlhzkxj1Mvq2sP8Jt0IfZNpXjBiXw6zH76Biozgq +gdTNypOz3an1OkfQRcsXjjaI/EKLxcTAV5DQR2++OI6eFfalVjbdmWCvOBOXa3tN +UtxACL/kNbjAegRzZTZyTy/uA1nj3E2FdDDGqUnxiNtXBVQ40s4xoi2w0sdWQ3ZL +ntLQ3NhLJw8UCCUMMQZEwghAeyXGqS7GuAszoPDBzjkJyI456sR8E5mapjwwVbMz +nCGfeEgNoRSMuAteJwS/nw2M9ApxgqYan+683cV2pIU8wGtkRzYxvRXMswKIBx83 +ksUCAwEAAaNgMF4wHQYDVR0OBBYEFOS5A19qy9nHzu0q5pXrpdT4vGXbMB8GA1Ud +IwQYMBaAFOS5A19qy9nHzu0q5pXrpdT4vGXbMA8GA1UdEwEB/wQFMAMBAf8wCwYD +VR0PBAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQBr3dLjF8MLJTGW92UejISaCLft +jqyXonrHLi3snrC2rW6OkGD35Mk+ylpuYHSzCl258P5W8J9A9snRgzL6pqXw2ZPB +GLjGyge4PKvzH25O8kCgAuvyNBPPDKMdhhMzaL1qsr8zveUIYxMtJMVZuzpYFD8u +2/MZQ6yf8PVVsnEPlrjg2ByUEiSBNAnmqC3zFnR90Zv2F8jQyhCZyZf/57lGDS4c +5ap8mFhwxY6gZbzv/LW8r13hT7QbGkgl6yildg1/+Ozn8RkRoYNcZ6r7Nl0Z6TL3 +Vs4vab0cuketxIdpzld5FW3b+DBjzpR1pdzGtnm3ultSBHD75vxBeKI3//K1Ar1G +U8NAuWIKEEOirhsm23XBowmwEcbKD8AlHjfZVDGs1kWvhdu09RY1BHYt0YBIbirE +g+F537+MUr5l934eQxIpOiHyJmiqNnfC4l9e4tUIKfX64yWoqu5davEIBI0x9mKe +Ug8uofPCm6U78S6jANauze6X9r8T8bDm56y9yztQXVf1mZxTgoHRspX6wt89YQVJ +VZzrWyRsMvPDGWevH809CHJaOFMjS23ffF5KML9Gzf/XShRQWCNkE85T55W8FgS0 +Kt0m9ZRPWEGulbsO85ETjssatI1xKKwRNOQIWdk625sci42PSgK/q8Avufe0rRbH +398+r4PDI8gaMQKG4A== +-----END CERTIFICATE----- diff --git a/components/camel-milo/src/test/resources/cert/Makefile b/components/camel-milo/src/test/resources/cert/Makefile deleted file mode 100644 index b44c850..0000000 --- a/components/camel-milo/src/test/resources/cert/Makefile +++ /dev/null @@ -1,16 +0,0 @@ -.PHONY: clean all show - -all: - openssl req -batch -x509 -sha256 -nodes -days 36500 \ - -subj '/C=XX/L=End of the universe/O=Milliways' \ - -config cert.ini \ - -newkey rsa:2048 -keyout privateKey.key -out certificate.crt - openssl pkcs12 -password pass:pwd1 -export -out cert.p12 -inkey privateKey.key -in certificate.crt - openssl x509 -outform der -in certificate.crt -out certificate.der - -show: - openssl x509 -in certificate.crt -text -noout - -clean: - @-rm privateKey.key certificate.crt cert.p12 certificate.der - diff --git a/components/camel-milo/src/test/resources/cert/cert.ini b/components/camel-milo/src/test/resources/cert/cert.ini deleted file mode 100644 index 7eaaed2..0000000 --- a/components/camel-milo/src/test/resources/cert/cert.ini +++ /dev/null @@ -1,13 +0,0 @@ -[req] -x509_extensions = v3_req -distinguished_name = req_distinguished_name - -[req_distinguished_name] - -[v3_req] -basicConstraints = CA:FALSE -keyUsage = digitalSignature, keyEncipherment -subjectAltName = @alt_names - -[alt_names] -URI.1 = http://camel.apache.org/EclipseMilo/Client diff --git a/components/camel-milo/src/test/resources/cert/cert.p12 b/components/camel-milo/src/test/resources/cert/cert.p12 deleted file mode 100644 index 8a2eed9..0000000 Binary files a/components/camel-milo/src/test/resources/cert/cert.p12 and /dev/null differ diff --git a/components/camel-milo/src/test/resources/cert/certificate.crt b/components/camel-milo/src/test/resources/cert/certificate.crt deleted file mode 100644 index 8b7c784..0000000 --- a/components/camel-milo/src/test/resources/cert/certificate.crt +++ /dev/null @@ -1,20 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDVDCCAjygAwIBAgIJAKvxBeV3q1AsMA0GCSqGSIb3DQEBCwUAMD8xCzAJBgNV -BAYTAlhYMRwwGgYDVQQHExNFbmQgb2YgdGhlIHVuaXZlcnNlMRIwEAYDVQQKEwlN -aWxsaXdheXMwIBcNMTYwNzIxMTMyODAwWhgPMjExNjA2MjcxMzI4MDBaMD8xCzAJ -BgNVBAYTAlhYMRwwGgYDVQQHExNFbmQgb2YgdGhlIHVuaXZlcnNlMRIwEAYDVQQK -EwlNaWxsaXdheXMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDYISUG -ggjH/3KuUHLjUvDWNomCDzMqTA4QrlK0cxt0VZq0DmzP1xEtAhXJ6eXVsBsfbvzW -F/AK1otAP67wsGz52edbzchW0TVvKd3CAKBH8LFgycu7k6bYdc+0crgyxeM1NUmC -x1nxbfTMWSC1CWOzjZXOVlqyUIHY8HJetPpJpS0GizAfa+eCHaRXOyC89dUX0cS8 -370NbcqfrYvbd8LOosHLYqJ/x7P7YIxOkw/wn5m2OAeSfItqfNCLbR2+oiCrLLv0 -MXQVCiiYfPLoEePFb288/AkgI/1go4Lsh6/vBnTU03r/YKM1QHVxygax3+I/9cXp -wNmV8HyDNo5Lu98bAgMBAAGjUTBPMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgWgMDUG -A1UdEQQuMCyGKmh0dHA6Ly9jYW1lbC5hcGFjaGUub3JnL0VjbGlwc2VNaWxvL0Ns -aWVudDANBgkqhkiG9w0BAQsFAAOCAQEAWplx3EQyl69Xrn73v55sPa8mlBKMjjJ3 -FmloVkUoYO8k8xciPDRHSVaeKYwU3dO4NBwPnbo0JMZWEaYr9SsVWbLRsfqu3pGt -ScHp7n1GB8gkstoX3cuqzVF0UQCkSsfUNXGCfQVQbQwJg8hBU77WTflDbcrGMbxf -PgTkwOv8qfNjPawu4j05/9SKoauKoNVQ1nHS7D3tkzoPxh+0efOhrhOPXtB/C9yH -QKIMFzsh0uLlzNZiMURjTZo/asZ9RdCplUzd3ciQDZk6QxW8DIrOfySUMuWU1UDa -1/qA0w7xg+1azBRl3oiUwNxOmHHVeWqonhbvYlG+GG/MjQHcg62NHg== ------END CERTIFICATE----- diff --git a/components/camel-milo/src/test/resources/cert/certificate.der b/components/camel-milo/src/test/resources/cert/certificate.der deleted file mode 100644 index 37abde7..0000000 Binary files a/components/camel-milo/src/test/resources/cert/certificate.der and /dev/null differ diff --git a/components/camel-milo/src/test/resources/cert/privateKey.key b/components/camel-milo/src/test/resources/cert/privateKey.key deleted file mode 100644 index 5900120..0000000 --- a/components/camel-milo/src/test/resources/cert/privateKey.key +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDYISUGggjH/3Ku -UHLjUvDWNomCDzMqTA4QrlK0cxt0VZq0DmzP1xEtAhXJ6eXVsBsfbvzWF/AK1otA -P67wsGz52edbzchW0TVvKd3CAKBH8LFgycu7k6bYdc+0crgyxeM1NUmCx1nxbfTM -WSC1CWOzjZXOVlqyUIHY8HJetPpJpS0GizAfa+eCHaRXOyC89dUX0cS8370Nbcqf -rYvbd8LOosHLYqJ/x7P7YIxOkw/wn5m2OAeSfItqfNCLbR2+oiCrLLv0MXQVCiiY -fPLoEePFb288/AkgI/1go4Lsh6/vBnTU03r/YKM1QHVxygax3+I/9cXpwNmV8HyD -No5Lu98bAgMBAAECggEBAJdMLJUvtmH7axan7qVATKRIrV5EsbasYzQ+NFtqMQ/x -VUkyx+1/SuDNEt+0Q1ah33rTwV9Ghp2vru+dJSQM/VyytAlKNzK/Zb6Z+klzEsEJ -t8JfwaVgKW5imrJhlJzGdtWqpflNAKPIK5RZ2FGjbw4k0XgOb5NgVGW/fPDblFK0 -ar1Yc/FLWOYFWFfNoZhni+ZH4U2KmEjZlkIL+d4KgewrjSPJ9w6XJijxMD5eDc5+ -r1ZfaSz5RNM/5LWokqLajCuxBxohRXldgF3Y3UtuQipeok1RPmQb0UcPSz7zndHF -BV90c3o09v0/aqp2USlzCABIHO0l5Qru9m5wtvI+L/ECgYEA+TaweFBy6vGLa1jU -hr8vaED880KNu6bIfHfmT580cG8ZXdz/+JHup+lERQmjuEX9Jc6QO2AUVsn1bZ8P -oxs/4kNKL3cYZ/r66cBSSu0xihXsFAvDm4rrwSI5IAzOioMxBVs65sicpaceunHP -l+c7Ma2cEUnlwYVuRAXhy1jjVhcCgYEA3gPR6q1lYgYsm8alOUzy/Eexp8PMbpgE -t6c5eyT4Swau9XkNxp5R/tLCMvuZxwplZauChpE7O1TTiu88vrqRVVl4fZVkIlnx -/xzzQFuAny/Qx+FOU18gNhFCRn4aAs5k+wdOncCIonz3IhMcusruseorBw57DLil -TNlLZtQPZZ0CgYEAtUiEHDEhNyiX63GFv7MpUCQeHPJn2X4cTvaFEZxU8AjRIgdW -KEI3oes8nx/A+ZXn7O2S264rfWqR3rkbDeIPmY6rU1XF6jWW+hzNf/WE2NbTkU1x -cB8hGa/EcD0ArZ97NFNFyIVb9eBYqPWLNgudcqjAY48m05w1NsQ0mNBDJucCgYAP -JWWRxAiRmmg6rF+jPBurmFyHXHU66kYQHWlvfEMwIyGWf46wCScA4nH7Nmz0RkJK -oFvEQG4xCwVvigiz3liB4Ru2PZXaPhajV99EebmZopJ0wGsuhuPUrHLACmRN4rTC -52m2m2b25t2ZRoKEP8nu+1G6JoPAh2xHhN9/AWKXhQKBgQDFmIUZwFmb3+oYgEN5 -QVmiwHgy9+AX+atZ76N3oajF42w3SFTLO9yQ5dlb9nBfwzpraZn2T9fAyiP0a9iU -A9Y5MUFWlqMfgeOK/HEprcA7/Wth8ou3iu6Ojn85Iy2VqmqA4OYUYQTsS6izus4W -Eel2uEuwhoIseSX1F0tqMlwxVg== ------END PRIVATE KEY----- diff --git a/components/camel-milo/src/test/resources/keystore b/components/camel-milo/src/test/resources/keystore new file mode 100644 index 0000000..ef6d68f Binary files /dev/null and b/components/camel-milo/src/test/resources/keystore differ diff --git a/components/camel-milo/src/test/resources/openssl-ca.cnf b/components/camel-milo/src/test/resources/openssl-ca.cnf new file mode 100644 index 0000000..bd29e0e --- /dev/null +++ b/components/camel-milo/src/test/resources/openssl-ca.cnf @@ -0,0 +1,80 @@ +HOME = . +RANDFILE = $ENV::HOME/.rnd + +#################################################################### +[ ca ] +default_ca = CA_default # The default ca section + +[ CA_default ] + +default_days = 1000 # How long to certify for +default_crl_days = 30 # How long before next CRL +default_md = sha256 # Use public key default MD +preserve = no # Keep passed DN ordering + +x509_extensions = ca_extensions # The extensions to add to the cert + +email_in_dn = no # Don't concat the email in the DN +copy_extensions = copy # Required to copy SANs from CSR to cert + +base_dir = . +certificate = $base_dir/ca/cacert.pem # The CA certifcate +private_key = $base_dir/ca/cakey.pem # The CA private key +new_certs_dir = $base_dir/cert # Location for new certs after signing +database = $base_dir/ca/index.txt # Database index file +serial = $base_dir/ca/serial.txt # The current serial number + +unique_subject = no # Set to 'no' to allow creation of + # several certificates with same subject. + +#################################################################### +[ req ] +default_bits = 4096 +default_keyfile = ca/cakey.pem +distinguished_name = ca_distinguished_name +x509_extensions = ca_extensions +string_mask = utf8only + +#################################################################### +[ ca_distinguished_name ] +countryName = Country Name (2 letter code) +countryName_default = RH + +stateOrProvinceName = State or Province Name (full name) +stateOrProvinceName_default = RH + +localityName = Locality Name (eg, city) +localityName_default = RH + +organizationName = Organization Name (eg, company) +organizationName_default = Test CA + +commonName = Common Name (e.g. server FQDN or YOUR name) +commonName_default = Test CA + +#################################################################### +[ ca_extensions ] + +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always, issuer +basicConstraints = critical, CA:true +keyUsage = keyCertSign, cRLSign + +#################################################################### +[ signing_policy ] +countryName = optional +stateOrProvinceName = optional +localityName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +#################################################################### +[ signing_req ] +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid,issuer +basicConstraints = CA:FALSE +keyUsage = digitalSignature, keyEncipherment + +#openssl pkcs12 -export -in servercert.pem -inkey serverkey.pem -certfile cacert.pem -name "test" -out keystore.p12 -passout pass:test diff --git a/components/camel-milo/src/test/resources/openssl-server.cnf b/components/camel-milo/src/test/resources/openssl-server.cnf new file mode 100644 index 0000000..dd3cd0e --- /dev/null +++ b/components/camel-milo/src/test/resources/openssl-server.cnf @@ -0,0 +1,39 @@ +HOME = . +RANDFILE = $ENV::HOME/.rnd + +#################################################################### +[ req ] +default_bits = 2048 +default_keyfile = cert/serverkey.pem +distinguished_name = server_distinguished_name +req_extensions = server_req_extensions +string_mask = utf8only + +#################################################################### +[ server_distinguished_name ] +countryName = Country Name (2 letter code) +countryName_default = RH + +stateOrProvinceName = State or Province Name (full name) +stateOrProvinceName_default = RH + +localityName = Locality Name (eg, city) +localityName_default = RH + +commonName = Common Name (e.g. server FQDN or YOUR name) +commonName_default = milo-test + + +#################################################################### +[ server_req_extensions ] + +subjectKeyIdentifier = hash +basicConstraints = CA:FALSE +keyUsage = digitalSignature, keyEncipherment +subjectAltName = @alternate_names +nsComment = "OpenSSL Generated Certificate" + +#################################################################### +[ alternate_names ] + +URI.1 = http://camel.apache.org/EclipseMilo/Client diff --git a/components/camel-milo/src/test/resources/run.sh b/components/camel-milo/src/test/resources/run.sh new file mode 100755 index 0000000..a6d1620 --- /dev/null +++ b/components/camel-milo/src/test/resources/run.sh @@ -0,0 +1,21 @@ +# script which prepares all needed certificates and keystore +# properties of certificates has to be confirmed +# password to be filled in the last step has to be 'test' + +mkdir cert +mkdir ca + +#generate ca certificate and key +openssl req -x509 -config openssl-ca.cnf -newkey rsa:4096 -sha256 -nodes -out ca/cacert.pem -outform PEM + +#generate csr +openssl req -config openssl-server.cnf -newkey rsa:2048 -sha256 -nodes -out cert/servercert.csr -outform PEM + +#sign +touch ca/index.txt +echo '01' > ca/serial.txt +openssl ca -config openssl-ca.cnf -policy signing_policy -extensions signing_req -out cert/servercert.pem -infiles cert/servercert.csr + +#import into keystore +openssl pkcs12 -export -in cert/servercert.pem -inkey cert/serverkey.pem -certfile ca/cacert.pem -name "test" -out keystore.p12 -passout pass:test +keytool -importkeystore -srckeystore keystore.p12 -srcstoretype pkcs12 -destkeystore keystore -deststoretype JKS -storepass testtest diff --git a/docs/components/modules/ROOT/pages/milo-client-component.adoc b/docs/components/modules/ROOT/pages/milo-client-component.adoc index 634f914..3517f7e 100644 --- a/docs/components/modules/ROOT/pages/milo-client-component.adoc +++ b/docs/components/modules/ROOT/pages/milo-client-component.adoc @@ -11,6 +11,9 @@ The Milo Client component provides access to OPC UA servers using the http://eclipse.org/milo[Eclipse Milo™] implementation. +*Important*: To successfully use certificates for secured communication, +JCE Jurisdiction Policy File Default has to be *Unlimited* (which is by default since jdk 9+). + Maven users will need to add the following dependency to their `pom.xml` for this component: @@ -53,13 +56,13 @@ The URI syntax of the endpoint is: [source] ------------------------ -milo-client:tcp://[user:password@]host:port/path/to/service?node=RAW(nsu=urn:foo:bar;s=item-1) +milo-client:opc.tcp://[user:password@]host:port/path/to/service?node=RAW(nsu=urn:foo:bar;s=item-1) ------------------------ If the server does not use a path, then it is possible to simply omit it: ------------------------ -milo-client:tcp://[user:password@]host:port?node=RAW(nsu=urn:foo:bar;s=item-1) +milo-client:opc.tcp://[user:password@]host:port?node=RAW(nsu=urn:foo:bar;s=item-1) ------------------------ If no user credentials are provided the client will switch to anonymous mode. @@ -240,7 +243,7 @@ As the values generated by the syntax cannot be transparently encoded into a URI However Camel allows to wrap the actual value inside `RAW(…)`, which makes escaping unnecessary. For example: ------------------------ -milo-client:tcp://user:password@localhost:12345?node=RAW(nsu=http://foo.bar;s=foo/bar) +milo-client:opc.tcp://user:password@localhost:12345?node=RAW(nsu=http://foo.bar;s=foo/bar) ------------------------ === Method ID diff --git a/docs/components/modules/ROOT/pages/milo-server-component.adoc b/docs/components/modules/ROOT/pages/milo-server-component.adoc index 861c31c..072675e 100644 --- a/docs/components/modules/ROOT/pages/milo-server-component.adoc +++ b/docs/components/modules/ROOT/pages/milo-server-component.adoc @@ -31,7 +31,7 @@ Value write requests from OPC UA Client will trigger messages which are sent int // component options: START -The OPC UA Server component supports 22 options, which are listed below. +The OPC UA Server component supports 20 options, which are listed below. @@ -40,12 +40,10 @@ The OPC UA Server component supports 22 options, which are listed below. | Name | Description | Default | Type | *namespaceUri* (common) | The URI of the namespace, defaults to urn:org:apache:camel | | String | *applicationName* (common) | The application name | | String +| *path* (common) | The path to be appended to the end of the endpoint url. (doesn't need to start with '/') | | String | *applicationUri* (common) | The application URI | | String | *productUri* (common) | The product URI | | String | *bindPort* (common) | The TCP port the server binds to | | int -| *strictEndpointUrlsEnabled* (common) | Set whether strict endpoint URLs are enforced | false | boolean -| *serverName* (common) | Server name | | String -| *hostname* (common) | Server hostname | | String | *securityPolicies* (common) | Security policies | | Set | *securityPoliciesById* (common) | Security policies by URI or name | | Collection | *userAuthenticationCredentials* (common) | Set user password combinations in the form of user1:pwd1,user2:pwd2 Usernames and passwords will be URL decoded | | String diff --git a/docs/user-manual/modules/ROOT/pages/camel-3x-upgrade-guide.adoc b/docs/user-manual/modules/ROOT/pages/camel-3x-upgrade-guide.adoc index 33abb55..4f75f8b 100644 --- a/docs/user-manual/modules/ROOT/pages/camel-3x-upgrade-guide.adoc +++ b/docs/user-manual/modules/ROOT/pages/camel-3x-upgrade-guide.adoc @@ -41,6 +41,27 @@ The `camel-irc` component has changed its endpoint syntax and removed option #ro irc:nick@host[:port]?[options] ---- +=== camel-milo + +The `camel-milo` client component has changed its endpoint syntax from `milo-client:tcp` to `milo-client:opc.tcp`. +For example before + +[source,text] +---- +milo-client:tcp://foo:bar@localhost:1234 +---- + +Should be changed to +---- +milo-client:opc.tcp://foo:bar@localhost:1234 +---- + +The `camel-milo` server component requires Java 9 at runtime. +Property `strictEndpointUrlsEnabled` is no longer supported. +Properties`hostName` and `serverName` are replaced by `path`. +To successfully use certificates for secured communication, JCE Jurisdiction Policy File Default +has to be *Unlimited* (which is by default since Java 9+). + === camel-nats The `camel-nats` component has changed its endpoint syntax from `nats:servers` to `nats:topic`. diff --git a/parent/pom.xml b/parent/pom.xml index c86d053..1cb3e58 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -164,6 +164,8 @@ <derby-version>10.14.2.0</derby-version> <digitalocean-api-client-version>2.17</digitalocean-api-client-version> <digitalocean-api-client-bundle-version>2.17_1</digitalocean-api-client-bundle-version> + <digitalpetri-fsm-client>0.2</digitalpetri-fsm-client> + <digitalpetri-netty-client>0.3</digitalpetri-netty-client> <directory-watcher-version>0.9.9</directory-watcher-version> <disruptor-version>3.4.2</disruptor-version> <dnsjava-version>2.1.9</dnsjava-version> @@ -458,7 +460,8 @@ <microprofile-config-version>1.3</microprofile-config-version> <microprofile-metrics-version>2.2.1</microprofile-metrics-version> <microprofile-health-version>2.1</microprofile-health-version> - <milo-version>0.2.5</milo-version> + <milo-version>0.3.7</milo-version> + <milo-guava-version>26.0-jre</milo-guava-version> <mimepull-version>1.9.12</mimepull-version> <mina-version>2.1.3</mina-version> <minidns-version>0.3.4</minidns-version> diff --git a/platforms/karaf/features/src/main/resources/features.xml b/platforms/karaf/features/src/main/resources/features.xml index a183476..ae3b5e9 100644 --- a/platforms/karaf/features/src/main/resources/features.xml +++ b/platforms/karaf/features/src/main/resources/features.xml @@ -1750,19 +1750,26 @@ <bundle dependency='true'>mvn:org.jooq/jool/${jool-version}</bundle> <bundle dependency='true'>mvn:com.google.code.findbugs/jsr305/${google-findbugs-jsr305-version}</bundle> <bundle dependency='true'>mvn:com.google.code.findbugs/annotations/${google-findbugs-annotations2-version}</bundle> - <bundle dependency='true'>mvn:io.netty/netty-common/${netty40-version}</bundle> - <bundle dependency='true'>mvn:io.netty/netty-buffer/${netty40-version}</bundle> - <bundle dependency='true'>mvn:io.netty/netty-handler/${netty40-version}</bundle> - <bundle dependency='true'>mvn:io.netty/netty-transport/${netty40-version}</bundle> - <bundle dependency='true'>mvn:io.netty/netty-codec/${netty40-version}</bundle> - <bundle dependency='true'>mvn:com.google.guava/guava/${google-guava-version}</bundle> + <bundle dependency='true'>mvn:org.bouncycastle/bcprov-jdk15on/${bouncycastle-version}</bundle> + <bundle dependency='true'>mvn:org.bouncycastle/bcpkix-jdk15on/${bouncycastle-version}</bundle> + <bundle dependency='true'>mvn:org.bouncycastle/bcmail-jdk15on/${bouncycastle-version}</bundle> + <bundle dependency='true'>mvn:io.netty/netty-common/${netty-version}</bundle> + <bundle dependency='true'>mvn:io.netty/netty-buffer/${netty-version}</bundle> + <bundle dependency='true'>mvn:io.netty/netty-resolver/${netty-version}</bundle> + <bundle dependency='true'>mvn:io.netty/netty-transport/${netty-version}</bundle> + <bundle dependency='true'>mvn:io.netty/netty-handler/${netty-version}</bundle> + <bundle dependency='true'>mvn:io.netty/netty-codec/${netty-version}</bundle> + <bundle dependency='true'>mvn:io.netty/netty-codec-http/${netty-version}</bundle> + <bundle dependency='true'>wrap:mvn:com.digitalpetri.fsm/strict-machine/${digitalpetri-fsm-client}</bundle> + <bundle dependency='true'>wrap:mvn:com.digitalpetri.netty/netty-channel-fsm/${digitalpetri-netty-client}</bundle> + <bundle dependency='true'>mvn:com.google.guava/guava/${milo-guava-version}</bundle> <bundle dependency='true'>mvn:org.eclipse.milo/stack-core/${milo-version}</bundle> <bundle dependency='true'>mvn:org.eclipse.milo/stack-server/${milo-version}</bundle> <bundle dependency='true'>mvn:org.eclipse.milo/stack-client/${milo-version}</bundle> <bundle dependency='true'>mvn:org.eclipse.milo/sdk-core/${milo-version}</bundle> - <bundle dependency='true'>mvn:org.eclipse.milo/sdk-server/${milo-version}</bundle> - <bundle dependency='true'>mvn:org.eclipse.milo/sdk-client/${milo-version}</bundle> + <bundle dependency='true'>mvn:org.eclipse.milo/sdk-server/${milo-version}</bundle> <bundle dependency='true'>mvn:org.eclipse.milo/bsd-parser-core/${milo-version}</bundle> + <bundle dependency='true'>mvn:org.eclipse.milo/sdk-client/${milo-version}</bundle> <bundle dependency='true'>mvn:org.eclipse.milo/bsd-parser-gson/${milo-version}</bundle> <bundle>mvn:org.apache.camel/camel-milo/${project.version}</bundle> </feature> diff --git a/tests/camel-itest-karaf/src/test/java/org/apache/camel/itest/karaf/CamelMiloTest.java b/tests/camel-itest-karaf/src/test/java/org/apache/camel/itest/karaf/CamelMiloTest.java index 60d881c..a66342c 100644 --- a/tests/camel-itest-karaf/src/test/java/org/apache/camel/itest/karaf/CamelMiloTest.java +++ b/tests/camel-itest-karaf/src/test/java/org/apache/camel/itest/karaf/CamelMiloTest.java @@ -16,12 +16,22 @@ */ package org.apache.camel.itest.karaf; +import org.junit.Assume; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.ops4j.pax.exam.junit.PaxExam; @RunWith(PaxExam.class) public class CamelMiloTest extends BaseKarafTest { + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + Assume.assumeTrue("Requires java 9+", isJavaVersionSatisfied(9)); + } + @Test public void testClient() throws Exception { testComponent("milo", "milo-client"); @@ -30,4 +40,22 @@ public class CamelMiloTest extends BaseKarafTest { public void testServer() throws Exception { testComponent("milo", "milo-server"); } + + /** + * Return true, if java version (defined by method getRequiredJavaVersion()) is satisfied. + * Works for java versions 9+ + */ + boolean isJavaVersionSatisfied(int requiredVersion) { + String version = System.getProperty("java.version"); + if (!version.startsWith("1.")) { + int dot = version.indexOf("."); + if (dot != -1) { + version = version.substring(0, dot); + } + if (Integer.parseInt(version) >= requiredVersion) { + return true; + } + } + return false; + } }