mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
* fix(ffi): eliminate aliasing UB via to_shared_ref migration Add to_shared_ref() helper that creates &T (shared reference) from raw pointers instead of &mut T. This eliminates undefined behavior caused by violating Rust's aliasing invariant when C# SafeHandle permits concurrent FFI calls on the same handle. With &mut T, the compiler may assume exclusive (noalias) access and reorder or elide reads/writes — a miscompilation risk when another thread holds a reference to the same object. Switching to &T removes that assumption; actual mutation is mediated by the interior RwLock inside Handle<T>, which is the sole synchronization mechanism. Migrated sites: - rvm.rs: 20 non-drop call sites - engine.rs: 30 non-drop call sites + with_unwind_guard for timer fns - compiled_policy.rs: 2 call sites - Fix null-data UB in regorus_program_deserialize_binary Drop paths retain to_ref() where exclusive access is guaranteed by the caller contract (preventing use-after-free). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(ffi): add Azure Policy JSON compilation FFI and C# bindings - AliasRegistry builder pattern: RegorusAliasRegistryBuilder (mutable, single-threaded) + RegorusAliasRegistry (immutable, Arc-wrapped) - Azure Policy JSON compilation: regorus_compile_azure_policy_rule and regorus_compile_azure_policy_definition with alias registry support - regorus_rvm_set_context for host-supplied ambient data - C# AliasRegistryBuilder and AliasRegistry classes with convenience factories (FromJson, FromManifest, Empty) - C# AzurePolicyCompiler static class for policy rule/definition compilation - Compile functions take *const RegorusAliasRegistry (read-only via to_shared_ref for concurrent compilation safety) - Fix pre-existing clippy warnings across multiple crates Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
182 lines
6.7 KiB
C#
182 lines
6.7 KiB
C#
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT License.
|
|
|
|
using System;
|
|
using System.IO;
|
|
using System.Text.Json.Nodes;
|
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
|
using Regorus;
|
|
|
|
namespace Regorus.Tests;
|
|
|
|
/// <summary>
|
|
/// Tests for Azure Policy alias normalization and denormalization
|
|
/// using the AliasRegistry exposed through the C# bindings.
|
|
/// </summary>
|
|
[TestClass]
|
|
public class AzurePolicyTests
|
|
{
|
|
/// <summary>
|
|
/// Sample alias definitions for Microsoft.Storage provider.
|
|
/// These mirror a subset of the test aliases used by the Rust test suite.
|
|
/// </summary>
|
|
private const string StorageAliasesJson = @"[{
|
|
""namespace"": ""Microsoft.Storage"",
|
|
""resourceTypes"": [{
|
|
""resourceType"": ""storageAccounts"",
|
|
""capabilities"": ""SupportsTags, SupportsLocation"",
|
|
""aliases"": [
|
|
{
|
|
""name"": ""Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly"",
|
|
""defaultPath"": ""properties.supportsHttpsTrafficOnly"",
|
|
""paths"": []
|
|
},
|
|
{
|
|
""name"": ""Microsoft.Storage/storageAccounts/minimumTlsVersion"",
|
|
""defaultPath"": ""properties.minimumTlsVersion"",
|
|
""paths"": []
|
|
},
|
|
{
|
|
""name"": ""Microsoft.Storage/storageAccounts/allowBlobPublicAccess"",
|
|
""defaultPath"": ""properties.allowBlobPublicAccess"",
|
|
""paths"": []
|
|
}
|
|
]
|
|
}]
|
|
}]";
|
|
|
|
/// <summary>
|
|
/// ARM resource in its original shape (with properties wrapper).
|
|
/// </summary>
|
|
private const string StorageResourceJson = @"{
|
|
""type"": ""Microsoft.Storage/storageAccounts"",
|
|
""name"": ""mystorage"",
|
|
""location"": ""eastus"",
|
|
""properties"": {
|
|
""supportsHttpsTrafficOnly"": true,
|
|
""minimumTlsVersion"": ""TLS1_2"",
|
|
""allowBlobPublicAccess"": false
|
|
}
|
|
}";
|
|
|
|
[TestMethod]
|
|
public void AliasRegistry_NormalizeAndWrap_produces_input_envelope()
|
|
{
|
|
using var registry = AliasRegistry.FromJson(StorageAliasesJson);
|
|
|
|
var result = registry.NormalizeAndWrap(
|
|
StorageResourceJson,
|
|
apiVersion: null,
|
|
contextJson: "{}",
|
|
parametersJson: "{}");
|
|
|
|
Assert.IsNotNull(result, "NormalizeAndWrap should return a non-null string");
|
|
|
|
// The result should be valid JSON with resource, parameters, and context keys.
|
|
var doc = JsonNode.Parse(result);
|
|
Assert.IsNotNull(doc);
|
|
Assert.IsNotNull(doc["resource"], "envelope must contain 'resource'");
|
|
Assert.IsNotNull(doc["parameters"], "envelope must contain 'parameters'");
|
|
Assert.IsNotNull(doc["context"], "envelope must contain 'context'");
|
|
}
|
|
|
|
[TestMethod]
|
|
public void AliasRegistry_NormalizeAndWrap_flattens_properties()
|
|
{
|
|
using var registry = AliasRegistry.FromJson(StorageAliasesJson);
|
|
|
|
var result = registry.NormalizeAndWrap(StorageResourceJson);
|
|
Assert.IsNotNull(result);
|
|
|
|
var doc = JsonNode.Parse(result);
|
|
var resource = doc!["resource"];
|
|
Assert.IsNotNull(resource);
|
|
|
|
// After normalization, alias-mapped properties should be
|
|
// available at the top level of the resource (lowercased).
|
|
// The normalizer flattens "properties.supportsHttpsTrafficOnly"
|
|
// to "supportshttpstrafficonly" at the resource root.
|
|
var httpsOnly = resource["supportshttpstrafficonly"];
|
|
Assert.IsNotNull(httpsOnly,
|
|
"normalized resource should have 'supportshttpstrafficonly' at top level");
|
|
Assert.AreEqual(true, httpsOnly!.GetValue<bool>());
|
|
}
|
|
|
|
[TestMethod]
|
|
public void AliasRegistry_NormalizeAndWrap_preserves_type_field()
|
|
{
|
|
using var registry = AliasRegistry.FromJson(StorageAliasesJson);
|
|
|
|
var result = registry.NormalizeAndWrap(StorageResourceJson);
|
|
var doc = JsonNode.Parse(result!);
|
|
var resource = doc!["resource"];
|
|
|
|
// The "type" field should be preserved (lowercased key).
|
|
var typeField = resource!["type"];
|
|
Assert.IsNotNull(typeField, "normalized resource should have 'type'");
|
|
Assert.AreEqual(
|
|
"microsoft.storage/storageaccounts",
|
|
typeField!.GetValue<string>().ToLowerInvariant());
|
|
}
|
|
|
|
[TestMethod]
|
|
public void AliasRegistry_NormalizeAndWrap_includes_parameters()
|
|
{
|
|
using var registry = AliasRegistry.FromJson(StorageAliasesJson);
|
|
|
|
var parametersJson = @"{ ""effect"": ""Deny"" }";
|
|
var result = registry.NormalizeAndWrap(
|
|
StorageResourceJson,
|
|
parametersJson: parametersJson);
|
|
Assert.IsNotNull(result);
|
|
|
|
var doc = JsonNode.Parse(result!);
|
|
var parameters = doc!["parameters"];
|
|
Assert.IsNotNull(parameters);
|
|
Assert.AreEqual("Deny", parameters!["effect"]!.GetValue<string>());
|
|
}
|
|
|
|
[TestMethod]
|
|
public void AliasRegistry_Denormalize_roundtrips_correctly()
|
|
{
|
|
using var registry = AliasRegistry.FromJson(StorageAliasesJson);
|
|
|
|
// Normalize the ARM resource.
|
|
var envelope = registry.NormalizeAndWrap(StorageResourceJson);
|
|
Assert.IsNotNull(envelope);
|
|
|
|
// Extract just the normalized resource from the envelope.
|
|
var doc = JsonNode.Parse(envelope!);
|
|
var normalizedResource = doc!["resource"]!.ToJsonString();
|
|
|
|
// Denormalize back to ARM shape.
|
|
var denormalized = registry.Denormalize(normalizedResource);
|
|
Assert.IsNotNull(denormalized, "Denormalize should return a non-null string");
|
|
|
|
// The denormalized result should have a "properties" wrapper again.
|
|
var denormDoc = JsonNode.Parse(denormalized!);
|
|
Assert.IsNotNull(denormDoc);
|
|
var props = denormDoc!["properties"];
|
|
Assert.IsNotNull(props, "denormalized resource should have 'properties'");
|
|
}
|
|
|
|
[TestMethod]
|
|
public void AliasRegistry_loads_test_aliases_file()
|
|
{
|
|
// Load the same aliases file used by the Rust test suite.
|
|
var aliasesPath = Path.Combine(AppContext.BaseDirectory, "tests", "azure_policy", "aliases", "test_aliases.json");
|
|
if (!File.Exists(aliasesPath))
|
|
{
|
|
Assert.Inconclusive($"Test aliases file not found at {aliasesPath}");
|
|
return;
|
|
}
|
|
|
|
var aliasesJson = File.ReadAllText(aliasesPath);
|
|
using var registry = AliasRegistry.FromJson(aliasesJson);
|
|
|
|
// The test_aliases.json file contains multiple providers.
|
|
Assert.IsTrue(registry.Length > 0,
|
|
"registry should have loaded at least one resource type");
|
|
}
|
|
}
|