Search in sources :

Example 1 with IdentityProvider

use of org.eclipse.milo.opcua.sdk.client.api.identity.IdentityProvider in project vantiq-extension-sources by Vantiq.

the class OpcUaESClient method createClient.

@SuppressWarnings({ "PMD.CognitiveComplexity", "PMD.MethodLengthCheck" })
private OpcUaClient createClient(Map<String, Object> config) throws Exception {
    if (storageDirectory == null) {
        throw new OpcExtConfigException(ERROR_PREFIX + ".missingStorageDirectory: No storage directory specified.");
    }
    SecurityPolicy securityPolicy = determineSecurityPolicy(config);
    MessageSecurityMode msgSecMode = determineMessageSecurityMode(config);
    File securityDir = new File(storageDirectory, SECURITY_DIRECTORY);
    if (!securityDir.exists() && !securityDir.mkdirs()) {
        throw new OpcExtConfigException(ERROR_PREFIX + ".invalidStorageDirectory: unable to create security dir: " + securityDir);
    }
    log.info("security temp dir: {}", securityDir.getAbsolutePath());
    keyStoreManager = new KeyStoreManager().load(securityDir);
    IdentityProvider idProvider = constructIdentityProvider(config);
    List<EndpointDescription> endpoints;
    discoveryEndpoint = (String) config.get(OpcConstants.CONFIG_DISCOVERY_ENDPOINT);
    serverEndpoint = (String) config.get(OpcConstants.CONFIG_SERVER_ENDPOINT);
    if (discoveryEndpoint == null && serverEndpoint == null) {
        String errorMsg = ERROR_PREFIX + ".noDiscoveryEndpoint: No discovery or server endpoint was provided in the configuration.";
        log.error(errorMsg);
        throw new OpcExtConfigException(errorMsg);
    }
    OpcUaClientConfig opcConfig;
    try {
        endpoints = DiscoveryClient.getEndpoints(discoveryEndpoint).get();
    } catch (Throwable ex) {
        try {
            // try the explicit discovery endpoint as well
            String discoveryUrl = discoveryEndpoint + "/discovery";
            log.info("Trying explicit discovery URL: {}", discoveryUrl);
            endpoints = DiscoveryClient.getEndpoints(discoveryUrl).get();
        } catch (ExecutionException e) {
            String errMsg = ERROR_PREFIX + ".discoveryError: Could not discover OPC Endpoints:" + e.getClass().getName() + "::" + e.getMessage();
            log.error(ERROR_PREFIX + ".discoveryError: Could not discover OPC Endpoints: {} :: {}", e.getClass().getName(), e.getMessage());
            throw new OpcExtConfigException(errMsg, e);
        }
    }
    if (log.isDebugEnabled()) {
        logDiscoveredEndpoints(endpoints);
    }
    List<EndpointDescription> validEndpoints = endpoints.stream().filter(e -> (e.getSecurityPolicyUri().equals(securityPolicy.getUri()) && e.getSecurityMode().equals(msgSecMode))).collect(Collectors.toList());
    if (log.isDebugEnabled()) {
        logAcceptableEndpoints(validEndpoints, securityPolicy, msgSecMode);
        // The following code is here for testing only.  It allows us to fake a poorly configured
        // server that reports invalid or unreachable endpoints as part of discovery.  This is, for
        // reasons I'm sure i don't agree with, part of the protocol, so we must tolerate it.  The
        // purportedly proper response is to substitute the address used for discovery for any
        // unreachable addresses.  This, of course, makes little sense since the whole point of discovery
        // is to allow these to be spread across different nodes.  But I didn't write the spec.
        Boolean fakeBadAddress = (Boolean) config.get(OpcConstants.CONFIG_TEST_DISCOVERY_UNREACHABLE);
        if (fakeBadAddress != null && fakeBadAddress) {
            List<EndpointDescription> newValidEndpoints = new ArrayList<>();
            for (EndpointDescription e : validEndpoints) {
                URI url = new URI(e.getEndpointUrl());
                URI borkedUri = new URI(url.getScheme(), null, "utterlyWorthlessHostThatShouldNeverResolve", url.getPort(), url.getPath(), null, null);
                EndpointDescription borkedEd = new EndpointDescription(borkedUri.toString(), e.getServer(), e.getServerCertificate(), e.getSecurityMode(), e.getSecurityPolicyUri(), e.getUserIdentityTokens(), e.getTransportProfileUri(), e.getSecurityLevel());
                newValidEndpoints.add(borkedEd);
            }
            validEndpoints = newValidEndpoints;
        }
    }
    // First, we'll look for an endpoint that doesn't contain localhost.  This is, generally,
    // a not too useful configuration since localhost is always a relative address.
    EndpointDescription endpoint = validEndpoints.stream().filter(e -> {
        try {
            // Note:  Must use URI here.  If you use URL, it will fail with
            // a MailformedURLException because the generic system doesn't
            // understand opc.tcp: as a scheme/protocol.
            URI url = new URI(e.getEndpointUrl());
            InetAddress ina = InetAddress.getByName(url.getHost());
            if (!ina.isLoopbackAddress() || ina.isReachable(3000)) {
                return true;
            }
        } catch (UnknownHostException | URISyntaxException ex) {
            log.warn("Recoverable error during discovered server URL validation:" + ex.getClass().getName() + "::" + ex.getMessage() + "-->" + e.getEndpointUrl());
        } catch (Exception ex) {
            // This means that we have some non-optimal addresses returned by discovery.
            // In these cases, we'll leave it up to the SDK & network stack to figure out how to get there.
            log.debug("Recoverable error during discovered server URL validation. Left to network stack to resolve:" + ex.getClass().getName() + "::" + ex.getMessage() + "-->" + e.getEndpointUrl());
        }
        return false;
    }).findFirst().orElse(null);
    if (endpoint == null) {
        // Discovery server returned either no reasonable endpoints or none that weren't a loopback.
        log.warn("No servers at reachable, non-loopback addresses found via discovery. " + "Fixing up addresses to match discovery server.");
        endpoint = validEndpoints.stream().findFirst().orElse(null);
        if (endpoint != null) {
            endpoint = fixLookbackAddress(endpoint);
        }
    }
    if (endpoint == null) {
        throw new Exception("No acceptable endpoints returned for security policy: " + securityPolicy.getUri() + " and security mode " + msgSecMode);
    }
    if (serverEndpoint != null) {
        // Then we'll override the endpoint provided but otherwise use the endpoint descriptor returned.
        // The SDK seems to have an issue when no EndpointDescriptor is provided.
        EndpointDescription newEndpoint = new EndpointDescription(serverEndpoint, endpoint.getServer(), endpoint.getServerCertificate(), endpoint.getSecurityMode(), endpoint.getSecurityPolicyUri(), endpoint.getUserIdentityTokens(), endpoint.getTransportProfileUri(), endpoint.getSecurityLevel());
        log.debug("Replacing endpoint address with provided serverEndpoint: {} --> {}", endpoint.getEndpointUrl(), newEndpoint.getEndpointUrl());
        endpoint = newEndpoint;
    }
    log.info("Using discovered endpoint: {} [{}, {}]", endpoint.getEndpointUrl(), securityPolicy, msgSecMode.toString());
    opcConfig = OpcUaClientConfig.builder().setApplicationName(LocalizedText.english("VANTIQ OPC-UA Source")).setApplicationUri("urn:io:vantiq:extsrc:opcua:client").setCertificate(keyStoreManager.getClientCertificate()).setKeyPair(keyStoreManager.getClientKeyPair()).setEndpoint(endpoint).setIdentityProvider(idProvider).setRequestTimeout(uint(5000)).build();
    return OpcUaClient.create(opcConfig);
}
Also used : MonitoringParameters(org.eclipse.milo.opcua.stack.core.types.structured.MonitoringParameters) X509Certificate(java.security.cert.X509Certificate) URISyntaxException(java.net.URISyntaxException) ByteString(org.eclipse.milo.opcua.stack.core.types.builtin.ByteString) MonitoredItemCreateRequest(org.eclipse.milo.opcua.stack.core.types.structured.MonitoredItemCreateRequest) InetAddress(java.net.InetAddress) QualifiedName(org.eclipse.milo.opcua.stack.core.types.builtin.QualifiedName) Unsigned.uint(org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.uint) UaSubscription(org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaSubscription) AttributeId(org.eclipse.milo.opcua.stack.core.AttributeId) Map(java.util.Map) URI(java.net.URI) AnonymousProvider(org.eclipse.milo.opcua.sdk.client.api.identity.AnonymousProvider) MessageFormatter(org.slf4j.helpers.MessageFormatter) X509IdentityProvider(org.eclipse.milo.opcua.sdk.client.api.identity.X509IdentityProvider) TimestampsToReturn(org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn) NodeId(org.eclipse.milo.opcua.stack.core.types.builtin.NodeId) UUID(java.util.UUID) Collectors(java.util.stream.Collectors) ReadValueId(org.eclipse.milo.opcua.stack.core.types.structured.ReadValueId) IdentityProvider(org.eclipse.milo.opcua.sdk.client.api.identity.IdentityProvider) Slf4j(lombok.extern.slf4j.Slf4j) List(java.util.List) Variant(org.eclipse.milo.opcua.stack.core.types.builtin.Variant) StatusCode(org.eclipse.milo.opcua.stack.core.types.builtin.StatusCode) PrivateKey(java.security.PrivateKey) Optional(java.util.Optional) UaVariableNode(org.eclipse.milo.opcua.sdk.client.nodes.UaVariableNode) MonitoringMode(org.eclipse.milo.opcua.stack.core.types.enumerated.MonitoringMode) DataValue(org.eclipse.milo.opcua.stack.core.types.builtin.DataValue) OpcUaClient(org.eclipse.milo.opcua.sdk.client.OpcUaClient) CompletableFuture(java.util.concurrent.CompletableFuture) ArrayList(java.util.ArrayList) ImmutableList(com.google.common.collect.ImmutableList) EndpointDescription(org.eclipse.milo.opcua.stack.core.types.structured.EndpointDescription) UShort(org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UShort) MessageSecurityMode(org.eclipse.milo.opcua.stack.core.types.enumerated.MessageSecurityMode) BiConsumer(java.util.function.BiConsumer) SecurityPolicy(org.eclipse.milo.opcua.stack.core.security.SecurityPolicy) UaMonitoredItem(org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaMonitoredItem) OpcUaClientConfig(org.eclipse.milo.opcua.sdk.client.api.config.OpcUaClientConfig) UInteger(org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger) LocalizedText(org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText) UnknownHostException(java.net.UnknownHostException) File(java.io.File) ExecutionException(java.util.concurrent.ExecutionException) AtomicLong(java.util.concurrent.atomic.AtomicLong) UaException(org.eclipse.milo.opcua.stack.core.UaException) DiscoveryClient(org.eclipse.milo.opcua.stack.client.DiscoveryClient) UsernameProvider(org.eclipse.milo.opcua.sdk.client.api.identity.UsernameProvider) MessageSecurityMode(org.eclipse.milo.opcua.stack.core.types.enumerated.MessageSecurityMode) UnknownHostException(java.net.UnknownHostException) ArrayList(java.util.ArrayList) X509IdentityProvider(org.eclipse.milo.opcua.sdk.client.api.identity.X509IdentityProvider) IdentityProvider(org.eclipse.milo.opcua.sdk.client.api.identity.IdentityProvider) OpcUaClientConfig(org.eclipse.milo.opcua.sdk.client.api.config.OpcUaClientConfig) EndpointDescription(org.eclipse.milo.opcua.stack.core.types.structured.EndpointDescription) ByteString(org.eclipse.milo.opcua.stack.core.types.builtin.ByteString) URISyntaxException(java.net.URISyntaxException) URI(java.net.URI) URISyntaxException(java.net.URISyntaxException) UnknownHostException(java.net.UnknownHostException) ExecutionException(java.util.concurrent.ExecutionException) UaException(org.eclipse.milo.opcua.stack.core.UaException) SecurityPolicy(org.eclipse.milo.opcua.stack.core.security.SecurityPolicy) ExecutionException(java.util.concurrent.ExecutionException) File(java.io.File) InetAddress(java.net.InetAddress)

