94 lines
6.1 KiB
C#
94 lines
6.1 KiB
C#
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
using Microsoft.Extensions.Configuration;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Opc.Ua;
|
|
using Opc.Ua.Client;
|
|
using Opc.Ua.Configuration;
|
|
using ZB.MOM.WW.OtOpcUa.Core.Authorization;
|
|
using ZB.MOM.WW.OtOpcUa.Host.Configuration;
|
|
using ZB.MOM.WW.OtOpcUa.Runtime.DeploymentCache;
|
|
|
|
using var deadline = new CancellationTokenSource(TimeSpan.FromSeconds(45));
|
|
var ct = deadline.Token;
|
|
var mode = args[0];
|
|
var root = Path.GetFullPath(args[1]);
|
|
if (mode == "seed")
|
|
{
|
|
var config = new ConfigurationBuilder().AddInMemoryCollection(new Dictionary<string, string?>
|
|
{ ["LocalDb:Path"] = Path.Combine(root, "localdb.db") }).Build();
|
|
await using var provider = new ServiceCollection().AddLogging().AddOtOpcUaLocalDb(config).BuildServiceProvider();
|
|
var presented = Environment.GetEnvironmentVariable("SMOKE_API_KEY") ?? throw new InvalidOperationException("Missing task credential.");
|
|
if (!ApiKeySecret.TryParse(presented, out var id, out var secret)) throw new InvalidOperationException("Invalid task credential format.");
|
|
var pepper = Environment.GetEnvironmentVariable("Security__ApiKeys__Pepper") ?? throw new InvalidOperationException("Missing task pepper.");
|
|
var artifact = JsonSerializer.SerializeToUtf8Bytes(new
|
|
{
|
|
Clusters = new[] { new { ClusterId = "smoke", Name = "Smoke" } },
|
|
RawFolders = new[] { new { RawFolderId = "smoke", Name = "Smoke", ClusterId = "smoke" } },
|
|
ApiKeys = new[] { new { ApiKeyId = id, Name = "publish-smoke", Principal = "publish-smoke",
|
|
Enabled = true, SecretHash = ApiKeySecret.ComputeHash(secret, pepper), CreatedUtc = DateTime.UtcNow } },
|
|
NodeAcls = new[] { new { NodeAclId = "smoke-read", ClusterId = "smoke", Principal = "publish-smoke",
|
|
ScopeKind = 0, ScopeId = (string?)null, PermissionFlags = 7 } },
|
|
});
|
|
var revision = Convert.ToHexStringLower(SHA256.HashData(artifact));
|
|
await provider.GetRequiredService<IDeploymentArtifactCache>().StoreAsync("smoke", ZB.MOM.WW.OtOpcUa.Commons.Types.DeploymentId.NewId().ToString(), revision, artifact, ct);
|
|
Console.WriteLine(JsonSerializer.Serialize(new { mode, result = "seeded-through-public-cache", revision }));
|
|
return;
|
|
}
|
|
|
|
var telemetry = DefaultTelemetry.Create(_ => { });
|
|
var clientName = mode == "prepare-cli" ? "OtOpcUaClient" : "PublishSmokeClient";
|
|
var security = new SecurityConfiguration
|
|
{
|
|
ApplicationCertificate = new CertificateIdentifier { StoreType = CertificateStoreType.Directory,
|
|
StorePath = Path.Combine(root, "own"), SubjectName = "CN=" + clientName },
|
|
TrustedIssuerCertificates = new CertificateTrustList { StoreType = CertificateStoreType.Directory, StorePath = Path.Combine(root, "issuer") },
|
|
TrustedPeerCertificates = new CertificateTrustList { StoreType = CertificateStoreType.Directory, StorePath = Path.Combine(root, "trusted") },
|
|
RejectedCertificateStore = new CertificateTrustList { StoreType = CertificateStoreType.Directory, StorePath = Path.Combine(root, "rejected") },
|
|
AutoAcceptUntrustedCertificates = false,
|
|
};
|
|
var configuration = new ApplicationConfiguration(telemetry)
|
|
{
|
|
ApplicationName = clientName, ApplicationUri = "urn:localhost:" + clientName,
|
|
ApplicationType = ApplicationType.Client, SecurityConfiguration = security,
|
|
ClientConfiguration = new ClientConfiguration { DefaultSessionTimeout = 60_000 },
|
|
};
|
|
await using var application = new ApplicationInstance(telemetry)
|
|
{ ApplicationName = configuration.ApplicationName, ApplicationType = ApplicationType.Client, ApplicationConfiguration = configuration };
|
|
await configuration.ValidateAsync(ApplicationType.Client, ct);
|
|
await application.CheckApplicationInstanceCertificatesAsync(true, lifeTimeInMonths: 12, ct);
|
|
if (mode is "prepare" or "prepare-cli")
|
|
{
|
|
Console.WriteLine(JsonSerializer.Serialize(new { mode, result = "prepared", autoAccept = security.AutoAcceptUntrustedCertificates }));
|
|
return;
|
|
}
|
|
var endpoint = args[2];
|
|
var expected = args[3];
|
|
var credential = Environment.GetEnvironmentVariable("SMOKE_API_KEY") ?? throw new InvalidOperationException("Missing task credential.");
|
|
try
|
|
{
|
|
var selected = await CoreClientUtils.SelectEndpointAsync(configuration, endpoint, true, telemetry, ct)
|
|
?? throw new InvalidOperationException("No secure endpoint advertised.");
|
|
if (selected.SecurityMode != MessageSecurityMode.SignAndEncrypt || selected.SecurityPolicyUri != SecurityPolicies.Basic256Sha256)
|
|
throw new InvalidOperationException("Unexpected negotiated security policy.");
|
|
using var session = await new DefaultSessionFactory(telemetry).CreateAsync(configuration,
|
|
new ConfiguredEndpoint(null, selected, EndpointConfiguration.Create(configuration)), false, true,
|
|
"PublishSmoke", 60_000, new UserIdentity("apikey", Encoding.UTF8.GetBytes(credential)), default, ct);
|
|
if (expected != "allow") throw new InvalidOperationException("A denied test credential or peer was admitted.");
|
|
var response = await session.ReadAsync(null, 0, TimestampsToReturn.Both,
|
|
[new ReadValueId { NodeId = VariableIds.Server_ServerStatus_State, AttributeId = Attributes.Value }], ct);
|
|
if (response.Results.Count != 1 || StatusCode.IsBad(response.Results[0].StatusCode)) throw new InvalidOperationException("Server status read failed.");
|
|
Console.WriteLine(JsonSerializer.Serialize(new { mode, expected, result = "allowed", policy = selected.SecurityPolicyUri,
|
|
securityMode = selected.SecurityMode.ToString(), status = response.Results[0].StatusCode.ToString(), autoAccept = false }));
|
|
await session.CloseAsync(ct);
|
|
}
|
|
catch (ServiceResultException error) when (expected != "allow")
|
|
{
|
|
var accepted = expected == "key-deny"
|
|
? new[] { StatusCodes.BadUserAccessDenied, StatusCodes.BadIdentityTokenRejected, StatusCodes.BadIdentityTokenInvalid }
|
|
: new[] { StatusCodes.BadCertificateUntrusted, StatusCodes.BadSecurityChecksFailed, StatusCodes.BadSecureChannelClosed };
|
|
if (!accepted.Contains(error.StatusCode)) throw;
|
|
Console.WriteLine(JsonSerializer.Serialize(new { mode, expected, result = "denied", status = error.StatusCode.ToString(), autoAccept = false }));
|
|
}
|