mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-15 06:35:21 +00:00
feat: added heartbeat and fixed up gateway page
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import z from "zod";
|
||||
|
||||
import { writeLimit } from "@app/server/config/rateLimiter";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
|
||||
@@ -29,6 +30,29 @@ export const registerGatewayV2Router = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "POST",
|
||||
url: "/heartbeat",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
response: {
|
||||
200: z.object({
|
||||
message: z.string()
|
||||
})
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
handler: async (req) => {
|
||||
await server.services.gatewayV2.heartbeat({
|
||||
orgPermission: req.permission
|
||||
});
|
||||
|
||||
return { message: "Successfully triggered heartbeat" };
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "GET",
|
||||
url: "/",
|
||||
@@ -46,4 +70,28 @@ export const registerGatewayV2Router = async (server: FastifyZodProvider) => {
|
||||
return gateways;
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "DELETE",
|
||||
url: "/:id",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
id: z.string()
|
||||
}),
|
||||
response: {
|
||||
200: z.any()
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.IDENTITY_ACCESS_TOKEN, AuthMode.JWT]),
|
||||
handler: async (req) => {
|
||||
const gateway = await server.services.gatewayV2.deleteGatewayById({
|
||||
orgPermission: req.permission,
|
||||
id: req.params.id
|
||||
});
|
||||
return { gateway };
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,11 +1,43 @@
|
||||
import { TDbClient } from "@app/db";
|
||||
import { TableName } from "@app/db/schemas";
|
||||
import { ormify } from "@app/lib/knex";
|
||||
import { GatewaysV2Schema, TableName, TGatewaysV2 } from "@app/db/schemas";
|
||||
import { DatabaseError } from "@app/lib/errors";
|
||||
import { buildFindFilter, ormify, selectAllTableCols, TFindFilter, TFindOpt } from "@app/lib/knex";
|
||||
|
||||
export type TGatewayV2DALFactory = ReturnType<typeof gatewayV2DalFactory>;
|
||||
|
||||
export const gatewayV2DalFactory = (db: TDbClient) => {
|
||||
const orm = ormify(db, TableName.GatewayV2);
|
||||
|
||||
return orm;
|
||||
const find = async (filter: TFindFilter<TGatewaysV2>, { offset, limit, sort, tx }: TFindOpt<TGatewaysV2> = {}) => {
|
||||
try {
|
||||
const query = (tx || db)(TableName.GatewayV2)
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
.where(buildFindFilter(filter, TableName.GatewayV2))
|
||||
.join(TableName.Identity, `${TableName.Identity}.id`, `${TableName.GatewayV2}.identityId`)
|
||||
.join(
|
||||
TableName.IdentityOrgMembership,
|
||||
`${TableName.IdentityOrgMembership}.identityId`,
|
||||
`${TableName.GatewayV2}.identityId`
|
||||
)
|
||||
.select(selectAllTableCols(TableName.GatewayV2))
|
||||
.select(db.ref("name").withSchema(TableName.Identity).as("identityName"));
|
||||
|
||||
if (limit) void query.limit(limit);
|
||||
if (offset) void query.offset(offset);
|
||||
if (sort) {
|
||||
void query.orderBy(sort.map(([column, order, nulls]) => ({ column: column as string, order, nulls })));
|
||||
}
|
||||
|
||||
const docs = await query;
|
||||
|
||||
return docs.map((el) => ({
|
||||
...GatewaysV2Schema.parse(el),
|
||||
identity: { id: el.identityId, name: el.identityName }
|
||||
}));
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: `${TableName.GatewayV2}: Find` });
|
||||
}
|
||||
};
|
||||
|
||||
return { ...orm, find };
|
||||
};
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import net from "node:net";
|
||||
|
||||
import * as x509 from "@peculiar/x509";
|
||||
|
||||
import { TProxies } from "@app/db/schemas";
|
||||
import { PgSqlLock } from "@app/keystore/keystore";
|
||||
import { crypto } from "@app/lib/crypto";
|
||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||
import { GatewayProxyProtocol } from "@app/lib/gateway/types";
|
||||
import { withGatewayV2Proxy } from "@app/lib/gateway-v2/gateway-v2";
|
||||
import { OrgServiceActor } from "@app/lib/types";
|
||||
import { ActorType } from "@app/services/auth/auth-type";
|
||||
import { constructPemChainFromCerts } from "@app/services/certificate/certificate-fns";
|
||||
@@ -494,9 +498,115 @@ export const gatewayV2ServiceFactory = ({
|
||||
};
|
||||
};
|
||||
|
||||
const heartbeat = async ({ orgPermission }: { orgPermission: OrgServiceActor }) => {
|
||||
const gateway = await gatewayV2DAL.findOne({
|
||||
orgId: orgPermission.orgId,
|
||||
identityId: orgPermission.id
|
||||
});
|
||||
|
||||
if (!gateway) {
|
||||
throw new NotFoundError({ message: `Gateway for identity ${orgPermission.id} not found.` });
|
||||
}
|
||||
|
||||
const gatewayV2ConnectionDetails = await getPlatformConnectionDetailsByGatewayId({
|
||||
gatewayId: gateway.id,
|
||||
targetHost: "health-check",
|
||||
targetPort: 443
|
||||
});
|
||||
|
||||
if (!gatewayV2ConnectionDetails) {
|
||||
throw new NotFoundError({ message: `Gateway connection details for gateway ${gateway.id} not found.` });
|
||||
}
|
||||
|
||||
const isGatewayReachable = await withGatewayV2Proxy(
|
||||
async (port) => {
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
const socket = new net.Socket();
|
||||
let responseReceived = false;
|
||||
let isResolved = false;
|
||||
|
||||
// Set socket timeout
|
||||
socket.setTimeout(10000);
|
||||
|
||||
const cleanup = () => {
|
||||
if (!socket.destroyed) {
|
||||
socket.destroy();
|
||||
}
|
||||
};
|
||||
|
||||
socket.on("data", (data: Buffer) => {
|
||||
const response = data.toString().trim();
|
||||
if (response === "PONG" && !isResolved) {
|
||||
isResolved = true;
|
||||
responseReceived = true;
|
||||
cleanup();
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("error", (err: Error) => {
|
||||
if (!isResolved) {
|
||||
isResolved = true;
|
||||
cleanup();
|
||||
reject(new Error(`TCP connection error: ${err.message}`));
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("timeout", () => {
|
||||
if (!isResolved) {
|
||||
isResolved = true;
|
||||
cleanup();
|
||||
reject(new Error("TCP connection timeout"));
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("close", () => {
|
||||
if (!isResolved && !responseReceived) {
|
||||
isResolved = true;
|
||||
cleanup();
|
||||
reject(new Error("Connection closed without receiving PONG"));
|
||||
}
|
||||
});
|
||||
|
||||
socket.connect(port, "localhost");
|
||||
});
|
||||
},
|
||||
{
|
||||
protocol: GatewayProxyProtocol.Ping,
|
||||
proxyIp: gatewayV2ConnectionDetails.proxyIp,
|
||||
gateway: gatewayV2ConnectionDetails.gateway,
|
||||
proxy: gatewayV2ConnectionDetails.proxy
|
||||
}
|
||||
);
|
||||
|
||||
if (!isGatewayReachable) {
|
||||
throw new BadRequestError({ message: `Gateway ${gateway.id} is not reachable` });
|
||||
}
|
||||
|
||||
await gatewayV2DAL.updateById(gateway.id, { heartbeat: new Date() });
|
||||
};
|
||||
|
||||
const deleteGatewayById = async ({ orgPermission, id }: { orgPermission: OrgServiceActor; id: string }) => {
|
||||
// const { permission } = await permissionService.getOrgPermission(
|
||||
// orgPermission.type,
|
||||
// orgPermission.id,
|
||||
// orgPermission.orgId,
|
||||
// orgPermission.authMethod,
|
||||
// orgPermission.orgId
|
||||
// );
|
||||
// ForbiddenError.from(permission).throwUnlessCan(
|
||||
// OrgPermissionGatewayActions.DeleteGateways,
|
||||
// OrgPermissionSubjects.Gateway
|
||||
// );
|
||||
|
||||
return gatewayV2DAL.deleteById(id);
|
||||
};
|
||||
|
||||
return {
|
||||
listGateways,
|
||||
registerGateway,
|
||||
getPlatformConnectionDetailsByGatewayId
|
||||
getPlatformConnectionDetailsByGatewayId,
|
||||
deleteGatewayById,
|
||||
heartbeat
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user