notify doctors work, audit work, new product advisory sprints
This commit is contained in:
@@ -4,6 +4,8 @@ namespace StellaOps.Scanner.Surface.Secrets;
|
||||
|
||||
public interface ISurfaceSecretProvider
|
||||
{
|
||||
SurfaceSecretHandle Get(SurfaceSecretRequest request);
|
||||
|
||||
ValueTask<SurfaceSecretHandle> GetAsync(
|
||||
SurfaceSecretRequest request,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
@@ -29,6 +29,50 @@ internal sealed class AuditingSurfaceSecretProvider : ISurfaceSecretProvider
|
||||
_componentName = componentName ?? throw new ArgumentNullException(nameof(componentName));
|
||||
}
|
||||
|
||||
public SurfaceSecretHandle Get(SurfaceSecretRequest request)
|
||||
{
|
||||
var startTime = _timeProvider.GetUtcNow();
|
||||
|
||||
try
|
||||
{
|
||||
var handle = _inner.Get(request);
|
||||
|
||||
var elapsed = _timeProvider.GetUtcNow() - startTime;
|
||||
LogAuditEvent(
|
||||
request,
|
||||
handle.Metadata,
|
||||
success: true,
|
||||
elapsed,
|
||||
error: null);
|
||||
|
||||
return handle;
|
||||
}
|
||||
catch (SurfaceSecretNotFoundException)
|
||||
{
|
||||
var elapsed = _timeProvider.GetUtcNow() - startTime;
|
||||
LogAuditEvent(
|
||||
request,
|
||||
metadata: null,
|
||||
success: false,
|
||||
elapsed,
|
||||
error: "NotFound");
|
||||
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var elapsed = _timeProvider.GetUtcNow() - startTime;
|
||||
LogAuditEvent(
|
||||
request,
|
||||
metadata: null,
|
||||
success: false,
|
||||
elapsed,
|
||||
error: ex.GetType().Name);
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask<SurfaceSecretHandle> GetAsync(
|
||||
SurfaceSecretRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
|
||||
@@ -35,6 +35,25 @@ internal sealed class CachingSurfaceSecretProvider : ISurfaceSecretProvider
|
||||
|
||||
public TimeSpan CacheTtl => _ttl;
|
||||
|
||||
public SurfaceSecretHandle Get(SurfaceSecretRequest request)
|
||||
{
|
||||
var key = BuildCacheKey(request);
|
||||
var now = _timeProvider.GetUtcNow();
|
||||
|
||||
if (_cache.TryGetValue(key, out var entry) && entry.ExpiresAt > now)
|
||||
{
|
||||
_logger.LogDebug("Surface secret cache hit for {Key}.", key);
|
||||
return entry.Handle;
|
||||
}
|
||||
|
||||
var handle = _inner.Get(request);
|
||||
var newEntry = new CacheEntry(handle, now.Add(_ttl));
|
||||
_cache[key] = newEntry;
|
||||
|
||||
_logger.LogDebug("Surface secret cached for {Key}, expires at {ExpiresAt}.", key, newEntry.ExpiresAt);
|
||||
return handle;
|
||||
}
|
||||
|
||||
public async ValueTask<SurfaceSecretHandle> GetAsync(
|
||||
SurfaceSecretRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
|
||||
@@ -18,6 +18,23 @@ internal sealed class CompositeSurfaceSecretProvider : ISurfaceSecretProvider
|
||||
}
|
||||
}
|
||||
|
||||
public SurfaceSecretHandle Get(SurfaceSecretRequest request)
|
||||
{
|
||||
foreach (var provider in _providers)
|
||||
{
|
||||
try
|
||||
{
|
||||
return provider.Get(request);
|
||||
}
|
||||
catch (SurfaceSecretNotFoundException)
|
||||
{
|
||||
// try next provider
|
||||
}
|
||||
}
|
||||
|
||||
throw new SurfaceSecretNotFoundException(request);
|
||||
}
|
||||
|
||||
public async ValueTask<SurfaceSecretHandle> GetAsync(
|
||||
SurfaceSecretRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
|
||||
@@ -20,6 +20,35 @@ internal sealed class FileSurfaceSecretProvider : ISurfaceSecretProvider
|
||||
_root = root;
|
||||
}
|
||||
|
||||
public SurfaceSecretHandle Get(SurfaceSecretRequest request)
|
||||
{
|
||||
if (request is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(request));
|
||||
}
|
||||
|
||||
var path = ResolvePath(request);
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
throw new SurfaceSecretNotFoundException(request);
|
||||
}
|
||||
|
||||
var json = File.ReadAllText(path);
|
||||
var descriptor = JsonSerializer.Deserialize<FileSecretDescriptor>(json);
|
||||
if (descriptor is null)
|
||||
{
|
||||
throw new SurfaceSecretNotFoundException(request);
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(descriptor.Payload))
|
||||
{
|
||||
return SurfaceSecretHandle.Empty;
|
||||
}
|
||||
|
||||
var bytes = Convert.FromBase64String(descriptor.Payload);
|
||||
return SurfaceSecretHandle.FromBytes(bytes, descriptor.Metadata);
|
||||
}
|
||||
|
||||
public async ValueTask<SurfaceSecretHandle> GetAsync(
|
||||
SurfaceSecretRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
|
||||
@@ -21,6 +21,21 @@ public sealed class InMemorySurfaceSecretProvider : ISurfaceSecretProvider
|
||||
_secrets[request.CacheKey] = handle;
|
||||
}
|
||||
|
||||
public SurfaceSecretHandle Get(SurfaceSecretRequest request)
|
||||
{
|
||||
if (request is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(request));
|
||||
}
|
||||
|
||||
if (_secrets.TryGetValue(request.CacheKey, out var handle))
|
||||
{
|
||||
return handle;
|
||||
}
|
||||
|
||||
throw new SurfaceSecretNotFoundException(request);
|
||||
}
|
||||
|
||||
public ValueTask<SurfaceSecretHandle> GetAsync(SurfaceSecretRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (request is null)
|
||||
|
||||
@@ -14,6 +14,30 @@ internal sealed class InlineSurfaceSecretProvider : ISurfaceSecretProvider
|
||||
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
|
||||
}
|
||||
|
||||
public SurfaceSecretHandle Get(SurfaceSecretRequest request)
|
||||
{
|
||||
if (!_configuration.AllowInline)
|
||||
{
|
||||
throw new SurfaceSecretNotFoundException(request);
|
||||
}
|
||||
|
||||
var envKey = BuildEnvironmentKey(request);
|
||||
var value = Environment.GetEnvironmentVariable(envKey);
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
throw new SurfaceSecretNotFoundException(request);
|
||||
}
|
||||
|
||||
var bytes = Convert.FromBase64String(value);
|
||||
var metadata = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["source"] = "inline-env",
|
||||
["key"] = envKey
|
||||
};
|
||||
|
||||
return SurfaceSecretHandle.FromBytes(bytes, metadata);
|
||||
}
|
||||
|
||||
public ValueTask<SurfaceSecretHandle> GetAsync(
|
||||
SurfaceSecretRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
|
||||
@@ -23,6 +23,30 @@ internal sealed class KubernetesSurfaceSecretProvider : ISurfaceSecretProvider
|
||||
}
|
||||
}
|
||||
|
||||
public SurfaceSecretHandle Get(SurfaceSecretRequest request)
|
||||
{
|
||||
var directory = Path.Combine(_configuration.Root!, request.Tenant, request.Component, request.SecretType);
|
||||
if (!Directory.Exists(directory))
|
||||
{
|
||||
_logger.LogDebug("Kubernetes secret directory {Directory} not found.", directory);
|
||||
throw new SurfaceSecretNotFoundException(request);
|
||||
}
|
||||
|
||||
var name = request.Name ?? "default";
|
||||
var payloadPath = Path.Combine(directory, name);
|
||||
if (!File.Exists(payloadPath))
|
||||
{
|
||||
throw new SurfaceSecretNotFoundException(request);
|
||||
}
|
||||
|
||||
var bytes = File.ReadAllBytes(payloadPath);
|
||||
return SurfaceSecretHandle.FromBytes(bytes, new Dictionary<string, string>
|
||||
{
|
||||
["source"] = "kubernetes",
|
||||
["path"] = payloadPath
|
||||
});
|
||||
}
|
||||
|
||||
public async ValueTask<SurfaceSecretHandle> GetAsync(
|
||||
SurfaceSecretRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
|
||||
@@ -42,6 +42,73 @@ internal sealed class OfflineSurfaceSecretProvider : ISurfaceSecretProvider
|
||||
}
|
||||
}
|
||||
|
||||
public SurfaceSecretHandle Get(SurfaceSecretRequest request)
|
||||
{
|
||||
if (request is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(request));
|
||||
}
|
||||
|
||||
var directory = Path.Combine(_root, request.Tenant, request.Component, request.SecretType);
|
||||
if (!Directory.Exists(directory))
|
||||
{
|
||||
_logger.LogDebug("Offline secret directory {Directory} not found.", directory);
|
||||
throw new SurfaceSecretNotFoundException(request);
|
||||
}
|
||||
|
||||
// Deterministic selection: if name is specified use it, otherwise pick lexicographically smallest
|
||||
var targetName = request.Name ?? SelectDeterministicName(directory);
|
||||
if (targetName is null)
|
||||
{
|
||||
throw new SurfaceSecretNotFoundException(request);
|
||||
}
|
||||
|
||||
var path = Path.Combine(directory, targetName + ".json");
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
throw new SurfaceSecretNotFoundException(request);
|
||||
}
|
||||
|
||||
var json = File.ReadAllText(path);
|
||||
var descriptor = JsonSerializer.Deserialize<OfflineSecretDescriptor>(json);
|
||||
if (descriptor is null)
|
||||
{
|
||||
throw new SurfaceSecretNotFoundException(request);
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(descriptor.Payload))
|
||||
{
|
||||
return SurfaceSecretHandle.Empty;
|
||||
}
|
||||
|
||||
var bytes = Convert.FromBase64String(descriptor.Payload);
|
||||
|
||||
// Verify integrity if manifest entry exists
|
||||
var manifestKey = BuildManifestKey(request.Tenant, request.Component, request.SecretType, targetName);
|
||||
if (_manifest?.TryGetValue(manifestKey, out var entry) == true)
|
||||
{
|
||||
var actualHash = ComputeSha256(bytes);
|
||||
if (!string.Equals(actualHash, entry.Sha256, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_logger.LogError(
|
||||
"Offline secret integrity check failed for {Key}. Expected {Expected}, got {Actual}.",
|
||||
manifestKey,
|
||||
entry.Sha256,
|
||||
actualHash);
|
||||
throw new InvalidOperationException($"Offline secret integrity check failed for {manifestKey}.");
|
||||
}
|
||||
|
||||
_logger.LogDebug("Offline secret integrity verified for {Key}.", manifestKey);
|
||||
}
|
||||
|
||||
var metadata = descriptor.Metadata ?? new Dictionary<string, string>();
|
||||
metadata["source"] = "offline";
|
||||
metadata["path"] = path;
|
||||
metadata["name"] = targetName;
|
||||
|
||||
return SurfaceSecretHandle.FromBytes(bytes, metadata);
|
||||
}
|
||||
|
||||
public async ValueTask<SurfaceSecretHandle> GetAsync(
|
||||
SurfaceSecretRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
|
||||
Reference in New Issue
Block a user