diff --git a/.gitignore b/.gitignore
index 1755562..ae3bb31 100644
--- a/.gitignore
+++ b/.gitignore
@@ -354,3 +354,4 @@ MigrationBackup/
/pki
/store
/trustlist.zip
+/customclientcert
diff --git a/AzureFileStorage.cs b/AzureFileStorage.cs
deleted file mode 100644
index ea7d79a..0000000
--- a/AzureFileStorage.cs
+++ /dev/null
@@ -1,164 +0,0 @@
-
-namespace Opc.Ua.Cloud.Publisher
-{
- using Azure.Storage.Blobs;
- using Azure.Storage.Blobs.Models;
- using Microsoft.Extensions.Logging;
- using System;
- using System.IO;
- using System.Threading;
- using System.Threading.Tasks;
- using Opc.Ua.Cloud.Publisher.Interfaces;
-
- public class AzureFileStorage : IFileStorage
- {
- private readonly ILogger _logger;
-
- private string _blobContainerName = "uacloudpublisher";
-
- public AzureFileStorage(ILoggerFactory logger)
- {
- _logger = logger.CreateLogger("AzureFileStorage");
-
- if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("STORAGE_CONTAINER_NAME")))
- {
- _blobContainerName = Environment.GetEnvironmentVariable("STORAGE_CONTAINER_NAME");
- }
- }
-
- public async Task FindFileAsync(string path, string name, CancellationToken cancellationToken = default)
- {
- if (string.IsNullOrEmpty(path) || string.IsNullOrEmpty(name))
- {
- return null;
- }
-
- try
- {
- if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("STORAGE_CONNECTION_STRING")))
- {
- // open blob storage
- BlobContainerClient container = new BlobContainerClient(Environment.GetEnvironmentVariable("STORAGE_CONNECTION_STRING"), _blobContainerName);
- await container.CreateIfNotExistsAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
-
- Diagnostics.Singleton.Info.ConnectedToCloudStorage = true;
-
- var resultSegment = container.GetBlobsAsync();
- await foreach (BlobItem blobItem in resultSegment.ConfigureAwait(false))
- {
- if (blobItem.Name.Contains(path.TrimStart('/')) && blobItem.Name.Contains(name))
- {
- return blobItem.Name;
- }
- }
- }
-
- return null;
- }
- catch (Exception ex)
- {
- _logger.LogError(ex.Message);
- return null;
- }
- }
-
- public async Task StoreFileAsync(string path, byte[] content, CancellationToken cancellationToken = default)
- {
- if (string.IsNullOrEmpty(path) || (content == null))
- {
- return null;
- }
-
- try
- {
- if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("STORAGE_CONNECTION_STRING")))
- {
- // open blob storage
- BlobContainerClient container = new BlobContainerClient(Environment.GetEnvironmentVariable("STORAGE_CONNECTION_STRING"), _blobContainerName);
- await container.CreateIfNotExistsAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
-
- Diagnostics.Singleton.Info.ConnectedToCloudStorage = true;
-
- // Get a reference to the blob
- BlobClient blob = container.GetBlobClient(path);
-
- // Open the file and upload its data
- using (MemoryStream file = new MemoryStream(content))
- {
- await blob.DeleteIfExistsAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
- await blob.UploadAsync(file, cancellationToken).ConfigureAwait(false);
-
- // Verify uploaded
- BlobProperties properties = await blob.GetPropertiesAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
- if (file.Length != properties.ContentLength)
- {
- throw new Exception("Could not verify upload!");
- }
-
- return path;
- }
- }
-
- return null;
- }
- catch (Exception ex)
- {
- _logger.LogError(ex.Message);
- return null;
- }
- }
-
- public async Task LoadFileAsync(string name, CancellationToken cancellationToken = default)
- {
- if (string.IsNullOrEmpty(name))
- {
- return null;
- }
-
- try
- {
- if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("STORAGE_CONNECTION_STRING")))
- {
- // open blob storage
- BlobContainerClient container = new BlobContainerClient(Environment.GetEnvironmentVariable("STORAGE_CONNECTION_STRING"), _blobContainerName);
- await container.CreateIfNotExistsAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
-
- Diagnostics.Singleton.Info.ConnectedToCloudStorage = true;
-
- var resultSegment = container.GetBlobsAsync();
- await foreach (BlobItem blobItem in resultSegment.ConfigureAwait(false))
- {
- if (blobItem.Name.Equals(name))
- {
- // Get a reference to the blob
- BlobClient blob = container.GetBlobClient(blobItem.Name);
-
- // Download the blob's contents and save it to a file
- BlobDownloadInfo download = await blob.DownloadAsync(cancellationToken).ConfigureAwait(false);
- using (MemoryStream file = new MemoryStream())
- {
- await download.Content.CopyToAsync(file, cancellationToken).ConfigureAwait(false);
-
- // Verify download
- BlobProperties properties = await blob.GetPropertiesAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
- if (file.Length != properties.ContentLength)
- {
- throw new Exception("Could not verify upload!");
- }
-
- return file.ToArray();
- }
- }
- }
- }
-
- return null;
- }
- catch (Exception ex)
- {
- _logger.LogError(ex.Message);
- return null;
- }
- }
- }
-}
diff --git a/Controllers/CertManagerController.cs b/Controllers/CertManagerController.cs
index 598e779..9ee83dc 100644
--- a/Controllers/CertManagerController.cs
+++ b/Controllers/CertManagerController.cs
@@ -6,11 +6,14 @@ namespace Opc.Ua.Cloud.Publisher.Controllers
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.Extensions.Logging;
using Opc.Ua.Cloud.Publisher.Interfaces;
+ using Opc.Ua.Cloud.Publisher.Models;
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
+ using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
+ using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -27,10 +30,10 @@ public CertManagerController(IUAApplication app, ILoggerFactory loggerFactory)
public IActionResult Index()
{
- return LoadTrustlist();
+ return View("Index", new CertManagerModel() { Certs = new SelectList(LoadTrustlist()) });
}
- private IActionResult LoadTrustlist()
+ private List LoadTrustlist()
{
List trustList = new();
CertificateTrustList ownTrustList = _app.UAApplicationInstance.ApplicationConfiguration.SecurityConfiguration.TrustedPeerCertificates;
@@ -39,7 +42,7 @@ private IActionResult LoadTrustlist()
trustList.Add(cert.Subject + " [" + cert.Thumbprint + "] ");
}
- return View("Index", new SelectList(trustList));
+ return trustList;
}
[HttpPost]
@@ -68,12 +71,12 @@ public async Task Load(IFormFile file)
// store in our own trust list
await _app.UAApplicationInstance.AddOwnCertificateToTrustedStoreAsync(certificate, CancellationToken.None).ConfigureAwait(false);
- return LoadTrustlist();
+ return View("Index", new CertManagerModel() { Certs = new SelectList(LoadTrustlist()) });
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
- return View("Index", new SelectList(new List() { ex.Message }));
+ return View("Index", new CertManagerModel() { Certs = new SelectList(new List() { ex.Message }) });
}
}
@@ -97,7 +100,29 @@ public ActionResult DownloadTrustlist()
catch (Exception ex)
{
_logger.LogError(ex.Message);
- return View("Index", new SelectList(new List() { ex.Message }));
+ return View("Index", new CertManagerModel() { Certs = new SelectList(new List() { ex.Message }) });
+ }
+ }
+ [HttpPost]
+ public ActionResult EncryptString(string plainTextString)
+ {
+ try
+ {
+ X509Certificate2 cert = _app.IssuerCert;
+ using RSA rsa = cert.GetRSAPublicKey();
+ if (!string.IsNullOrEmpty(plainTextString) && (rsa != null))
+ {
+ return View("Index", new CertManagerModel() { Encrypt = Convert.ToBase64String(rsa.Encrypt(Encoding.UTF8.GetBytes(plainTextString), RSAEncryptionPadding.Pkcs1)), Certs = new SelectList(LoadTrustlist()) });
+ }
+ else
+ {
+ throw new Exception("Encryption failed");
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex.Message);
+ return View("Index", new CertManagerModel() { Certs = new SelectList(new List() { ex.Message }) });
}
}
}
diff --git a/Controllers/ConfigController.cs b/Controllers/ConfigController.cs
index bd8504a..537594b 100644
--- a/Controllers/ConfigController.cs
+++ b/Controllers/ConfigController.cs
@@ -71,13 +71,13 @@ public async Task LocalCertOpen(IFormFile file)
// update cert file hash and expiry
X509Certificate2 cert = new X509Certificate2(filePath);
- Settings.Instance.UACertThumbprint = cert.Thumbprint;
- Settings.Instance.UACertExpiry = cert.NotAfter;
+ Settings.Instance.MQTTClientCertThumbprint = cert.Thumbprint;
+ Settings.Instance.MQTTClientCertExpiry = cert.NotAfter;
}
catch (Exception ex)
{
- Settings.Instance.UACertThumbprint = ex.Message;
- Settings.Instance.UACertExpiry = DateTime.MinValue;
+ Settings.Instance.MQTTClientCertThumbprint = ex.Message;
+ Settings.Instance.MQTTClientCertExpiry = DateTime.MinValue;
}
return View("Index", Settings.Instance);
diff --git a/Controllers/PublishedController.cs b/Controllers/PublishedController.cs
index a129595..ace239a 100644
--- a/Controllers/PublishedController.cs
+++ b/Controllers/PublishedController.cs
@@ -12,24 +12,22 @@ namespace Opc.Ua.Cloud.Publisher.Controllers
using System.Collections.Generic;
using System.IO;
using System.Text;
+ using System.Threading.Tasks;
public class PublishedController : Controller
{
private readonly ILogger _logger;
private readonly IPublishedNodesFileHandler _publishedNodesFileHandler;
private readonly IUAClient _uaclient;
- private readonly IFileStorage _storage;
public PublishedController(
ILoggerFactory loggerFactory,
IPublishedNodesFileHandler publishedNodesFileHandler,
- IUAClient client,
- IFileStorage storage)
+ IUAClient client)
{
_logger = loggerFactory.CreateLogger("PublishedController");
_publishedNodesFileHandler = publishedNodesFileHandler;
_uaclient = client;
- _storage = storage;
}
public IActionResult Index()
@@ -89,25 +87,22 @@ public IActionResult LoadPersisted()
{
try
{
- string persistencyFilePath = _storage.FindFileAsync(Path.Combine(Directory.GetCurrentDirectory(), "settings"), "persistency.json").GetAwaiter().GetResult();
- byte[] persistencyFile = _storage.LoadFileAsync(persistencyFilePath).GetAwaiter().GetResult();
+ byte[] persistencyFile = System.IO.File.ReadAllBytes(Path.Combine(Directory.GetCurrentDirectory(), "settings", "persistency.json"));
if (persistencyFile == null)
{
// no file persisted yet
- _logger.LogInformation("Persistency file not found.");
+ throw new Exception("Persistency file not found.");
}
else
{
- _logger.LogInformation($"Parsing persistency file...");
- _publishedNodesFileHandler.ParseFile(persistencyFile);
- _logger.LogInformation("Persistency file parsed successfully.");
+ _ = Task.Run(() => _publishedNodesFileHandler.ParseFile(persistencyFile));
}
return View("Index", GeneratePublishedNodesArray());
}
catch (Exception ex)
{
- _logger.LogError(ex, "Persistency file not loaded!");
+ _logger.LogError(ex.Message);
return View("Index", new string[] { "Error: " + ex.Message });
}
}
diff --git a/Diagnostics.cs b/Diagnostics.cs
index 0a04626..e07cdf0 100644
--- a/Diagnostics.cs
+++ b/Diagnostics.cs
@@ -52,7 +52,6 @@ private void Clear()
{
Info.PublisherStartTime = DateTime.UtcNow;
Info.ConnectedToBroker = false;
- Info.ConnectedToCloudStorage = false;
Info.NumberOfOpcSessionsConnected = 0;
Info.NumberOfOpcSubscriptionsConnected = 0;
Info.NumberOfOpcMonitoredItemsMonitored = 0;
@@ -98,7 +97,6 @@ public async Task RunAsync(CancellationToken cancellationToken = default)
_hubClient.AddOrUpdateTableEntry("Publisher Start Time", Info.PublisherStartTime.ToString());
_hubClient.AddOrUpdateTableEntry("Connected to broker(s)", Info.ConnectedToBroker.ToString());
- _hubClient.AddOrUpdateTableEntry("Connected to cloud storage/OneLake", Info.ConnectedToCloudStorage.ToString());
_hubClient.AddOrUpdateTableEntry("OPC UA sessions", Info.NumberOfOpcSessionsConnected.ToString());
_hubClient.AddOrUpdateTableEntry("OPC UA subscriptions", Info.NumberOfOpcSubscriptionsConnected.ToString());
_hubClient.AddOrUpdateTableEntry("OPC UA monitored items", Info.NumberOfOpcMonitoredItemsMonitored.ToString());
@@ -131,7 +129,6 @@ public async Task RunAsync(CancellationToken cancellationToken = default)
if (ticks % 10 == 0)
{
DiagnosticsSend("ConnectedToBroker", new DataValue(Info.ConnectedToBroker));
- DiagnosticsSend("ConnectedToCloudStorage", new DataValue(Info.ConnectedToCloudStorage));
DiagnosticsSend("NumOpcSessions", new DataValue(Info.NumberOfOpcSessionsConnected));
DiagnosticsSend("NumOpcSubscriptions", new DataValue(Info.NumberOfOpcSubscriptionsConnected));
DiagnosticsSend("NumOpcMonitoredItems", new DataValue(Info.NumberOfOpcMonitoredItemsMonitored));
diff --git a/Interfaces/IFileStorage.cs b/Interfaces/IFileStorage.cs
deleted file mode 100644
index 4a4e976..0000000
--- a/Interfaces/IFileStorage.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-
-namespace Opc.Ua.Cloud.Publisher.Interfaces
-{
- using System.Threading;
- using System.Threading.Tasks;
-
- public interface IFileStorage
- {
- Task FindFileAsync(string path, string name, CancellationToken cancellationToken = default);
-
- Task StoreFileAsync(string name, byte[] content, CancellationToken cancellationToken = default);
-
- Task LoadFileAsync(string name, CancellationToken cancellationToken = default);
- }
-}
diff --git a/LocalFileStorage.cs b/LocalFileStorage.cs
deleted file mode 100644
index a396de6..0000000
--- a/LocalFileStorage.cs
+++ /dev/null
@@ -1,89 +0,0 @@
-
-namespace Opc.Ua.Cloud.Publisher
-{
- using Microsoft.Extensions.Logging;
- using System;
- using System.IO;
- using System.Threading;
- using System.Threading.Tasks;
- using Opc.Ua.Cloud.Publisher.Interfaces;
-
- public class LocalFileStorage : IFileStorage
- {
- private readonly ILogger _logger;
-
- public LocalFileStorage(ILoggerFactory logger)
- {
- _logger = logger.CreateLogger("LocalFileStorage");
- }
-
- public Task FindFileAsync(string path, string name, CancellationToken cancellationToken = default)
- {
- if (string.IsNullOrEmpty(path) || string.IsNullOrEmpty(name))
- {
- return null;
- }
-
- try
- {
- foreach (string filePath in Directory.GetFiles(path))
- {
- if (filePath.Contains(name))
- {
- return Task.FromResult(filePath);
- }
- }
-
- return Task.FromResult(string.Empty);
- }
- catch (Exception ex)
- {
- _logger.LogError(ex.Message);
- return Task.FromResult(string.Empty);
- }
- }
-
- public async Task StoreFileAsync(string path, byte[] content, CancellationToken cancellationToken = default)
- {
- if (string.IsNullOrEmpty(path) || (content == null))
- {
- return null;
- }
-
- try
- {
- if (!Directory.Exists(Path.GetDirectoryName(path)))
- {
- Directory.CreateDirectory(Path.GetDirectoryName(path));
- }
-
- await File.WriteAllBytesAsync(path, content).ConfigureAwait(false);
-
- return path;
- }
- catch (Exception ex)
- {
- _logger.LogError(ex.Message);
- return null;
- }
- }
-
- public async Task LoadFileAsync(string path, CancellationToken cancellationToken = default)
- {
- if (string.IsNullOrEmpty(path))
- {
- return null;
- }
-
- try
- {
- return await File.ReadAllBytesAsync(path).ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- _logger.LogError(ex.Message);
- return null;
- }
- }
- }
-}
diff --git a/Models/CertManagerModel.cs b/Models/CertManagerModel.cs
new file mode 100644
index 0000000..b7f83b2
--- /dev/null
+++ b/Models/CertManagerModel.cs
@@ -0,0 +1,11 @@
+using Microsoft.AspNetCore.Mvc.Rendering;
+
+namespace Opc.Ua.Cloud.Publisher.Models
+{
+ public class CertManagerModel
+ {
+ public SelectList Certs { get; set; }
+
+ public string Encrypt { get; set; }
+ }
+}
diff --git a/OneLakeFileStorage.cs b/OneLakeFileStorage.cs
deleted file mode 100644
index cdf55cc..0000000
--- a/OneLakeFileStorage.cs
+++ /dev/null
@@ -1,190 +0,0 @@
-
-namespace Opc.Ua.Cloud.Publisher
-{
- using Azure.Identity;
- using Azure.Storage.Files.DataLake;
- using Azure.Storage.Files.DataLake.Models;
- using Microsoft.Extensions.Logging;
- using Opc.Ua.Cloud.Publisher.Interfaces;
- using System;
- using System.IO;
- using System.Threading;
- using System.Threading.Tasks;
-
- public class OneLakeFileStorage : IFileStorage
- {
- private readonly ILogger _logger;
-
- private string _blobContainerName = "uacloudpublisher";
-
- private DeviceCodeCredential _credential;
-
- private DataLakeServiceClient _dataLakeServiceClient;
-
- private DataLakeFileSystemClient _fileSystemClient;
-
- private object _lock = new object();
-
- private Task MyDeviceCodeCallback(DeviceCodeInfo info, CancellationToken cancellation)
- {
- _logger.LogInformation(info.Message);
-
- Settings.Instance.AuthenticationCode = info.UserCode;
-
- return Task.CompletedTask;
- }
-
- public OneLakeFileStorage(ILoggerFactory logger)
- {
- _logger = logger.CreateLogger("OneLakeFileStorage");
-
- if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("STORAGE_CONTAINER_NAME")))
- {
- _blobContainerName = Environment.GetEnvironmentVariable("STORAGE_CONTAINER_NAME");
- }
-
- DeviceCodeCredentialOptions options = new()
- {
- DeviceCodeCallback = MyDeviceCodeCallback
- };
- _credential = new(options);
- }
-
- public Task FindFileAsync(string path, string name, CancellationToken cancellationToken = default)
- {
- if (string.IsNullOrEmpty(path) || string.IsNullOrEmpty(name))
- {
- return null;
- }
-
- try
- {
- lock (_lock)
- {
- VerifyOneLakeConnectivity();
-
- if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("STORAGE_CONNECTION_STRING")))
- {
- string[] connectionStringParts = Environment.GetEnvironmentVariable("STORAGE_CONNECTION_STRING").Split("/");
-
- string dirName = connectionStringParts[4] + "/Files/" + _blobContainerName + path;
- foreach (var fspath in _fileSystemClient.GetPaths(dirName))
- {
- if (fspath.Name.Contains(dirName + "/" + name))
- {
- return Task.FromResult(fspath.Name);
- }
- }
- }
- }
-
- return null;
- }
- catch (Exception ex)
- {
- _logger.LogError(ex.Message);
- return null;
- }
- }
-
- public Task StoreFileAsync(string path, byte[] content, CancellationToken cancellationToken = default)
- {
- if (string.IsNullOrEmpty(path) || (content == null) || (content.Length == 0))
- {
- return null;
- }
-
- try
- {
- lock (_lock)
- {
- VerifyOneLakeConnectivity();
-
- if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("STORAGE_CONNECTION_STRING")))
- {
- string[] connectionStringParts = Environment.GetEnvironmentVariable("STORAGE_CONNECTION_STRING").Split("/");
-
- string filePath = connectionStringParts[4] + "/Files/" + _blobContainerName + path;
- DataLakeFileClient client = _fileSystemClient.GetFileClient(filePath);
- client.Upload(new MemoryStream(content), true);
-
- return Task.FromResult(path);
- }
- }
-
- return null;
- }
- catch (Exception ex)
- {
- _logger.LogError(ex.Message);
- return null;
- }
- }
-
- public Task LoadFileAsync(string name, CancellationToken cancellationToken = default)
- {
- if (string.IsNullOrEmpty(name))
- {
- return null;
- }
-
- try
- {
- lock (_lock)
- {
- VerifyOneLakeConnectivity();
-
- if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("STORAGE_CONNECTION_STRING")))
- {
- string[] connectionStringParts = Environment.GetEnvironmentVariable("STORAGE_CONNECTION_STRING").Split("/");
-
- DataLakeFileClient client = _fileSystemClient.GetFileClient(name);
- Azure.Response response = client.Read();
- MemoryStream content = new();
- response.Value.Content.CopyTo(content);
- return Task.FromResult(content.ToArray());
- }
- }
-
- return null;
- }
- catch (Exception ex)
- {
- _logger.LogError(ex.Message);
- return null;
- }
- }
-
- private void VerifyOneLakeConnectivity()
- {
- if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("STORAGE_CONNECTION_STRING")))
- {
- string[] connectionStringParts = Environment.GetEnvironmentVariable("STORAGE_CONNECTION_STRING").Split("/");
-
- _dataLakeServiceClient = new DataLakeServiceClient(new Uri("https://" + connectionStringParts[2]), _credential);
- _fileSystemClient = _dataLakeServiceClient.GetFileSystemClient(connectionStringParts[3]);
-
- // make sure our directory exists
- string dirName = connectionStringParts[4] + "/Files/" + _blobContainerName;
- string authNotification = "Not required - OneLake access authenticated!";
- bool found = false;
- foreach (var fspath in _fileSystemClient.GetPaths(connectionStringParts[4] + "/Files"))
- {
- if (fspath.Name == dirName)
- {
- found = true;
- Settings.Instance.AuthenticationCode = authNotification;
- Diagnostics.Singleton.Info.ConnectedToCloudStorage = true;
- }
- }
-
- if (!found)
- {
- _fileSystemClient.CreateDirectory(connectionStringParts[4] + "/Files/" + _blobContainerName);
- Settings.Instance.AuthenticationCode = authNotification;
- Diagnostics.Singleton.Info.ConnectedToCloudStorage = true;
- }
- }
- }
- }
-}
diff --git a/Properties/launchSettings.json b/Properties/launchSettings.json
index 695da5c..f5a6415 100644
--- a/Properties/launchSettings.json
+++ b/Properties/launchSettings.json
@@ -31,12 +31,11 @@
"useSSL": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
- "STORAGE_TYPE": "",
- "STORAGE_CONTAINER_NAME": "uacloudpublisher",
- "STORAGE_CONNECTION_STRING": "DefaultEndpointsProtocol=https;AccountName=[yourstorageaccountname];AccountKey=[key];EndpointSuffix=core.windows.net",
"AZURE_OPENAI_API_ENDPOINT": "https://[yourinstancename].openai.azure.com/",
"AZURE_OPENAI_API_KEY": "",
- "AZURE_OPENAI_API_DEPLOYMENT_NAME": ""
+ "AZURE_OPENAI_API_DEPLOYMENT_NAME": "",
+ "OPCUA_USERNAME": "",
+ "OPCUA_PASSWORD": ""
}
}
}
diff --git a/PublishedNodesFileHandler.cs b/PublishedNodesFileHandler.cs
index f11f20f..5fde386 100644
--- a/PublishedNodesFileHandler.cs
+++ b/PublishedNodesFileHandler.cs
@@ -9,25 +9,52 @@ namespace Opc.Ua.Cloud.Publisher.Configuration
using Opc.Ua.Cloud.Publisher.Models;
using System;
using System.Collections.Generic;
+ using System.Security.Cryptography;
+ using System.Security.Cryptography.X509Certificates;
using System.Text;
public class PublishedNodesFileHandler : IPublishedNodesFileHandler
{
private readonly ILogger _logger;
private readonly IUAClient _uaClient;
+ private readonly IUAApplication _uaApplication;
private readonly StatusHubClient _hubClient;
public PublishedNodesFileHandler(
ILoggerFactory loggerFactory,
- IUAClient client)
+ IUAClient client,
+ IUAApplication uaApplication)
{
_logger = loggerFactory.CreateLogger("PublishedNodesFileHandler");
_uaClient = client;
+ _uaApplication = uaApplication;
_hubClient = new StatusHubClient((IHubContext)Program.AppHost.Services.GetService(typeof(IHubContext)));
}
+ private string DecryptString(string encryptedString)
+ {
+ if (!string.IsNullOrEmpty(encryptedString))
+ {
+ X509Certificate2 cert = _uaApplication.IssuerCert;
+ using RSA rsa = cert.GetRSAPrivateKey();
+ bool isBase64String = Convert.TryFromBase64String(encryptedString, new Span(new byte[encryptedString.Length]), out int bytesParsed);
+ if (isBase64String && (rsa != null))
+ {
+ return Encoding.UTF8.GetString(rsa.Decrypt(Convert.FromBase64String(encryptedString), RSAEncryptionPadding.Pkcs1));
+ }
+ else
+ {
+ return encryptedString;
+ }
+ }
+ else
+ {
+ return string.Empty;
+ }
+ }
public void ParseFile(byte[] content)
{
+ _logger.LogInformation($"Processing persistency file...");
List _configurationFileEntries = JsonConvert.DeserializeObject>(Encoding.UTF8.GetString(content));
// process loaded config file entries
@@ -35,9 +62,8 @@ public void ParseFile(byte[] content)
{
_logger.LogInformation($"Loaded {_configurationFileEntries.Count} config file entry/entries.");
- // figure out how many nodes there are in total
- // and capture all unique OPC UA server endpoints
- Dictionary uniqueEndpoints = new();
+ // figure out how many nodes there are in total and capture all unique OPC UA server endpoints
+ Dictionary uniqueEndpoints = new();
int totalNodeCount = 0;
foreach (PublishNodesInterfaceModel configFileEntry in _configurationFileEntries)
{
@@ -66,7 +92,7 @@ public void ParseFile(byte[] content)
{
try
{
- _uaClient.GDSServerPush(server.EndpointUrl, server.UserName, server.Password);
+ _uaClient.GDSServerPush(server.EndpointUrl, server.UserName, DecryptString(server.Password));
}
catch (Exception ex)
{
@@ -106,13 +132,21 @@ public void ParseFile(byte[] content)
EndpointUrl = new Uri(configFileEntry.EndpointUrl).ToString(),
OpcAuthenticationMode = configFileEntry.OpcAuthenticationMode,
Username = configFileEntry.UserName,
- Password = configFileEntry.Password
+ Password = DecryptString(configFileEntry.Password)
};
publishingInfo.Filter = new List();
publishingInfo.Filter.AddRange(opcEvent.Filter);
+ try
+ {
_uaClient.PublishNodeAsync(publishingInfo).GetAwaiter().GetResult();
+ }
+ catch (Exception ex)
+ {
+ // skip this event and log an error
+ _logger.LogError("Cannot publish event " + publishingInfo.ExpandedNodeId + " on server " + publishingInfo.EndpointUrl + "due to " + ex.Message);
+ }
currentpublishedNodeCount++;
_hubClient.UpdateClientProgressAsync(currentpublishedNodeCount * 100 / totalNodeCount).GetAwaiter().GetResult();
@@ -134,16 +168,26 @@ public void ParseFile(byte[] content)
SkipFirst = opcNode.SkipFirst,
OpcAuthenticationMode = configFileEntry.OpcAuthenticationMode,
Username = configFileEntry.UserName,
- Password = configFileEntry.Password
+ Password = DecryptString(configFileEntry.Password)
};
+ try
+ {
_uaClient.PublishNodeAsync(publishingInfo).GetAwaiter().GetResult();
+ }
+ catch (Exception ex)
+ {
+ // skip this variable and log an error
+ _logger.LogError("Cannot publish variable " + publishingInfo.ExpandedNodeId + " on server " + publishingInfo.EndpointUrl + "due to " + ex.Message);
+ }
currentpublishedNodeCount++;
_hubClient.UpdateClientProgressAsync(currentpublishedNodeCount * 100 / totalNodeCount).GetAwaiter().GetResult();
}
}
}
+
+ _logger.LogInformation("Publishednodes.json/persistency file processed successfully.");
}
}
}
diff --git a/README.md b/README.md
index 5535499..a90db4e 100644
--- a/README.md
+++ b/README.md
@@ -35,8 +35,6 @@ A cross-platform OPC UA cloud publisher reference implementation leveraging OPC
- Publishing on data changes or on regular intervals
- Supports `publishednodes.json` input file format
- Support for storing configuration files locally
-- Support for storing configuration files in the Azure cloud
-- Support for storing configuration files in Microsoft OneLake
- Support for Store & Forward during internet connection outages
- Support for username and password authentication
- Support for Intel/AMD `x64` and `arm64` architectures (Raspberry Pi4, etc.) with pre-built container images ready for use
@@ -80,15 +78,11 @@ And then point your browser to .
Note: We have also provided a [test environment](./TestEnvironment/readme.md) to get you started.
-### Persisting Settings
+### Persisting Logs, Settings, Published Nodes and OPC UA Certificates
-UA Cloud Publisher settings and published nodes configuration can be persisted in the Cloud across Docker container restarts by running:
+UA Cloud Publisher logs, settings, published nodes and OPC UA certificates can be persisted locally across Docker container restarts by running:
-`docker run -itd -e STORAGE_TYPE="Azure" -e STORAGE_CONNECTION_STRING="yourCloudStorageConnectionString" -p 80:80 ghcr.io/barnstee/ua-cloudpublisher:main`
-
-UA Cloud Publisher settings and published nodes configuration can be persisted locally across Docker container restarts by running:
-
-`docker run -itd -v c:/publisher/logs:/app/logs -v c:/publisher/settings:/app/settings -p 80:80 ghcr.io/barnstee/ua-cloudpublisher:main`
+`docker run -itd -v c:/publisher/logs:/app/logs -v c:/publisher/settings:/app/settings -v c:/publisher/pki:/app/pki -p 80:80 ghcr.io/barnstee/ua-cloudpublisher:main`
For Linux hosts, remove the `c:` instances from the command above.
@@ -100,10 +94,11 @@ UA Cloud Publisher contains a second broker client that can be used either to **
## Optional Environment Variables
-- `LOG_FILE_PATH` - path to the log file to use. Default is /app/logs/UACloudPublisher.log (in the Docker container).
-- `STORAGE_TYPE` - type of storage to use for settings and configuration files. Current options are `Azure` and `OneLake`. Default is local file storage (under `/app/settings/` in the Docker container).
-- `STORAGE_CONNECTION_STRING` - when using `STORAGE_TYPE`=`Azure` or `OneLake`, specifies the connection string to the cloud storage. For `OneLake`, this is called `URL` and can be retrieved from your Lakehouse `Files` folder properties in Microsoft Fabric.
-- `STORAGE_CONTAINER_NAME` - when using STORAGE_TYPE="Azure" or "OneLake", specifies the storage container name. Default is "uacloudpublisher".
+* AZURE_OPENAI_API_ENDPOINT - the endpoint URL of the Azure OpenAI instance to use in the form https://[yourinstancename].openai.azure.com/
+* AZURE_OPENAI_API_KEY - the key to use
+* AZURE_OPENAI_API_DEPLOYMENT_NAME - the deployment to use
+* OPCUA_USERNAME - OPC UA server username to use when none is specified in publishednodes.json file
+* OPCUA_PASSWORD - OPC UA server password to use when none is specified in publishednodes.json file
## PublishedNodes.json File Format
@@ -305,7 +300,6 @@ Response:
{
"PublisherStartTime": "2022-02-22T22:22:22.222Z",
"ConnectedToBroker": false,
- "ConnectedToCloudStorage": false,
"NumberOfOpcSessionsConnected": 0,
"NumberOfOpcSubscriptionsConnected": 0,
"NumberOfOpcMonitoredItemsMonitored": 0,
diff --git a/Settings.cs b/Settings.cs
index e63d2e5..0cb4f67 100644
--- a/Settings.cs
+++ b/Settings.cs
@@ -31,7 +31,7 @@ public static Settings Instance
{
if (_instance == null)
{
- _instance = LoadAsync().GetAwaiter().GetResult();
+ _instance = Load();
}
}
}
@@ -47,16 +47,14 @@ public static Settings Instance
}
}
- private static async Task LoadAsync()
+ private static Settings Load()
{
ILoggerFactory loggerFactory = (ILoggerFactory)Program.AppHost.Services.GetService(typeof(ILoggerFactory));
ILogger logger = loggerFactory.CreateLogger("Settings");
- IFileStorage storage = (IFileStorage)Program.AppHost.Services.GetService(typeof(IFileStorage));
try
{
- string settingsFilePath = await storage.FindFileAsync(Path.Combine(Directory.GetCurrentDirectory(), "settings"), "settings.json").ConfigureAwait(false);
- byte[] settingsFile = await storage.LoadFileAsync(settingsFilePath).ConfigureAwait(false);
+ byte[] settingsFile = File.ReadAllBytes(Path.Combine(Directory.GetCurrentDirectory(), "settings", "settings.json"));
if (settingsFile == null)
{
// no file persisted yet
@@ -79,23 +77,32 @@ private static async Task LoadAsync()
}
}
- public async void Save()
+ public void Save()
{
ILoggerFactory loggerFactory = (ILoggerFactory)Program.AppHost.Services.GetService(typeof(ILoggerFactory));
ILogger logger = loggerFactory.CreateLogger("Settings");
- IFileStorage storage = (IFileStorage)Program.AppHost.Services.GetService(typeof(IFileStorage));
- if (await storage.StoreFileAsync(Path.Combine(Directory.GetCurrentDirectory(), "settings", "settings.json"), Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(this, Formatting.Indented))).ConfigureAwait(false) == null)
+ try
+ {
+ File.WriteAllBytes(Path.Combine(Directory.GetCurrentDirectory(), "settings", "settings.json"), Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(this, Formatting.Indented)));
+ }
+ catch (Exception)
{
logger.LogError("Could not store settings file. Settings won't be persisted!");
}
}
- public string AuthenticationCode { get; set; } = "Not applicable";
+ public string UAClientCertThumbprint { get; set; } = string.Empty;
+
+ public DateTime UAClientCertExpiry { get; set; } = DateTime.MinValue;
+
+ public string UAIssuerCertThumbprint { get; set; } = string.Empty;
+
+ public DateTime UAIssuerCertExpiry { get; set; } = DateTime.MinValue;
- public string UACertThumbprint { get; set; } = string.Empty;
+ public string MQTTClientCertThumbprint { get; set; } = "N/A";
- public DateTime UACertExpiry { get; set; } = DateTime.MinValue;
+ public DateTime MQTTClientCertExpiry { get; set; } = DateTime.MinValue;
public string PublisherName { get; set; } = "UACloudPublisher";
diff --git a/Startup.cs b/Startup.cs
index 60cb8f4..01f1281 100644
--- a/Startup.cs
+++ b/Startup.cs
@@ -37,14 +37,9 @@ public void ConfigureServices(IServiceCollection services)
services.AddRazorPages();
services.AddServerSideBlazor();
- string logFilePath = Configuration["LOG_FILE_PATH"];
- if (string.IsNullOrEmpty(logFilePath))
- {
- logFilePath = "./logs/UACloudPublisher.log";
- }
services.AddLogging(logging =>
{
- logging.AddFile(logFilePath);
+ logging.AddFile("./logs/UACloudPublisher.log");
});
// add our singletons
@@ -75,14 +70,6 @@ public void ConfigureServices(IServiceCollection services)
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
-
- // setup file storage
- switch (Configuration["STORAGE_TYPE"])
- {
- case "Azure": services.AddSingleton(); break;
- case "OneLake": services.AddSingleton(); break;
- default: services.AddSingleton(); break;
- }
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
@@ -92,8 +79,7 @@ public void Configure(IApplicationBuilder app,
IUAApplication uaApp,
IMessageProcessor engine,
Settings.BrokerResolver brokerResolver,
- IPublishedNodesFileHandler publishedNodesFileHandler,
- IFileStorage storage)
+ IPublishedNodesFileHandler publishedNodesFileHandler)
{
ILogger logger = loggerFactory.CreateLogger("Statup");
@@ -164,23 +150,20 @@ public void Configure(IApplicationBuilder app,
{
try
{
- string persistencyFilePath = storage.FindFileAsync(Path.Combine(Directory.GetCurrentDirectory(), "settings"), "persistency.json").GetAwaiter().GetResult();
- byte[] persistencyFile = storage.LoadFileAsync(persistencyFilePath).GetAwaiter().GetResult();
+ byte[] persistencyFile = File.ReadAllBytes(Path.Combine(Directory.GetCurrentDirectory(), "settings", "persistency.json"));
if (persistencyFile == null)
{
// no file persisted yet
- logger.LogInformation("Persistency file not found.");
+ throw new Exception("Persistency file not found.");
}
else
{
- logger.LogInformation($"Parsing persistency file...");
- publishedNodesFileHandler.ParseFile(persistencyFile);
- logger.LogInformation("Persistency file parsed successfully.");
+ _ = Task.Run(() => publishedNodesFileHandler.ParseFile(persistencyFile));
}
}
catch (Exception ex)
{
- logger.LogError(ex, "Persistency file not loaded!");
+ logger.LogError(ex.Message);
}
}
});
diff --git a/UA-CloudPublisher.csproj b/UA-CloudPublisher.csproj
index a68601f..64c340e 100644
--- a/UA-CloudPublisher.csproj
+++ b/UA-CloudPublisher.csproj
@@ -45,9 +45,6 @@
-
-
-
diff --git a/UAApplication.cs b/UAApplication.cs
index 77135cf..0273ea0 100644
--- a/UAApplication.cs
+++ b/UAApplication.cs
@@ -8,6 +8,7 @@ namespace Opc.Ua.Cloud.Publisher
using Opc.Ua.Configuration;
using System;
using System.IO;
+ using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
@@ -15,7 +16,6 @@ namespace Opc.Ua.Cloud.Publisher
public class UAApplication : IUAApplication
{
private readonly ILogger _logger;
- private readonly IFileStorage _storage;
public X509Certificate2 IssuerCert { get; set; }
@@ -23,67 +23,15 @@ public class UAApplication : IUAApplication
public ReverseConnectManager ReverseConnectManager { get; set; } = new();
- public UAApplication(ILoggerFactory loggerFactory, IFileStorage storage)
+ public UAApplication(ILoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger("UAApplication");
- _storage = storage;
}
public async Task CreateAsync(CancellationToken cancellationToken = default)
{
_logger.LogInformation($"Creating OPC UA app named {Settings.Instance.PublisherName}");
- try
- {
- // load app cert from storage
- string certFilePath = await _storage.FindFileAsync(Path.Combine(Directory.GetCurrentDirectory(), "pki", "own", "certs"), Settings.Instance.PublisherName).ConfigureAwait(false);
- byte[] certFile = await _storage.LoadFileAsync(certFilePath).ConfigureAwait(false);
- if (certFile == null)
- {
- _logger.LogError("Could not load cert file, creating a new one. This means the new cert needs to be trusted by all OPC UA servers we connect to!");
- }
- else
- {
- if (!Path.IsPathRooted(certFilePath))
- {
- certFilePath = Path.DirectorySeparatorChar.ToString() + certFilePath;
- }
-
- if (!Directory.Exists(Path.GetDirectoryName(certFilePath)))
- {
- Directory.CreateDirectory(Path.GetDirectoryName(certFilePath));
- }
-
- File.WriteAllBytes(certFilePath, certFile);
- }
-
- // load app private key from storage
- string keyFilePath = await _storage.FindFileAsync(Path.Combine(Directory.GetCurrentDirectory(), "pki", "own", "private"), Settings.Instance.PublisherName).ConfigureAwait(false);
- byte[] keyFile = await _storage.LoadFileAsync(keyFilePath).ConfigureAwait(false);
- if (keyFile == null)
- {
- _logger.LogError("Could not load key file, creating a new one. This means the new cert generated from the key needs to be trusted by all OPC UA servers we connect to!");
- }
- else
- {
- if (!Path.IsPathRooted(keyFilePath))
- {
- keyFilePath = Path.DirectorySeparatorChar.ToString() + keyFilePath;
- }
-
- if (!Directory.Exists(Path.GetDirectoryName(keyFilePath)))
- {
- Directory.CreateDirectory(Path.GetDirectoryName(keyFilePath));
- }
-
- File.WriteAllBytes(keyFilePath, keyFile);
- }
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Cloud not load cert or private key files, creating a new ones. This means the new cert needs to be trusted by all OPC UA servers we connect to!");
- }
-
// create UA app
UAApplicationInstance = new ApplicationInstance
{
@@ -114,26 +62,8 @@ public async Task CreateAsync(CancellationToken cancellationToken = default)
else
{
// store UA cert thumbprint
- Settings.Instance.UACertThumbprint = UAApplicationInstance.ApplicationConfiguration.SecurityConfiguration.ApplicationCertificate.Certificate.Thumbprint;
- Settings.Instance.UACertExpiry = UAApplicationInstance.ApplicationConfiguration.SecurityConfiguration.ApplicationCertificate.Certificate.NotAfter;
-
- // store app certs
- foreach (string filePath in Directory.EnumerateFiles(Path.Combine(Directory.GetCurrentDirectory(), "pki", "own", "certs"), "*.der"))
- {
- await _storage.StoreFileAsync(filePath, await File.ReadAllBytesAsync(filePath).ConfigureAwait(false), cancellationToken).ConfigureAwait(false);
- }
-
- // store private keys
- foreach (string filePath in Directory.EnumerateFiles(Path.Combine(Directory.GetCurrentDirectory(), "pki", "own", "private"), "*.pfx"))
- {
- await _storage.StoreFileAsync(filePath, await File.ReadAllBytesAsync(filePath).ConfigureAwait(false), cancellationToken).ConfigureAwait(false);
- }
-
- // store trusted certs
- foreach (string filePath in Directory.EnumerateFiles(Path.Combine(Directory.GetCurrentDirectory(), "pki", "trusted", "certs"), "*.der"))
- {
- await _storage.StoreFileAsync(filePath, await File.ReadAllBytesAsync(filePath).ConfigureAwait(false), cancellationToken).ConfigureAwait(false);
- }
+ Settings.Instance.UAClientCertThumbprint = UAApplicationInstance.ApplicationConfiguration.SecurityConfiguration.ApplicationCertificate.Certificate.Thumbprint;
+ Settings.Instance.UAClientCertExpiry = UAApplicationInstance.ApplicationConfiguration.SecurityConfiguration.ApplicationCertificate.Certificate.NotAfter;
}
_logger.LogInformation($"Application Certificate subject name is: {UAApplicationInstance.ApplicationConfiguration.SecurityConfiguration.ApplicationCertificate.SubjectName}");
@@ -147,9 +77,14 @@ public async Task CreateAsync(CancellationToken cancellationToken = default)
private async Task CreateIssuerCert()
{
- string certFilePath = await _storage.FindFileAsync(Path.Combine(Directory.GetCurrentDirectory(), "pki", "issuer", "private"), Settings.Instance.PublisherName).ConfigureAwait(false);
- byte[] certFile = await _storage.LoadFileAsync(certFilePath).ConfigureAwait(false);
- if (certFile == null)
+ string pathToIssuerStore = Path.Combine(Directory.GetCurrentDirectory(), "pki", "issuer", "private");
+ if (!Directory.Exists(pathToIssuerStore))
+ {
+ Directory.CreateDirectory(pathToIssuerStore);
+ }
+
+ string[] issuerCerts = Directory.GetFiles(pathToIssuerStore);
+ if ((issuerCerts == null) || (issuerCerts.Count() == 0))
{
_logger.LogError("Could not load issuer cert file, creating a new one. This means all conected OPC UA servers need to be issued a new cert!");
@@ -166,8 +101,11 @@ private async Task CreateIssuerCert()
}
else
{
- IssuerCert = new X509Certificate2(certFile);
+ IssuerCert = new X509Certificate2(File.ReadAllBytes(issuerCerts[0]));
}
+
+ Settings.Instance.UAIssuerCertThumbprint = IssuerCert.Thumbprint;
+ Settings.Instance.UAIssuerCertExpiry = IssuerCert.NotAfter;
}
private void OpcStackLoggingHandler(object sender, TraceEventArgs e)
diff --git a/UAClient.cs b/UAClient.cs
index 1bf62db..9ff3817 100644
--- a/UAClient.cs
+++ b/UAClient.cs
@@ -29,7 +29,6 @@ public class UAClient : IUAClient
private readonly IUAApplication _app;
private readonly ILogger _logger;
private readonly ILoggerFactory _loggerFactory;
- private readonly IFileStorage _storage;
private IMessageSource _trigger;
@@ -53,14 +52,12 @@ public class UAClient : IUAClient
public UAClient(
IUAApplication app,
ILoggerFactory loggerFactory,
- IMessageSource trigger,
- IFileStorage storage)
+ IMessageSource trigger)
{
_logger = loggerFactory.CreateLogger("UAClient");
_loggerFactory = loggerFactory;
_app = app;
_trigger = trigger;
- _storage = storage;
}
public void Dispose()
@@ -282,7 +279,7 @@ public void UnpublishAllNodes(bool updatePersistencyFile = true)
// update our persistency
if (updatePersistencyFile)
{
- PersistPublishedNodesAsync().GetAwaiter().GetResult();
+ PersistPublishedNodes();
}
}
@@ -629,7 +626,7 @@ public async Task PublishNodeAsync(NodePublishingModel nodeToPublish, Ca
Diagnostics.Singleton.Info.NumberOfOpcMonitoredItemsMonitored++;
// update our persistency
- PersistPublishedNodesAsync().GetAwaiter().GetResult();
+ PersistPublishedNodes();
return "Successfully published node " + nodeToPublish.ExpandedNodeId.ToString();
}
@@ -716,7 +713,7 @@ public void UnpublishNode(NodePublishingModel nodeToUnpublish)
}
// update our persistency
- PersistPublishedNodesAsync().GetAwaiter().GetResult();
+ PersistPublishedNodes();
return;
}
@@ -848,7 +845,7 @@ public IEnumerable GetPublishedNodes()
return publisherConfigurationFileEntries;
}
- private async Task PersistPublishedNodesAsync(CancellationToken cancellationToken = default)
+ private void PersistPublishedNodes(CancellationToken cancellationToken = default)
{
try
{
@@ -856,10 +853,7 @@ private async Task PersistPublishedNodesAsync(CancellationToken cancellationToke
IEnumerable publisherNodeConfiguration = GetPublishedNodes();
// update the persistency file
- if (await _storage.StoreFileAsync(Path.Combine(Directory.GetCurrentDirectory(), "settings", "persistency.json"), Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(publisherNodeConfiguration, Formatting.Indented)), cancellationToken).ConfigureAwait(false) == null)
- {
- _logger.LogError("Could not store persistency file. Published nodes won't be persisted!");
- }
+ File.WriteAllBytes(Path.Combine(Directory.GetCurrentDirectory(), "settings", "persistency.json"), Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(publisherNodeConfiguration, Formatting.Indented)));
}
catch (Exception ex)
{
@@ -1071,6 +1065,17 @@ public async Task GDSServerPush(string endpointURL, string adminUsername, string
{
ServerPushConfigurationClient serverPushClient = new(_app.UAApplicationInstance.ApplicationConfiguration);
+ // use environment variables if nothing else was specified
+ if (string.IsNullOrEmpty(adminUsername))
+ {
+ adminUsername = Environment.GetEnvironmentVariable("OPCUA_USERNAME");
+ }
+
+ if (string.IsNullOrEmpty(adminPassword))
+ {
+ adminPassword = Environment.GetEnvironmentVariable("OPCUA_PASSWORD");
+ }
+
serverPushClient.AdminCredentials = new UserIdentity(adminUsername, adminPassword);
await serverPushClient.Connect(endpointURL).ConfigureAwait(false);
diff --git a/Views/CertManager/Index.cshtml b/Views/CertManager/Index.cshtml
index a7eea4c..85e4e72 100644
--- a/Views/CertManager/Index.cshtml
+++ b/Views/CertManager/Index.cshtml
@@ -1,6 +1,6 @@
@using Opc.Ua.Cloud.Publisher.Models
-@model IEnumerable
+@model CertManagerModel
@{
ViewData["Title"] = "Certificate Management";
@@ -13,7 +13,7 @@
Certificates currently in the trust list:
- @Html.ListBox("trust list certificates", Model, new { disabled = true })
+ @Html.ListBox("trust list certificates", Model.Certs, new { disabled = true })
@@ -38,5 +38,21 @@
}
+
+
+
+
diff --git a/Views/Config/Index.cshtml b/Views/Config/Index.cshtml
index 52031f0..b93c545 100644
--- a/Views/Config/Index.cshtml
+++ b/Views/Config/Index.cshtml
@@ -8,39 +8,43 @@
@ViewData["Title"]
-
- Click here to use the code below for Microsoft OneLake authentication:
- @Html.TextBox("AuthCode", @Model.AuthenticationCode, new { style = "width:100%;background-color:grey;color:white;", @readonly = "true" })
-
- Note: UA Cloud Publisher will show up as Microsoft Azure Cross-platform Command Line Interface. When prompted "Are you trying to sign in to Microsoft Azure CLI?", select continue.
-
-
-
-