Add logging for access for new public resources

This commit is contained in:
Owen
2026-06-29 18:05:29 -04:00
parent 42d98fa83b
commit ccabddc225
9 changed files with 185 additions and 8 deletions

View File

@@ -342,7 +342,7 @@ export default function GeneralPage() {
return (
<Link
href={
row.original.type === "ssh"
row.original.siteResourceId != null
? `/${row.original.orgId}/settings/resources/private?query=${row.original.resourceNiceId}`
: `/${row.original.orgId}/settings/resources/public/${row.original.resourceNiceId}`
}
@@ -369,7 +369,9 @@ export default function GeneralPage() {
value: "whitelistedEmail",
label: "Whitelisted Email"
},
{ value: "ssh", label: "SSH" }
{ value: "ssh", label: "SSH" },
{ value: "rdp", label: "RDP" },
{ value: "vnc", label: "VNC" }
]}
label={t("type")}
selectedValue={filters.type}
@@ -384,8 +386,10 @@ export default function GeneralPage() {
},
cell: ({ row }) => {
const typeLabel =
row.original.type === "ssh"
? "SSH"
row.original.type === "ssh" ||
row.original.type === "rdp" ||
row.original.type === "vnc"
? row.original.type.toUpperCase()
: row.original.type.charAt(0).toUpperCase() +
row.original.type.slice(1);
return <span>{typeLabel || "-"}</span>;
@@ -513,7 +517,15 @@ export default function GeneralPage() {
function generateSampleAccessLogs(): QueryAccessAuditLogResponse["log"] {
const locations = ["US", "DE", "GB", "FR", "JP", "CA", "AU"];
const types = ["password", "pincode", "login", "whitelistedEmail", "ssh"];
const types = [
"password",
"pincode",
"login",
"whitelistedEmail",
"ssh",
"rdp",
"vnc"
];
const actors = [
"alice@example.com",
"bob@example.com",
@@ -538,6 +550,7 @@ function generateSampleAccessLogs(): QueryAccessAuditLogResponse["log"] {
actor,
actorId: actor ? `user-${i}` : null,
resourceId: Math.floor(Math.random() * 5) + 1,
siteResourceId: null,
resourceNiceId: `resource-${(i % 3) + 1}`,
resourceName: `Resource ${(i % 3) + 1}`,
ip: `${Math.floor(Math.random() * 255)}.${Math.floor(Math.random() * 255)}.${Math.floor(Math.random() * 255)}.${Math.floor(Math.random() * 255)}`,

View File

@@ -42,6 +42,8 @@ import {
loadEncryptedLocalStorage,
saveEncryptedLocalStorage
} from "@app/lib/secureLocalStorage";
import { createApiClient } from "@app/lib/api";
import { useEnvContext } from "@app/hooks/useEnvContext";
declare module "react" {
namespace JSX {
@@ -96,6 +98,7 @@ export default function RdpClient({
primaryColor?: string | null;
}) {
const t = useTranslations();
const api = createApiClient(useEnvContext());
const STORAGE_KEY = "pangolin_rdp_credentials";
const resourceName = target?.name?.trim() || null;
@@ -311,6 +314,11 @@ export default function RdpClient({
values,
target.authToken
);
void api.post(`/org/${target.orgId}/logs/access/attempt`, {
resourceId: target.resourceId,
action: true,
type: "rdp"
});
setConnecting(false);
setShowLogin(false);
userInteraction.setVisibility(true);
@@ -320,6 +328,11 @@ export default function RdpClient({
fileTransferRef.current = null;
setShowLogin(true);
} catch (err) {
void api.post(`/org/${target.orgId}/logs/access/attempt`, {
resourceId: target.resourceId,
action: false,
type: "rdp"
});
setConnecting(false);
setShowLogin(true);
if (isIronError(err)) {

View File

@@ -36,6 +36,8 @@ import {
loadEncryptedLocalStorage,
saveEncryptedLocalStorage
} from "@app/lib/secureLocalStorage";
import { createApiClient } from "@app/lib/api";
import { useEnvContext } from "@app/hooks/useEnvContext";
type AuthTab = "password" | "privateKey";
@@ -73,6 +75,7 @@ export default function SshClient({
}) {
const STORAGE_KEY = "pangolin_ssh_credentials";
const t = useTranslations();
const api = createApiClient(useEnvContext());
const resourceName = target?.name?.trim() || null;
const passwordTabSchema = z.object({
@@ -263,6 +266,17 @@ export default function SshClient({
let authConfirmed = false;
let authErrorShown = false;
let socketOpened = false;
let auditLogged = false;
const logAudit = (action: boolean) => {
if (auditLogged || !target) return;
auditLogged = true;
void api.post(`/org/${target.orgId}/logs/access/attempt`, {
resourceId: target.resourceId,
action,
type: "ssh"
});
};
ws.onopen = () => {
socketOpened = true;
@@ -294,6 +308,7 @@ export default function SshClient({
if (msg.type === "data" && msg.data) {
if (!authConfirmed) {
authConfirmed = true;
logAudit(true);
setConnecting(false);
setConnected(true);
}
@@ -301,6 +316,7 @@ export default function SshClient({
} else if (msg.type === "error") {
if (!authConfirmed) {
authErrorShown = true;
logAudit(false);
setConnecting(false);
setConnectError(
msg.error ?? t("sshErrorAuthFailed")
@@ -323,6 +339,7 @@ export default function SshClient({
evt.data.text().then((text) => {
if (!authConfirmed) {
authConfirmed = true;
logAudit(true);
setConnecting(false);
setConnected(true);
}
@@ -332,6 +349,7 @@ export default function SshClient({
};
ws.onerror = () => {
logAudit(false);
setConnecting(false);
setConnected(false);
setConnectError(t("sshErrorWebSocket"));
@@ -355,6 +373,7 @@ export default function SshClient({
);
}
if (!authConfirmed && !authErrorShown) {
logAudit(false);
setConnectError(t("sshErrorConnectionClosed"));
}
};

View File

@@ -33,6 +33,8 @@ import {
loadEncryptedLocalStorage,
saveEncryptedLocalStorage
} from "@app/lib/secureLocalStorage";
import { createApiClient } from "@app/lib/api";
import { useEnvContext } from "@app/hooks/useEnvContext";
type VncCredentialsForm = {
password: string;
@@ -52,6 +54,7 @@ export default function VncClient({
primaryColor?: string | null;
}) {
const t = useTranslations();
const api = createApiClient(useEnvContext());
const STORAGE_KEY = "pangolin_vnc_credentials";
const resourceName = target?.name?.trim() || null;
@@ -179,6 +182,7 @@ export default function VncClient({
}
let authConfirmed = false;
let auditLogged = false;
rfb.scaleViewport = true;
rfb.resizeSession = true;
@@ -190,6 +194,12 @@ export default function VncClient({
target.authToken
);
authConfirmed = true;
auditLogged = true;
void api.post(`/org/${target.orgId}/logs/access/attempt`, {
resourceId: target.resourceId,
action: true,
type: "vnc"
});
setConnecting(false);
setConnected(true);
});
@@ -201,6 +211,17 @@ export default function VncClient({
setConnecting(false);
setConnected(false);
if (!authConfirmed && !e.detail.clean) {
if (!auditLogged) {
auditLogged = true;
void api.post(
`/org/${target.orgId}/logs/access/attempt`,
{
resourceId: target.resourceId,
action: false,
type: "vnc"
}
);
}
setConnectError(t("sshErrorConnectionClosed"));
}
}
@@ -209,6 +230,12 @@ export default function VncClient({
rfb.addEventListener(
"securityfailure",
(e: { detail: { status: number; reason?: string } }) => {
auditLogged = true;
void api.post(`/org/${target.orgId}/logs/access/attempt`, {
resourceId: target.resourceId,
action: false,
type: "vnc"
});
disconnect();
setConnectError(
e.detail.reason ??