Step 7 — Security Hardening
Setelah transport jalan, tutup celah keamanan utama.
7.1 ACL pipe ketat (Windows)
Sudah dilakukan di Step 3 — hapus WorldSid. Verifikasi:
// HARUS BEGINI:
sec.AddAccessRule(new PipeAccessRule(
new SecurityIdentifier(WellKnownSidType.LocalSystemSid, null),
PipeAccessRights.FullControl,
AccessControlType.Allow));
sec.AddAccessRule(new PipeAccessRule(
new SecurityIdentifier(WellKnownSidType.InteractiveSid, null),
PipeAccessRights.ReadWrite,
AccessControlType.Allow));
// HARUS TIDAK ADA:
// new SecurityIdentifier(WellKnownSidType.WorldSid, null) ← HAPUS
7.2 Verify caller process (Windows)
ACL melindungi pipe access, tapi tidak verifikasi identitas binary yang connect. Tambahkan:
using System;
using System.IO;
using System.IO.Pipes;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
namespace HermesServiceEngine.Rpc;
[SupportedOSPlatform("windows")]
public static class PeerVerifier
{
/// <summary>
/// Verifikasi proses yang terhubung adalah HermesNetwork.exe yang ditandatangani.
/// </summary>
public static void Verify(Stream stream)
{
if (stream is not NamedPipeServerStream pipe)
throw new InvalidOperationException("Not a NamedPipeServerStream");
if (!GetNamedPipeClientProcessId(pipe.SafePipeHandle.DangerousGetHandle(), out var clientPid))
throw new InvalidOperationException("Cannot get client PID");
var process = System.Diagnostics.Process.GetProcessById((int)clientPid);
var exePath = process.MainModule?.FileName
?? throw new InvalidOperationException("Cannot read client exe path");
// 1. Whitelist nama executable
var fileName = Path.GetFileName(exePath);
if (!string.Equals(fileName, "HermesNetwork.exe", StringComparison.OrdinalIgnoreCase))
throw new UnauthorizedAccessException($"Unexpected client: {fileName}");
// 2. Verifikasi signature Authenticode
if (!AuthenticodeVerifier.IsSignedByExpectedPublisher(exePath))
throw new UnauthorizedAccessException($"Client not properly signed: {exePath}");
}
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool GetNamedPipeClientProcessId(IntPtr Pipe, out uint ClientProcessId);
}
using System.Security.Cryptography.X509Certificates;
namespace HermesServiceEngine.Rpc;
public static class AuthenticodeVerifier
{
// Replace dengan thumbprint sertifikat code signing milik tim
private const string ExpectedThumbprint = "ABCDEF0123456789ABCDEF0123456789ABCDEF01";
public static bool IsSignedByExpectedPublisher(string filePath)
{
try
{
var cert = X509Certificate.CreateFromSignedFile(filePath);
var cert2 = new X509Certificate2(cert);
return string.Equals(cert2.Thumbprint, ExpectedThumbprint,
System.StringComparison.OrdinalIgnoreCase);
}
catch
{
return false;
}
}
}
Untuk verifikasi penuh chain trust + revocation, gunakan
WinVerifyTrustWin32 API (lebih ketat tapi kompleks). Untuk MVP, thumbprint check cukup.
Panggil PeerVerifier.Verify(stream) di HermesRpcHost.HandleClientAsync sebelum rpc.StartListening().
7.3 Verify caller (macOS / Linux)
Untuk UDS, gunakan SO_PEERCRED (Linux) atau LOCAL_PEERCRED (macOS) via Socket.GetSocketOption:
using System.Net.Sockets;
using System.Runtime.InteropServices;
namespace HermesServiceEngine.Rpc;
public static class UnixPeerVerifier
{
public static void Verify(Socket socket, int expectedUid)
{
// Linux: SO_PEERCRED returns ucred {pid, uid, gid}
// macOS: LOCAL_PEERCRED returns xucred
// Gunakan getsockopt P/Invoke — code disesuaikan per OS
var peerUid = GetPeerUid(socket);
if (peerUid != expectedUid)
throw new System.UnauthorizedAccessException(
$"Peer uid {peerUid} mismatch (expected {expectedUid})");
}
private static int GetPeerUid(Socket socket)
{
// Implementasi platform-specific via P/Invoke ke getsockopt
// (detail diluar scope — ada library Mono.Posix atau snippet di referensi)
throw new System.NotImplementedException("See references for implementation");
}
}
Pada macOS modern, kombinasikan dengan SecCodeCheckValidity untuk verifikasi code signature dari pid client.
7.4 Logging dengan redaksi
Tambahkan trace listener custom:
using System.Diagnostics;
using System.Text.RegularExpressions;
namespace HermesServiceEngine.Rpc;
public sealed class RedactingTraceListener : TraceListener
{
private static readonly Regex PrivateKeyRegex =
new(@"PrivateKey\s*=\s*[A-Za-z0-9+/=]+", RegexOptions.Compiled);
private static readonly Regex Base64SecretRegex =
new(@"[A-Za-z0-9+/]{40,}={0,2}", RegexOptions.Compiled);
public override void Write(string? message) =>
HermesServiceEngine.Log.HelpReport.LogInfo(Redact(message));
public override void WriteLine(string? message) =>
HermesServiceEngine.Log.HelpReport.LogInfo(Redact(message));
private static string Redact(string? input)
{
if (string.IsNullOrEmpty(input)) return input ?? "";
input = PrivateKeyRegex.Replace(input, "PrivateKey=<REDACTED>");
input = Base64SecretRegex.Replace(input, "<REDACTED_SECRET>");
return input;
}
}
Pasang di host:
var rpc = new JsonRpc(handler)
{
TraceSource = new TraceSource("HermesRpc", SourceLevels.Information)
};
rpc.TraceSource.Listeners.Add(new RedactingTraceListener());
7.5 Rate limiting (opsional)
Untuk mitigasi DoS dari proses lokal yang sah-tapi-buggy, tambahkan throttle di server:
private readonly SemaphoreSlim _rateLimit = new(initialCount: 10, maxCount: 10);
public async Task<RpcResult> StartXdrAsync(string config, CancellationToken ct)
{
if (!await _rateLimit.WaitAsync(0, ct))
return RpcResult.Fail("Rate limit exceeded");
try { /* ... */ }
finally { _rateLimit.Release(); }
}
Checklist
-
WorldSidbenar-benar dihapus dari ACL -
PeerVerifier.Verify(stream)dipanggil sebelumStartListening - Thumbprint sertifikat di-config (jangan hardcode di repo public!)
-
RedactingTraceListeneraktif - (macOS)
UnixPeerVerifierdiimplementasikan - Rate limit di method yang sensitif