Example 2 with IdentityProvider

use of org.eclipse.milo.opcua.sdk.client.api.identity.IdentityProvider in project vantiq-extension-sources by Vantiq.

the class OpcUaESClient method constructIdentityProvider.

private IdentityProvider constructIdentityProvider(Map<String, Object> config) throws OpcExtConfigException, OpcExtKeyStoreException {
    IdentityProvider retVal = null;
    String anonymous = (String) config.get(OpcConstants.CONFIG_IDENTITY_ANONYMOUS);
    // This can be empty -- presence is sufficient
    boolean anonIsPresent = anonymous != null;
    String certAlias = (String) config.get(OpcConstants.CONFIG_IDENTITY_CERTIFICATE);
    boolean certIsPresent = foundValue(certAlias);
    String userPass = (String) config.get(OpcConstants.CONFIG_IDENTITY_USERNAME_PASSWORD);
    boolean upwIsPresent = foundValue(userPass);
    boolean exactlyOnePresent = (anonIsPresent ^ certIsPresent ^ upwIsPresent) ^ (anonIsPresent && certIsPresent && upwIsPresent);
    if (!anonIsPresent && !certIsPresent && !upwIsPresent) {
        log.warn(ERROR_PREFIX + ".noIdentitySpecification: No identity specification was provided.  Using Anonymous as default.");
        retVal = new AnonymousProvider();
    } else if (exactlyOnePresent) {
        // Now we know there is exactly one of them set.
        if (anonIsPresent) {
            retVal = new AnonymousProvider();
        } else if (certIsPresent) {
            X509Certificate namedCert = keyStoreManager.fetchCertByAlias(certAlias);
            PrivateKey pKey = keyStoreManager.fetchPrivateKeyByAlias(certAlias);
            retVal = new X509IdentityProvider(namedCert, pKey);
        } else if (upwIsPresent) {
            String[] upw = userPass.split(",[ ]*");
            if (upw.length != 2) {
                String errMsg = MessageFormatter.arrayFormat(ERROR_PREFIX + ".invalidUserPasswordSpecification: the {} ({}) must contain only a username AND password separated by a comma.", new Object[] { OpcConstants.CONFIG_IDENTITY_USERNAME_PASSWORD, userPass }).getMessage();
                log.error(errMsg);
                throw new OpcExtConfigException(errMsg);
            } else {
                retVal = new UsernameProvider(upw[0], upw[1]);
            }
        }
    } else {
        String errMsg = MessageFormatter.arrayFormat(ERROR_PREFIX + ".invalidIdentitySpecification: exactly one identity specification ({}, {}, {}) is required.", new Object[] { OpcConstants.CONFIG_IDENTITY_ANONYMOUS, OpcConstants.CONFIG_IDENTITY_CERTIFICATE, OpcConstants.CONFIG_IDENTITY_USERNAME_PASSWORD }).getMessage();
        log.error(errMsg);
        throw new OpcExtConfigException(errMsg);
    }
    return retVal;
}
Also used : PrivateKey(java.security.PrivateKey) AnonymousProvider(org.eclipse.milo.opcua.sdk.client.api.identity.AnonymousProvider) X509IdentityProvider(org.eclipse.milo.opcua.sdk.client.api.identity.X509IdentityProvider) IdentityProvider(org.eclipse.milo.opcua.sdk.client.api.identity.IdentityProvider) ByteString(org.eclipse.milo.opcua.stack.core.types.builtin.ByteString) X509Certificate(java.security.cert.X509Certificate) UsernameProvider(org.eclipse.milo.opcua.sdk.client.api.identity.UsernameProvider) X509IdentityProvider(org.eclipse.milo.opcua.sdk.client.api.identity.X509IdentityProvider)

Aggregations

PrivateKey (java.security.PrivateKey)2 X509Certificate (java.security.cert.X509Certificate)2 AnonymousProvider (org.eclipse.milo.opcua.sdk.client.api.identity.AnonymousProvider)2 IdentityProvider (org.eclipse.milo.opcua.sdk.client.api.identity.IdentityProvider)2 UsernameProvider (org.eclipse.milo.opcua.sdk.client.api.identity.UsernameProvider)2 X509IdentityProvider (org.eclipse.milo.opcua.sdk.client.api.identity.X509IdentityProvider)2 ByteString (org.eclipse.milo.opcua.stack.core.types.builtin.ByteString)2 ImmutableList (com.google.common.collect.ImmutableList)1 File (java.io.File)1 InetAddress (java.net.InetAddress)1 URI (java.net.URI)1 URISyntaxException (java.net.URISyntaxException)1 UnknownHostException (java.net.UnknownHostException)1 ArrayList (java.util.ArrayList)1 List (java.util.List)1 Map (java.util.Map)1 Optional (java.util.Optional)1 UUID (java.util.UUID)1 CompletableFuture (java.util.concurrent.CompletableFuture)1 ExecutionException (java.util.concurrent.ExecutionException)1