This is an automated email from the ASF dual-hosted git repository.

riemer pushed a commit to branch 3822-opc-ua-x509-user-authentication
in repository https://gitbox.apache.org/repos/asf/streampipes.git


The following commit(s) were added to 
refs/heads/3822-opc-ua-x509-user-authentication by this push:
     new fd91938bea feat(#3822): Support OPC-UA certificate authentication
fd91938bea is described below

commit fd91938beae0334fdd52fcb4732a434c48722076
Author: Dominik Riemer <[email protected]>
AuthorDate: Tue Oct 7 15:13:33 2025 +0200

    feat(#3822): Support OPC-UA certificate authentication
---
 .../opcua/OpcUaConnectorsModuleExport.java         |   6 +-
 .../connectors/opcua/adapter/OpcUaAdapter.java     |   2 +-
 .../opcua/config/SharedUserConfiguration.java      |  13 ++-
 .../opcua/config/SpOpcUaConfigExtractor.java       |  13 ++-
 .../opcua/config/identity/X509IdentityConfig.java  | 114 +++++++++++++++++++++
 .../opcua/migration/OpcUaAdapterMigrationV6.java   |  76 ++++++++++++++
 .../opcua/migration/OpcUaSinkMigrationV2.java      |  48 +++++++++
 .../connectors/opcua/sink/OpcUaSink.java           |   2 +-
 .../strings.en                                     |   9 ++
 .../strings.en                                     |   9 ++
 ui/src/app/core-ui/core-ui.module.ts               |   3 +-
 .../static-free-input.component.html               |  43 +++++---
 .../static-free-input.component.ts                 |   4 +
 13 files changed, 316 insertions(+), 26 deletions(-)

diff --git 
a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/OpcUaConnectorsModuleExport.java
 
b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/OpcUaConnectorsModuleExport.java
index e70c1dceea..4eb8168567 100644
--- 
a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/OpcUaConnectorsModuleExport.java
+++ 
b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/OpcUaConnectorsModuleExport.java
@@ -29,7 +29,9 @@ import 
org.apache.streampipes.extensions.connectors.opcua.migration.OpcUaAdapter
 import 
org.apache.streampipes.extensions.connectors.opcua.migration.OpcUaAdapterMigrationV3;
 import 
org.apache.streampipes.extensions.connectors.opcua.migration.OpcUaAdapterMigrationV4;
 import 
org.apache.streampipes.extensions.connectors.opcua.migration.OpcUaAdapterMigrationV5;
+import 
org.apache.streampipes.extensions.connectors.opcua.migration.OpcUaAdapterMigrationV6;
 import 
org.apache.streampipes.extensions.connectors.opcua.migration.OpcUaSinkMigrationV1;
+import 
org.apache.streampipes.extensions.connectors.opcua.migration.OpcUaSinkMigrationV2;
 import org.apache.streampipes.extensions.connectors.opcua.sink.OpcUaSink;
 
 import java.util.List;
@@ -64,7 +66,9 @@ public class OpcUaConnectorsModuleExport implements 
IExtensionModuleExport {
         new OpcUaAdapterMigrationV3(),
         new OpcUaAdapterMigrationV4(),
         new OpcUaAdapterMigrationV5(),
-        new OpcUaSinkMigrationV1()
+        new OpcUaAdapterMigrationV6(),
+        new OpcUaSinkMigrationV1(),
+        new OpcUaSinkMigrationV2()
     );
   }
 }
diff --git 
a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/adapter/OpcUaAdapter.java
 
