up
This commit is contained in:
@@ -198,16 +198,16 @@ internal sealed class RuntimePostureEvaluator : IRuntimePostureEvaluator
|
||||
return posture;
|
||||
}
|
||||
|
||||
private async Task EnrichWithManifestAsync(string? manifestDigest, List<RuntimeEvidence> evidence, CancellationToken cancellationToken)
|
||||
private async Task EnrichWithManifestAsync(string? manifestPointer, List<RuntimeEvidence> evidence, CancellationToken cancellationToken)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(manifestDigest))
|
||||
if (string.IsNullOrWhiteSpace(manifestPointer))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var manifest = await surfaceFsClient.TryGetManifestAsync(manifestDigest, cancellationToken).ConfigureAwait(false);
|
||||
var manifest = await surfaceFsClient.TryGetManifestAsync(manifestPointer, cancellationToken).ConfigureAwait(false);
|
||||
if (manifest is null)
|
||||
{
|
||||
ManifestFailuresCounter.Add(1, new KeyValuePair<string, object?>("reason", "not_found"));
|
||||
@@ -216,7 +216,7 @@ internal sealed class RuntimePostureEvaluator : IRuntimePostureEvaluator
|
||||
Signal = "runtime.surface.manifest",
|
||||
Value = "not_found"
|
||||
});
|
||||
logger.LogDebug("Surface manifest {ManifestDigest} not found in local cache.", manifestDigest);
|
||||
logger.LogDebug("Surface manifest {ManifestPointer} not found in local cache.", manifestPointer);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -228,8 +228,10 @@ internal sealed class RuntimePostureEvaluator : IRuntimePostureEvaluator
|
||||
|
||||
evidence.Add(new RuntimeEvidence
|
||||
{
|
||||
Signal = "runtime.surface.manifestDigest",
|
||||
Value = manifestDigest
|
||||
Signal = manifestPointer.StartsWith("cas://", StringComparison.OrdinalIgnoreCase)
|
||||
? "runtime.surface.manifestUri"
|
||||
: "runtime.surface.manifestDigest",
|
||||
Value = manifestPointer
|
||||
});
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(manifest.ImageDigest))
|
||||
@@ -276,7 +278,7 @@ internal sealed class RuntimePostureEvaluator : IRuntimePostureEvaluator
|
||||
Signal = "runtime.surface.manifest",
|
||||
Value = "fetch_error"
|
||||
});
|
||||
logger.LogWarning(ex, "Failed to fetch Surface manifest {ManifestDigest}.", manifestDigest);
|
||||
logger.LogWarning(ex, "Failed to fetch Surface manifest {ManifestPointer}.", manifestPointer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,10 @@ namespace StellaOps.Zastava.Observer.Surface;
|
||||
|
||||
internal interface IRuntimeSurfaceFsClient
|
||||
{
|
||||
Task<SurfaceManifestDocument?> TryGetManifestAsync(string manifestDigest, CancellationToken cancellationToken = default);
|
||||
/// <summary>
|
||||
/// Resolve a Surface manifest from either a digest (sha256:...) or a CAS URI (cas://.../*.json).
|
||||
/// </summary>
|
||||
Task<SurfaceManifestDocument?> TryGetManifestAsync(string manifestPointer, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
internal sealed class RuntimeSurfaceFsClient : IRuntimeSurfaceFsClient
|
||||
@@ -19,14 +22,25 @@ internal sealed class RuntimeSurfaceFsClient : IRuntimeSurfaceFsClient
|
||||
_environment = environment ?? throw new ArgumentNullException(nameof(environment));
|
||||
}
|
||||
|
||||
public async Task<SurfaceManifestDocument?> TryGetManifestAsync(string manifestDigest, CancellationToken cancellationToken = default)
|
||||
public async Task<SurfaceManifestDocument?> TryGetManifestAsync(string manifestPointer, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(manifestDigest))
|
||||
if (string.IsNullOrWhiteSpace(manifestPointer))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var manifest = await _manifestReader.TryGetByDigestAsync(manifestDigest.Trim(), cancellationToken).ConfigureAwait(false);
|
||||
manifestPointer = manifestPointer.Trim();
|
||||
|
||||
SurfaceManifestDocument? manifest = null;
|
||||
if (manifestPointer.StartsWith("cas://", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
manifest = await _manifestReader.TryGetByUriAsync(manifestPointer, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
manifest = await _manifestReader.TryGetByDigestAsync(manifestPointer, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (manifest is null)
|
||||
{
|
||||
return null;
|
||||
|
||||
@@ -95,6 +95,85 @@ public sealed class RuntimeSurfaceFsClientTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TryGetManifestAsync_ResolvesCasUri_AndRespectsTenant()
|
||||
{
|
||||
var envVars = new Dictionary<string, string?>
|
||||
{
|
||||
["ZASTAVA_SURFACE_FS_ENDPOINT"] = "https://surface.example",
|
||||
["ZASTAVA_SURFACE_TENANT"] = "team-a"
|
||||
};
|
||||
|
||||
var originals = CaptureEnvironment(AllRelevantEnvVars);
|
||||
try
|
||||
{
|
||||
ClearEnvironment(AllRelevantEnvVars);
|
||||
foreach (var pair in envVars)
|
||||
{
|
||||
Environment.SetEnvironmentVariable(pair.Key, pair.Value);
|
||||
}
|
||||
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(new Dictionary<string, string?>
|
||||
{
|
||||
[$"{ZastavaObserverOptions.SectionName}:runtimes:0:engine"] = "Containerd",
|
||||
[$"{ZastavaObserverOptions.SectionName}:backend:baseAddress"] = "https://scanner.internal"
|
||||
})
|
||||
.Build();
|
||||
|
||||
var services = new ServiceCollection();
|
||||
services.AddSingleton<IConfiguration>(configuration);
|
||||
services.AddLogging();
|
||||
services.AddZastavaObserver(configuration);
|
||||
|
||||
using var provider = services.BuildServiceProvider();
|
||||
var manifestWriter = provider.GetRequiredService<ISurfaceManifestWriter>();
|
||||
var client = provider.GetRequiredService<IRuntimeSurfaceFsClient>();
|
||||
|
||||
var tenantMismatchDoc = new SurfaceManifestDocument
|
||||
{
|
||||
Tenant = "team-b",
|
||||
ImageDigest = "sha256:tenant-mismatch",
|
||||
GeneratedAt = DateTimeOffset.UtcNow,
|
||||
Artifacts = Array.Empty<SurfaceManifestArtifact>()
|
||||
};
|
||||
|
||||
var matchedDoc = new SurfaceManifestDocument
|
||||
{
|
||||
Tenant = "team-a",
|
||||
ImageDigest = "sha256:good",
|
||||
GeneratedAt = DateTimeOffset.UtcNow,
|
||||
Artifacts = new[]
|
||||
{
|
||||
new SurfaceManifestArtifact
|
||||
{
|
||||
Kind = "entry-trace",
|
||||
Uri = "cas://surface-cache/team-a/entry-trace.ndjson",
|
||||
Digest = "sha256:trace",
|
||||
MediaType = "application/x-ndjson"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var mismatchPublished = await manifestWriter.PublishAsync(tenantMismatchDoc, default);
|
||||
var matchedPublished = await manifestWriter.PublishAsync(matchedDoc, default);
|
||||
|
||||
// Tenant mismatch should be filtered out
|
||||
var mismatch = await client.TryGetManifestAsync(mismatchPublished.ManifestUri, default);
|
||||
Assert.Null(mismatch);
|
||||
|
||||
// cas:// URI should resolve and respect tenant
|
||||
var resolved = await client.TryGetManifestAsync(matchedPublished.ManifestUri, default);
|
||||
Assert.NotNull(resolved);
|
||||
Assert.Equal(matchedDoc.ImageDigest, resolved!.ImageDigest);
|
||||
Assert.Equal("team-a", resolved.Tenant);
|
||||
}
|
||||
finally
|
||||
{
|
||||
RestoreEnvironment(originals);
|
||||
}
|
||||
}
|
||||
|
||||
private static IReadOnlyDictionary<string, string?> CaptureEnvironment(IEnumerable<string> names)
|
||||
{
|
||||
var snapshot = new Dictionary<string, string?>();
|
||||
|
||||
Reference in New Issue
Block a user