b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/adapter/OpcUaAdapter.java
index 75d0b2ec44..d9c6f4d7b0 100644
--- 
a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/adapter/OpcUaAdapter.java
+++ 
b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/adapter/OpcUaAdapter.java
@@ -237,7 +237,7 @@ public class OpcUaAdapter implements StreamPipesAdapter, 
IPullAdapter, SupportsR
 
   @Override
   public IAdapterConfiguration declareConfig() {
-    var builder = AdapterConfigurationBuilder.create(ID, 5, () -> new 
OpcUaAdapter(clientProvider))
+    var builder = AdapterConfigurationBuilder.create(ID, 6, () -> new 
OpcUaAdapter(clientProvider))
         .withAssets(ExtensionAssetType.DOCUMENTATION, ExtensionAssetType.ICON)
         .withLocales(Locales.EN)
         .withCategory(AdapterType.Generic, AdapterType.Manufacturing)
diff --git 
a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/SharedUserConfiguration.java
 
b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/SharedUserConfiguration.java
index 2668b90347..2e3960baf4 100644
--- 
a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/SharedUserConfiguration.java
+++ 
b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/SharedUserConfiguration.java
@@ -56,6 +56,9 @@ public class SharedUserConfiguration {
   public static final String SECURITY_POLICY = "securityPolicy";
   public static final String USER_AUTHENTICATION = "userAuthentication";
   public static final String USER_AUTHENTICATION_ANONYMOUS = "anonymous";
+  public static final String X509_GROUP = "x509Group";
+  public static final String X509_PRIVATE_KEY_PEM = "x509PrivateKeyPem";
+  public static final String X509_PUBLIC_KEY_PEM = "x509PublicKeyPem";
 
   public static OneOfStaticProperty makeNamingStrategyOption() {
     return StaticProperties.singleValueSelection(
@@ -72,6 +75,13 @@ public class SharedUserConfiguration {
 
     var dependsOn = getDependsOn(adapterConfig);
 
+    var x509Group = StaticProperties.group(
+            Labels.withId(X509_GROUP),
+            StaticProperties.secretValue(Labels.withId(X509_PRIVATE_KEY_PEM)),
+            
StaticProperties.stringFreeTextProperty(Labels.withId(X509_PUBLIC_KEY_PEM), 
true, false));
+
+    x509Group.setHorizontalRendering(false);
+
     builder
         .requiredSingleValueSelection(
             Labels.withId(SECURITY_MODE),
@@ -89,7 +99,8 @@ public class SharedUserConfiguration {
                     StaticProperties.stringFreeTextProperty(
                         Labels.withId(USERNAME)),
                     StaticProperties.secretValue(Labels.withId(PASSWORD))
-                ))
+                )),
+            Alternatives.from(Labels.withId(X509_GROUP), x509Group)
         )
         .requiredAlternatives(Labels.withId(OPC_HOST_OR_URL),
             Alternatives.from(
diff --git 
a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/SpOpcUaConfigExtractor.java
 
b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/SpOpcUaConfigExtractor.java
index 36f49e4754..e6fea5c2d6 100644
--- 
a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/SpOpcUaConfigExtractor.java
+++ 
b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/SpOpcUaConfigExtractor.java
@@ -23,6 +23,7 @@ import 
org.apache.streampipes.extensions.api.extractor.IParameterExtractor;
 import 
org.apache.streampipes.extensions.api.extractor.IStaticPropertyExtractor;
 import 
org.apache.streampipes.extensions.connectors.opcua.config.identity.AnonymousIdentityConfig;
 import 
org.apache.streampipes.extensions.connectors.opcua.config.identity.UsernamePasswordIdentityConfig;
+import 
org.apache.streampipes.extensions.connectors.opcua.config.identity.X509IdentityConfig;
 import 
org.apache.streampipes.extensions.connectors.opcua.config.security.SecurityConfig;
 import org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabels;
 import 
org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaNamingStrategy;
@@ -44,6 +45,7 @@ import static 
org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabe
 import static 
org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabels.PULLING_INTERVAL;
 import static 
org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabels.PULL_MODE;
 import static 
org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabels.USERNAME;
+import static 
org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabels.USERNAME_GROUP;
 
 public class SpOpcUaConfigExtractor {
 
@@ -128,15 +130,16 @@ public class SpOpcUaConfigExtractor {
       config.setOpcServerURL(serverAddress + ":" + port);
     }
 
-    boolean unauthenticated = selectedAlternativeAuthentication.equals(
-        SharedUserConfiguration.USER_AUTHENTICATION_ANONYMOUS
-    );
-    if (unauthenticated) {
+    if 
(selectedAlternativeAuthentication.equals(SharedUserConfiguration.USER_AUTHENTICATION_ANONYMOUS))
 {
       config.setIdentityConfig(new AnonymousIdentityConfig());
-    } else {
+    } else if 
(selectedAlternativeAuthentication.equals(USERNAME_GROUP.name())) {
       String username = extractor.singleValueParameter(USERNAME.name(), 
String.class);
       String password = extractor.secretValue(PASSWORD.name());
       config.setIdentityConfig(new UsernamePasswordIdentityConfig(username, 
password));
+    } else {
+      String privateKeyPem = 
extractor.secretValue(SharedUserConfiguration.X509_PRIVATE_KEY_PEM);
+      String publicKeyPem = 
extractor.textParameter(SharedUserConfiguration.X509_PUBLIC_KEY_PEM);
+      config.setIdentityConfig(new X509IdentityConfig(publicKeyPem, 
privateKeyPem));
     }
 
     return config;
diff --git 
a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/identity/X509IdentityConfig.java
 
b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/identity/X509IdentityConfig.java
new file mode 100644
index 0000000000..1b79459586
--- /dev/null
+++ 
b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/identity/X509IdentityConfig.java
@@ -0,0 +1,114 @@
+/*
+ * 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.streampipes.extensions.connectors.opcua.config.identity;
+
+import org.apache.commons.codec.digest.DigestUtils;
+import org.eclipse.milo.opcua.sdk.client.api.config.OpcUaClientConfigBuilder;
+import org.eclipse.milo.opcua.sdk.client.api.identity.X509IdentityProvider;
+
+import java.io.ByteArrayInputStream;
+import java.nio.charset.StandardCharsets;
+import java.security.KeyFactory;
+import java.security.PrivateKey;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.Base64;
+import java.util.Objects;
+
+public class X509IdentityConfig implements IdentityConfig {
+
+  private final X509Certificate certificate;
+  private final PrivateKey privateKey;
+
+  /**
+   * @param certificatePem String containing one X.509 certificate in PEM
+   *                       (-----BEGIN CERTIFICATE----- ... -----END 
CERTIFICATE-----)
+   * @param privateKeyPem  String containing a PKCS#8 private key in PEM
+   *                       (-----BEGIN PRIVATE KEY----- ... -----END PRIVATE 
KEY-----)
+   */
+  public X509IdentityConfig(String certificatePem, String privateKeyPem) {
+    this.certificate = parseCertificatePem(certificatePem);
+    this.privateKey  = parsePrivateKeyPem(privateKeyPem);
+  }
+
+  @Override
+  public void configureIdentity(OpcUaClientConfigBuilder builder) {
+    builder.setIdentityProvider(new X509IdentityProvider(certificate, 
privateKey));
+  }
+
+  @Override
+  public String toString() {
+    try {
+      String subject = certificate.getSubjectX500Principal().getName();
+      String thumb = DigestUtils.sha256Hex(certificate.getEncoded());
+      return String.format("%s [%s]", subject, thumb);
+    } catch (Exception e) {
+      return "X509PemIdentity";
+    }
+  }
+
+  private X509Certificate parseCertificatePem(String pem) {
+    Objects.requireNonNull(pem, "certificatePem");
+    byte[] der = extractPemBlock(pem, "CERTIFICATE");
+    try {
+      CertificateFactory cf = CertificateFactory.getInstance("X.509");
+      return (X509Certificate) cf.generateCertificate(new 
ByteArrayInputStream(der));
+    } catch (Exception e) {
+      throw new IllegalArgumentException("Failed to parse X.509 certificate 
PEM", e);
+    }
+  }
+
+  private PrivateKey parsePrivateKeyPem(String pem) {
+    Objects.requireNonNull(pem, "privateKeyPem");
+    byte[] der = extractPemBlock(pem, "PRIVATE KEY"); // PKCS#8
+    PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(der);
+
+    // Try common algorithms in order.
+    String[] algos = new String[] { "RSA", "EC", "Ed25519", "Ed448" };
+    for (String algo : algos) {
+      try {
+        KeyFactory kf = KeyFactory.getInstance(algo);
+        return kf.generatePrivate(spec);
+      } catch (Exception ignore) {
+        // try next
+      }
+    }
+    throw new IllegalArgumentException(
+        "Unsupported or invalid PKCS#8 private key. " +
+            "Make sure it is an unencrypted PKCS#8 key (BEGIN PRIVATE KEY).");
+  }
+
+  private static byte[] extractPemBlock(String pem, String type) {
+    String begin = "-----BEGIN " + type + "-----";
+    String end   = "-----END " + type + "-----";
+
+    String normalized = pem.replace("\r", "");
+    int start = normalized.indexOf(begin);
+    int stop  = normalized.indexOf(end);
+    if (start < 0 || stop < 0) {
+      throw new IllegalArgumentException("Missing PEM markers for " + type);
+    }
+    String base64 = normalized.substring(start + begin.length(), stop)
+        .replace("\n", "")
+        .replace("\t", "")
+        .replace(" ", "");
+    return 
Base64.getMimeDecoder().decode(base64.getBytes(StandardCharsets.US_ASCII));
+  }
+}
diff --git 
a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/migration/OpcUaAdapterMigrationV6.java
 
b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/migration/OpcUaAdapterMigrationV6.java
new file mode 100644
index 0000000000..619f565f71
--- /dev/null
+++ 
b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/migration/OpcUaAdapterMigrationV6.java
@@ -0,0 +1,76 @@
+/*
+ * 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.streampipes.extensions.connectors.opcua.migration;
+
+import org.apache.streampipes.extensions.api.extractor.IParameterExtractor;
+import 
org.apache.streampipes.extensions.api.extractor.IStaticPropertyExtractor;
+import org.apache.streampipes.extensions.api.migration.IAdapterMigrator;
+import org.apache.streampipes.extensions.connectors.opcua.adapter.OpcUaAdapter;
+import org.apache.streampipes.model.connect.adapter.AdapterDescription;
+import org.apache.streampipes.model.extensions.svcdiscovery.SpServiceTagPrefix;
+import org.apache.streampipes.model.migration.MigrationResult;
+import org.apache.streampipes.model.migration.ModelMigratorConfig;
+import org.apache.streampipes.model.staticproperty.StaticProperty;
+import org.apache.streampipes.model.staticproperty.StaticPropertyAlternatives;
+import org.apache.streampipes.sdk.StaticProperties;
+import org.apache.streampipes.sdk.helpers.Alternatives;
+import org.apache.streampipes.sdk.helpers.Labels;
+
+import java.util.List;
+
+import static 
org.apache.streampipes.extensions.connectors.opcua.config.SharedUserConfiguration.X509_GROUP;
+import static 
org.apache.streampipes.extensions.connectors.opcua.config.SharedUserConfiguration.X509_PRIVATE_KEY_PEM;
+import static 
org.apache.streampipes.extensions.connectors.opcua.config.SharedUserConfiguration.X509_PUBLIC_KEY_PEM;
+
+public class OpcUaAdapterMigrationV6 implements IAdapterMigrator {
+  @Override
+  public ModelMigratorConfig config() {
+    return new ModelMigratorConfig(
+        OpcUaAdapter.ID,
+        SpServiceTagPrefix.ADAPTER,
+        5,
+        6
+    );
+  }
+
+  @Override
+  public MigrationResult<AdapterDescription> migrate(AdapterDescription 
element,
+                                                     IStaticPropertyExtractor 
extractor) throws RuntimeException {
+    var config = element.getConfig();
+    element.setConfig(migrate(config, 4));
+
+    return MigrationResult.success(element);
+  }
+
+  public List<StaticProperty> migrate(List<StaticProperty> staticProperties,
+                                      int authenticationConfigIndex) {
+    var authentication = staticProperties.get(authenticationConfigIndex);
+
+    if (authentication instanceof StaticPropertyAlternatives) {
+      var group = StaticProperties.group(
+          Labels.withId(X509_GROUP),
+          StaticProperties.secretValue(Labels.withId(X509_PRIVATE_KEY_PEM)),
+          
StaticProperties.stringFreeTextProperty(Labels.withId(X509_PUBLIC_KEY_PEM), 
true, false));
+      group.setHorizontalRendering(false);
+      var x509Alternative = Alternatives.from(Labels.withId(X509_GROUP), 
group);
+      ((StaticPropertyAlternatives) 
authentication).getAlternatives().add(x509Alternative);
+    }
+    return staticProperties;
+  }
+}
diff --git 
a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/migration/OpcUaSinkMigrationV2.java
 
b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/migration/OpcUaSinkMigrationV2.java
new file mode 100644
index 0000000000..29656a9c75
--- /dev/null
+++ 
b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/migration/OpcUaSinkMigrationV2.java
@@ -0,0 +1,48 @@
+/*
+ * 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.streampipes.extensions.connectors.opcua.migration;
+
+import 
org.apache.streampipes.extensions.api.extractor.IDataSinkParameterExtractor;
+import org.apache.streampipes.extensions.api.migration.IDataSinkMigrator;
+import org.apache.streampipes.extensions.connectors.opcua.sink.OpcUaSink;
+import org.apache.streampipes.model.extensions.svcdiscovery.SpServiceTagPrefix;
+import org.apache.streampipes.model.graph.DataSinkInvocation;
+import org.apache.streampipes.model.migration.MigrationResult;
+import org.apache.streampipes.model.migration.ModelMigratorConfig;
+
+public class OpcUaSinkMigrationV2 implements IDataSinkMigrator {
+  @Override
+  public ModelMigratorConfig config() {
+    return new ModelMigratorConfig(
+        OpcUaSink.ID,
+        SpServiceTagPrefix.DATA_SINK,
+        1,
+        2
+    );
+  }
+
+  @Override
+  public MigrationResult<DataSinkInvocation> migrate(DataSinkInvocation 
element,
+                                                     
IDataSinkParameterExtractor extractor) throws RuntimeException {
+    var config = element.getStaticProperties();
+    var migratedConfigs = new OpcUaAdapterMigrationV6().migrate(config, 3);
+    element.setStaticProperties(migratedConfigs);
+    return MigrationResult.success(element);
+  }
+}
diff --git 
a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/sink/OpcUaSink.java
 
b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/sink/OpcUaSink.java
index 6e908f1102..9270f18b8c 100644
--- 
a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/sink/OpcUaSink.java
+++ 
b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/sink/OpcUaSink.java
@@ -57,7 +57,7 @@ public class OpcUaSink implements IStreamPipesDataSink, 
SupportsRuntimeConfig {
 
   @Override
   public IDataSinkConfiguration declareConfig() {
-    var builder = DataSinkBuilder.create(ID, 0)
+    var builder = DataSinkBuilder.create(ID, 2)
         .withLocales(Locales.EN)
         .withAssets(ExtensionAssetType.DOCUMENTATION, ExtensionAssetType.ICON)
         .category(DataSinkType.FORWARD)
diff --git 
a/streampipes-extensions/streampipes-connectors-opcua/src/main/resources/org.apache.streampipes.connect.iiot.adapters.opcua/strings.en
 
b/streampipes-extensions/streampipes-connectors-opcua/src/main/resources/org.apache.streampipes.connect.iiot.adapters.opcua/strings.en
index a7a8008e52..853b9f11ea 100644
--- 
a/streampipes-extensions/streampipes-connectors-opcua/src/main/resources/org.apache.streampipes.connect.iiot.adapters.opcua/strings.en
+++ 
b/streampipes-extensions/streampipes-connectors-opcua/src/main/resources/org.apache.streampipes.connect.iiot.adapters.opcua/strings.en
@@ -85,3 +85,12 @@ incomplete-event-handling.description=Select how events with 
missing values (e.g
 
 NAMING_STRATEGY.title=Naming Strategy
 NAMING_STRATEGY.description=Select how the runtime name of the OPC-UA nodes 
are generated.
+
+x509Group.title=X.509 Certificate
+x509Group.description=
+
+x509PrivateKeyPem.title=Private Key PEM
+x509PrivateKeyPem.description=Private key in PEM format
+
+x509PublicKeyPem.title=Certificate PEM
+x509PublicKeyPem.description=Public key in PEM format
diff --git 
a/streampipes-extensions/streampipes-connectors-opcua/src/main/resources/org.apache.streampipes.sinks.databases.jvm.opcua/strings.en
 
b/streampipes-extensions/streampipes-connectors-opcua/src/main/resources/org.apache.streampipes.sinks.databases.jvm.opcua/strings.en
index b26c09c598..a08aef30b6 100644
--- 
a/streampipes-extensions/streampipes-connectors-opcua/src/main/resources/org.apache.streampipes.sinks.databases.jvm.opcua/strings.en
+++ 
b/streampipes-extensions/streampipes-connectors-opcua/src/main/resources/org.apache.streampipes.sinks.databases.jvm.opcua/strings.en
@@ -68,3 +68,12 @@ userAuthentication.description=Choose an authentication 
method for the user
 
 anonymous.title=Anonymous
 anonymous.description=
+
+x509Group.title=X.509 Certificate
+x509Group.description=
+
+x509PrivateKeyPem.title=Private Key PEM
+x509PrivateKeyPem.description=Private key in PEM format
+
+x509PublicKeyPem.title=Certificate PEM
+x509PublicKeyPem.description=Public key in PEM format
diff --git a/ui/src/app/core-ui/core-ui.module.ts 
b/ui/src/app/core-ui/core-ui.module.ts
index cbc12c8ca2..9d2886d92d 100644
--- a/ui/src/app/core-ui/core-ui.module.ts
+++ b/ui/src/app/core-ui/core-ui.module.ts
@@ -67,7 +67,6 @@ import { PipelineElementTemplatePipe } from 
'./pipeline-element-template-config/
 import { StatusWidgetComponent } from './status/status-widget.component';
 import { SpSimpleMetricsComponent } from 
'./monitoring/simple-metrics/simple-metrics.component';
 import { SpSimpleLogsComponent } from 
'./monitoring/simple-logs/simple-logs.component';
-//import { HelpComponent } from 
'../../../projects/streampipes/shared-ui/src/lib/dialog/pipeline-element-help/help.component';
 import { MatButtonModule } from '@angular/material/button';
 import { MatCardModule } from '@angular/material/card';
 import { MatCheckboxModule } from '@angular/material/checkbox';
@@ -108,6 +107,7 @@ import { JsonPrettyPrintPipe } from 
'./pipes/json-pretty-print.pipe';
 import { YamlPrettyPrintPipe } from './pipes/yaml-pretty-print.pipe';
 import { TopicsComponent } from './topics/topics.component';
 import { TranslateModule } from '@ngx-translate/core';
+import { TextFieldModule } from '@angular/cdk/text-field';
 
 @NgModule({
     imports: [
@@ -159,6 +159,7 @@ import { TranslateModule } from '@ngx-translate/core';
         MatTreeModule,
         TranslateModule.forChild(),
         LeafletModule,
+        TextFieldModule,
     ],
     declarations: [
         ConfigurationCodePanelComponent,
diff --git 
a/ui/src/app/core-ui/static-properties/static-free-input/static-free-input.component.html
 
b/ui/src/app/core-ui/static-properties/static-free-input/static-free-input.component.html
index 16ef21767d..73ee8ba815 100644
--- 
a/ui/src/app/core-ui/static-properties/static-free-input/static-free-input.component.html
+++ 
b/ui/src/app/core-ui/static-properties/static-free-input/static-free-input.component.html
@@ -22,7 +22,7 @@
         fxLayout="row"
         *ngIf="!staticProperty.valueSpecification && !staticProperty.multiLine"
     >
-        <mat-form-field fxFlex color="accent">
+        <mat-form-field fxFlex>
             <input
                 formControlName="{{ fieldName }}"
                 fxFlex
@@ -46,7 +46,6 @@
                 [attr.data-cy]="fieldName"
                 [discrete]="true"
                 [displayWith]="formatLabel"
-                color="accent"
             >
                 <input
                     matSliderThumb
@@ -80,20 +79,32 @@
             </div>
         </div>
         <div fxFlex="100" *ngIf="staticProperty.multiLine">
-            <quill-editor
-                fxFlex="100"
-                *ngIf="staticProperty.htmlFontFormat"
-                #textEditor
-                formControlName="{{ fieldName }}"
-                [modules]="quillModulesFontFormat"
-            ></quill-editor>
-            <quill-editor
-                fxFlex="100"
-                *ngIf="!staticProperty.htmlFontFormat"
-                #textEditor
-                formControlName="{{ fieldName }}"
-                [modules]="quillModules"
-            ></quill-editor>
+            @if (staticProperty.htmlAllowed) {
+                <quill-editor
+                    fxFlex="100"
+                    *ngIf="staticProperty.htmlFontFormat"
+                    #textEditor
+                    formControlName="{{ fieldName }}"
+                    [modules]="quillModulesFontFormat"
+                ></quill-editor>
+                <quill-editor
+                    fxFlex="100"
+                    *ngIf="!staticProperty.htmlFontFormat"
+                    #textEditor
+                    formControlName="{{ fieldName }}"
+                    [modules]="quillModules"
+                ></quill-editor>
+            } @else {
+                <mat-form-field fxFlex>
+                    <textarea
+                        formControlName="{{ fieldName }}"
+                        matInput
+                        [placeholder]="staticProperty.label"
+                        (blur)="emitUpdate()"
+                        [attr.data-cy]="fieldName"
+                    ></textarea>
+                </mat-form-field>
+            }
         </div>
     </div>
 </div>
diff --git 
a/ui/src/app/core-ui/static-properties/static-free-input/static-free-input.component.ts
 
b/ui/src/app/core-ui/static-properties/static-free-input/static-free-input.component.ts
index 664471eb25..f5a963b626 100644
--- 
a/ui/src/app/core-ui/static-properties/static-free-input/static-free-input.component.ts
+++ 
b/ui/src/app/core-ui/static-properties/static-free-input/static-free-input.component.ts
@@ -32,6 +32,7 @@ import {
 import { AbstractValidatedStaticPropertyRenderer } from 
'../base/abstract-validated-static-property';
 import { QuillEditorComponent } from 'ngx-quill';
 import { TranslateService } from '@ngx-translate/core';
+import { CdkTextareaAutosize } from '@angular/cdk/text-field';
 
 @Component({
     selector: 'sp-app-static-free-input',
@@ -61,6 +62,8 @@ export class StaticFreeInputComponent
     @ViewChild('textEditor', { static: false })
     quillEditorComponent: QuillEditorComponent;
 
+    @ViewChild('autosize') autosize: CdkTextareaAutosize;
+
     constructor() {
         super();
     }
@@ -69,6 +72,7 @@ export class StaticFreeInputComponent
         this.addValidator(this.staticProperty.value, this.collectValidators());
         this.enableValidators();
         this.emitUpdate();
+        console.log(this.staticProperty);
     }
 
     collectValidators() {

Reply via email to