Merge branch 'main' into feat/secure-mfa-endpoints-with-improved-rate-limiting

This commit is contained in:
Sheen Capadngan
2024-05-27 10:55:06 +08:00
14 changed files with 304 additions and 111 deletions
@@ -3,7 +3,6 @@ import { RawAxiosRequestHeaders } from "axios";
import { SecretKeyEncoding } from "@app/db/schemas"; import { SecretKeyEncoding } from "@app/db/schemas";
import { request } from "@app/lib/config/request"; import { request } from "@app/lib/config/request";
import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption"; import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
import { logger } from "@app/lib/logger";
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue"; import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
import { TProjectDALFactory } from "@app/services/project/project-dal"; import { TProjectDALFactory } from "@app/services/project/project-dal";
@@ -113,35 +112,7 @@ export const auditLogQueueServiceFactory = ({
); );
}); });
queueService.start(QueueName.AuditLogPrune, async () => {
logger.info(`${QueueName.AuditLogPrune}: queue task started`);
await auditLogDAL.pruneAuditLog();
logger.info(`${QueueName.AuditLogPrune}: queue task completed`);
});
// we do a repeat cron job in utc timezone at 12 Midnight each day
const startAuditLogPruneJob = async () => {
// clear previous job
await queueService.stopRepeatableJob(
QueueName.AuditLogPrune,
QueueJobs.AuditLogPrune,
{ pattern: "0 0 * * *", utc: true },
QueueName.AuditLogPrune // just a job id
);
await queueService.queue(QueueName.AuditLogPrune, QueueJobs.AuditLogPrune, undefined, {
delay: 5000,
jobId: QueueName.AuditLogPrune,
repeat: { pattern: "0 0 * * *", utc: true }
});
};
queueService.listen(QueueName.AuditLogPrune, "failed", (err) => {
logger.error(err?.failedReason, `${QueueName.AuditLogPrune}: log pruning failed`);
});
return { return {
pushToLog, pushToLog
startAuditLogPruneJob
}; };
}; };
+50 -6
View File
@@ -104,24 +104,68 @@ export const ormify = <DbOps extends object, Tname extends keyof Tables>(db: Kne
throw new DatabaseError({ error, name: "Create" }); throw new DatabaseError({ error, name: "Create" });
} }
}, },
updateById: async (id: string, data: Tables[Tname]["update"], tx?: Knex) => { updateById: async (
id: string,
{
$incr,
$decr,
...data
}: Tables[Tname]["update"] & {
$incr?: { [x in keyof Partial<Tables[Tname]["base"]>]: number };
$decr?: { [x in keyof Partial<Tables[Tname]["base"]>]: number };
},
tx?: Knex
) => {
try { try {
const [res] = await (tx || db)(tableName) const query = (tx || db)(tableName)
.where({ id } as never) .where({ id } as never)
.update(data as never) .update(data as never)
.returning("*"); .returning("*");
return res; if ($incr) {
Object.entries($incr).forEach(([incrementField, incrementValue]) => {
void query.increment(incrementField, incrementValue);
});
}
if ($decr) {
Object.entries($decr).forEach(([incrementField, incrementValue]) => {
void query.increment(incrementField, incrementValue);
});
}
const [docs] = await query;
return docs;
} catch (error) { } catch (error) {
throw new DatabaseError({ error, name: "Update by id" }); throw new DatabaseError({ error, name: "Update by id" });
} }
}, },
update: async (filter: TFindFilter<Tables[Tname]["base"]>, data: Tables[Tname]["update"], tx?: Knex) => { update: async (
filter: TFindFilter<Tables[Tname]["base"]>,
{
$incr,
$decr,
...data
}: Tables[Tname]["update"] & {
$incr?: { [x in keyof Partial<Tables[Tname]["base"]>]: number };
$decr?: { [x in keyof Partial<Tables[Tname]["base"]>]: number };
},
tx?: Knex
) => {
try { try {
const res = await (tx || db)(tableName) const query = (tx || db)(tableName)
.where(buildFindFilter(filter)) .where(buildFindFilter(filter))
.update(data as never) .update(data as never)
.returning("*"); .returning("*");
return res; // increment and decrement operation in update
if ($incr) {
Object.entries($incr).forEach(([incrementField, incrementValue]) => {
void query.increment(incrementField, incrementValue);
});
}
if ($decr) {
Object.entries($decr).forEach(([incrementField, incrementValue]) => {
void query.increment(incrementField, incrementValue);
});
}
return await query;
} catch (error) { } catch (error) {
throw new DatabaseError({ error, name: "Update" }); throw new DatabaseError({ error, name: "Update" });
} }
+11 -1
View File
@@ -12,7 +12,9 @@ export enum QueueName {
SecretRotation = "secret-rotation", SecretRotation = "secret-rotation",
SecretReminder = "secret-reminder", SecretReminder = "secret-reminder",
AuditLog = "audit-log", AuditLog = "audit-log",
// TODO(akhilmhdh): This will get removed later. For now this is kept to stop the repeatable queue
AuditLogPrune = "audit-log-prune", AuditLogPrune = "audit-log-prune",
DailyResourceCleanUp = "daily-resource-cleanup",
TelemetryInstanceStats = "telemtry-self-hosted-stats", TelemetryInstanceStats = "telemtry-self-hosted-stats",
IntegrationSync = "sync-integrations", IntegrationSync = "sync-integrations",
SecretWebhook = "secret-webhook", SecretWebhook = "secret-webhook",
@@ -26,7 +28,9 @@ export enum QueueJobs {
SecretReminder = "secret-reminder-job", SecretReminder = "secret-reminder-job",
SecretRotation = "secret-rotation-job", SecretRotation = "secret-rotation-job",
AuditLog = "audit-log-job", AuditLog = "audit-log-job",
// TODO(akhilmhdh): This will get removed later. For now this is kept to stop the repeatable queue
AuditLogPrune = "audit-log-prune-job", AuditLogPrune = "audit-log-prune-job",
DailyResourceCleanUp = "daily-resource-cleanup-job",
SecWebhook = "secret-webhook-trigger", SecWebhook = "secret-webhook-trigger",
TelemetryInstanceStats = "telemetry-self-hosted-stats", TelemetryInstanceStats = "telemetry-self-hosted-stats",
IntegrationSync = "secret-integration-pull", IntegrationSync = "secret-integration-pull",
@@ -55,6 +59,10 @@ export type TQueueJobTypes = {
name: QueueJobs.AuditLog; name: QueueJobs.AuditLog;
payload: TCreateAuditLogDTO; payload: TCreateAuditLogDTO;
}; };
[QueueName.DailyResourceCleanUp]: {
name: QueueJobs.DailyResourceCleanUp;
payload: undefined;
};
[QueueName.AuditLogPrune]: { [QueueName.AuditLogPrune]: {
name: QueueJobs.AuditLogPrune; name: QueueJobs.AuditLogPrune;
payload: undefined; payload: undefined;
@@ -172,7 +180,9 @@ export const queueServiceFactory = (redisUrl: string) => {
jobId?: string jobId?: string
) => { ) => {
const q = queueContainer[name]; const q = queueContainer[name];
return q.removeRepeatable(job, repeatOpt, jobId); if (q) {
return q.removeRepeatable(job, repeatOpt, jobId);
}
}; };
const stopRepeatableJobByJobId = async <T extends QueueName>(name: T, jobId: string) => { const stopRepeatableJobByJobId = async <T extends QueueName>(name: T, jobId: string) => {
+16 -1
View File
@@ -6,6 +6,7 @@ const headersOrder = [
"cf-connecting-ip", // Cloudflare "cf-connecting-ip", // Cloudflare
"Cf-Pseudo-IPv4", // Cloudflare "Cf-Pseudo-IPv4", // Cloudflare
"x-client-ip", // Most common "x-client-ip", // Most common
"x-envoy-external-address", // for envoy
"x-forwarded-for", // Mostly used by proxies "x-forwarded-for", // Mostly used by proxies
"fastly-client-ip", "fastly-client-ip",
"true-client-ip", // Akamai and Cloudflare "true-client-ip", // Akamai and Cloudflare
@@ -23,7 +24,21 @@ export const fastifyIp = fp(async (fastify) => {
const forwardedIpHeader = headersOrder.find((header) => Boolean(req.headers[header])); const forwardedIpHeader = headersOrder.find((header) => Boolean(req.headers[header]));
const forwardedIp = forwardedIpHeader ? req.headers[forwardedIpHeader] : undefined; const forwardedIp = forwardedIpHeader ? req.headers[forwardedIpHeader] : undefined;
if (forwardedIp) { if (forwardedIp) {
req.realIp = Array.isArray(forwardedIp) ? forwardedIp[0] : forwardedIp; if (Array.isArray(forwardedIp)) {
// eslint-disable-next-line
req.realIp = forwardedIp[0];
return;
}
if (forwardedIp.includes(",")) {
// the ip header when placed with load balancers that proxy request
// will attach the internal ips to header by appending with comma
// https://github.com/go-chi/chi/blob/master/middleware/realip.go
const clientIPFromProxy = forwardedIp.slice(0, forwardedIp.indexOf(",")).trim();
req.realIp = clientIPFromProxy;
return;
}
req.realIp = forwardedIp;
} else { } else {
req.realIp = req.ip; req.realIp = req.ip;
} }
+7 -1
View File
@@ -115,6 +115,7 @@ import { projectMembershipServiceFactory } from "@app/services/project-membershi
import { projectUserMembershipRoleDALFactory } from "@app/services/project-membership/project-user-membership-role-dal"; import { projectUserMembershipRoleDALFactory } from "@app/services/project-membership/project-user-membership-role-dal";
import { projectRoleDALFactory } from "@app/services/project-role/project-role-dal"; import { projectRoleDALFactory } from "@app/services/project-role/project-role-dal";
import { projectRoleServiceFactory } from "@app/services/project-role/project-role-service"; import { projectRoleServiceFactory } from "@app/services/project-role/project-role-service";
import { dailyResourceCleanUpQueueServiceFactory } from "@app/services/resource-cleanup/resource-cleanup-queue";
import { secretDALFactory } from "@app/services/secret/secret-dal"; import { secretDALFactory } from "@app/services/secret/secret-dal";
import { secretQueueFactory } from "@app/services/secret/secret-queue"; import { secretQueueFactory } from "@app/services/secret/secret-queue";
import { secretServiceFactory } from "@app/services/secret/secret-service"; import { secretServiceFactory } from "@app/services/secret/secret-service";
@@ -769,14 +770,19 @@ export const registerRoutes = async (
folderDAL, folderDAL,
licenseService licenseService
}); });
const dailyResourceCleanUp = dailyResourceCleanUpQueueServiceFactory({
auditLogDAL,
queueService,
identityAccessTokenDAL
});
await superAdminService.initServerCfg(); await superAdminService.initServerCfg();
// //
// setup the communication with license key server // setup the communication with license key server
await licenseService.init(); await licenseService.init();
await auditLogQueue.startAuditLogPruneJob();
await telemetryQueue.startTelemetryCheck(); await telemetryQueue.startTelemetryCheck();
await dailyResourceCleanUp.startCleanUp();
// inject all services // inject all services
server.decorate<FastifyZodProvider["services"]>("services", { server.decorate<FastifyZodProvider["services"]>("services", {
@@ -70,5 +70,48 @@ export const identityAccessTokenDALFactory = (db: TDbClient) => {
} }
}; };
return { ...identityAccessTokenOrm, findOne }; const removeExpiredTokens = async (tx?: Knex) => {
try {
const docs = (tx || db)(TableName.IdentityAccessToken)
.where({
isAccessTokenRevoked: true
})
.orWhere((qb) => {
void qb
.where("accessTokenNumUsesLimit", ">", 0)
.andWhere(
"accessTokenNumUses",
">=",
db.ref("accessTokenNumUsesLimit").withSchema(TableName.IdentityAccessToken)
);
})
.orWhere((qb) => {
void qb.where("accessTokenTTL", ">", 0).andWhere((qb2) => {
void qb2
.where((qb3) => {
void qb3
.whereNotNull("accessTokenLastRenewedAt")
// accessTokenLastRenewedAt + convert_integer_to_seconds(accessTokenTTL) < present_date
.andWhereRaw(
`"${TableName.IdentityAccessToken}"."accessTokenLastRenewedAt" + make_interval(secs => "${TableName.IdentityAccessToken}"."accessTokenTTL") < NOW()`
);
})
.orWhere((qb3) => {
void qb3
.whereNull("accessTokenLastRenewedAt")
// created + convert_integer_to_seconds(accessTokenTTL) < present_date
.andWhereRaw(
`"${TableName.IdentityAccessToken}"."createdAt" + make_interval(secs => "${TableName.IdentityAccessToken}"."accessTokenTTL") < NOW()`
);
});
});
})
.delete();
return await docs;
} catch (error) {
throw new DatabaseError({ error, name: "IdentityAccessTokenPrune" });
}
};
return { ...identityAccessTokenOrm, findOne, removeExpiredTokens };
}; };
@@ -21,17 +21,18 @@ export const identityAccessTokenServiceFactory = ({
identityAccessTokenDAL, identityAccessTokenDAL,
identityOrgMembershipDAL identityOrgMembershipDAL
}: TIdentityAccessTokenServiceFactoryDep) => { }: TIdentityAccessTokenServiceFactoryDep) => {
const validateAccessTokenExp = (identityAccessToken: TIdentityAccessTokens) => { const validateAccessTokenExp = async (identityAccessToken: TIdentityAccessTokens) => {
const { const {
id: tokenId,
accessTokenTTL, accessTokenTTL,
accessTokenNumUses, accessTokenNumUses,
accessTokenNumUsesLimit, accessTokenNumUsesLimit,
accessTokenLastRenewedAt, accessTokenLastRenewedAt,
accessTokenMaxTTL,
createdAt: accessTokenCreatedAt createdAt: accessTokenCreatedAt
} = identityAccessToken; } = identityAccessToken;
if (accessTokenNumUsesLimit > 0 && accessTokenNumUses > 0 && accessTokenNumUses >= accessTokenNumUsesLimit) { if (accessTokenNumUsesLimit > 0 && accessTokenNumUses > 0 && accessTokenNumUses >= accessTokenNumUsesLimit) {
await identityAccessTokenDAL.deleteById(tokenId);
throw new BadRequestError({ throw new BadRequestError({
message: "Unable to renew because access token number of uses limit reached" message: "Unable to renew because access token number of uses limit reached"
}); });
@@ -46,41 +47,26 @@ export const identityAccessTokenServiceFactory = ({
const ttlInMilliseconds = Number(accessTokenTTL) * 1000; const ttlInMilliseconds = Number(accessTokenTTL) * 1000;
const expirationDate = new Date(accessTokenRenewed.getTime() + ttlInMilliseconds); const expirationDate = new Date(accessTokenRenewed.getTime() + ttlInMilliseconds);
if (currentDate > expirationDate) if (currentDate > expirationDate) {
await identityAccessTokenDAL.deleteById(tokenId);
throw new UnauthorizedError({ throw new UnauthorizedError({
message: "Failed to renew MI access token due to TTL expiration" message: "Failed to renew MI access token due to TTL expiration"
}); });
}
} else { } else {
// access token has never been renewed // access token has never been renewed
const accessTokenCreated = new Date(accessTokenCreatedAt); const accessTokenCreated = new Date(accessTokenCreatedAt);
const ttlInMilliseconds = Number(accessTokenTTL) * 1000; const ttlInMilliseconds = Number(accessTokenTTL) * 1000;
const expirationDate = new Date(accessTokenCreated.getTime() + ttlInMilliseconds); const expirationDate = new Date(accessTokenCreated.getTime() + ttlInMilliseconds);
if (currentDate > expirationDate) if (currentDate > expirationDate) {
await identityAccessTokenDAL.deleteById(tokenId);
throw new UnauthorizedError({ throw new UnauthorizedError({
message: "Failed to renew MI access token due to TTL expiration" message: "Failed to renew MI access token due to TTL expiration"
}); });
}
} }
} }
// max ttl checks
if (Number(accessTokenMaxTTL) > 0) {
const accessTokenCreated = new Date(accessTokenCreatedAt);
const ttlInMilliseconds = Number(accessTokenMaxTTL) * 1000;
const currentDate = new Date();
const expirationDate = new Date(accessTokenCreated.getTime() + ttlInMilliseconds);
if (currentDate > expirationDate)
throw new UnauthorizedError({
message: "Failed to renew MI access token due to Max TTL expiration"
});
const extendToDate = new Date(currentDate.getTime() + Number(accessTokenTTL));
if (extendToDate > expirationDate)
throw new UnauthorizedError({
message: "Failed to renew MI access token past its Max TTL expiration"
});
}
}; };
const renewAccessToken = async ({ accessToken }: TRenewAccessTokenDTO) => { const renewAccessToken = async ({ accessToken }: TRenewAccessTokenDTO) => {
@@ -97,7 +83,32 @@ export const identityAccessTokenServiceFactory = ({
}); });
if (!identityAccessToken) throw new UnauthorizedError(); if (!identityAccessToken) throw new UnauthorizedError();
validateAccessTokenExp(identityAccessToken); await validateAccessTokenExp(identityAccessToken);
const { accessTokenMaxTTL, createdAt: accessTokenCreatedAt, accessTokenTTL } = identityAccessToken;
// max ttl checks - will it go above max ttl
if (Number(accessTokenMaxTTL) > 0) {
const accessTokenCreated = new Date(accessTokenCreatedAt);
const ttlInMilliseconds = Number(accessTokenMaxTTL) * 1000;
const currentDate = new Date();
const expirationDate = new Date(accessTokenCreated.getTime() + ttlInMilliseconds);
if (currentDate > expirationDate) {
await identityAccessTokenDAL.deleteById(identityAccessToken.id);
throw new UnauthorizedError({
message: "Failed to renew MI access token due to Max TTL expiration"
});
}
const extendToDate = new Date(currentDate.getTime() + Number(accessTokenTTL * 1000));
if (extendToDate > expirationDate) {
await identityAccessTokenDAL.deleteById(identityAccessToken.id);
throw new UnauthorizedError({
message: "Failed to renew MI access token past its Max TTL expiration"
});
}
}
const updatedIdentityAccessToken = await identityAccessTokenDAL.updateById(identityAccessToken.id, { const updatedIdentityAccessToken = await identityAccessTokenDAL.updateById(identityAccessToken.id, {
accessTokenLastRenewedAt: new Date() accessTokenLastRenewedAt: new Date()
@@ -131,7 +142,7 @@ export const identityAccessTokenServiceFactory = ({
}); });
if (!identityAccessToken) throw new UnauthorizedError(); if (!identityAccessToken) throw new UnauthorizedError();
if (ipAddress) { if (ipAddress && identityAccessToken) {
checkIPAgainstBlocklist({ checkIPAgainstBlocklist({
ipAddress, ipAddress,
trustedIps: identityAccessToken?.accessTokenTrustedIps as TIp[] trustedIps: identityAccessToken?.accessTokenTrustedIps as TIp[]
@@ -146,7 +157,14 @@ export const identityAccessTokenServiceFactory = ({
throw new UnauthorizedError({ message: "Identity does not belong to any organization" }); throw new UnauthorizedError({ message: "Identity does not belong to any organization" });
} }
validateAccessTokenExp(identityAccessToken); await validateAccessTokenExp(identityAccessToken);
await identityAccessTokenDAL.updateById(identityAccessToken.id, {
accessTokenLastUsedAt: new Date(),
$incr: {
accessTokenNumUses: 1
}
});
return { ...identityAccessToken, orgId: identityOrgMembership.orgId }; return { ...identityAccessToken, orgId: identityOrgMembership.orgId };
}; };
@@ -2696,18 +2696,21 @@ const syncSecretsCloudflarePages = async ({
}) })
).data.result.deployment_configs[integration.targetEnvironment as string].env_vars; ).data.result.deployment_configs[integration.targetEnvironment as string].env_vars;
// copy the secrets object, so we can set deleted keys to null let secretEntries: [string, object | null][] = Object.entries(getSecretKeyValuePair(secrets)).map(([key, val]) => [
const secretsObj = Object.fromEntries( key,
Object.entries(getSecretKeyValuePair(secrets)).map(([key, val]) => [ { type: "secret_text", value: val }
key, ]);
key in Object.keys(getSecretsRes) ? { type: "secret_text", value: val } : null
]) if (getSecretsRes) {
); const toDeleteKeys = Object.keys(getSecretsRes).filter((key) => !Object.keys(secrets).includes(key));
const toDeleteEntries: [string, null][] = toDeleteKeys.map((key) => [key, null]);
secretEntries = [...secretEntries, ...toDeleteEntries];
}
const data = { const data = {
deployment_configs: { deployment_configs: {
[integration.targetEnvironment as string]: { [integration.targetEnvironment as string]: {
env_vars: secretsObj env_vars: Object.fromEntries(secretEntries)
} }
} }
}; };
@@ -0,0 +1,58 @@
import { TAuditLogDALFactory } from "@app/ee/services/audit-log/audit-log-dal";
import { logger } from "@app/lib/logger";
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
type TDailyResourceCleanUpQueueServiceFactoryDep = {
auditLogDAL: Pick<TAuditLogDALFactory, "pruneAuditLog">;
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "removeExpiredTokens">;
queueService: TQueueServiceFactory;
};
export type TDailyResourceCleanUpQueueServiceFactory = ReturnType<typeof dailyResourceCleanUpQueueServiceFactory>;
export const dailyResourceCleanUpQueueServiceFactory = ({
auditLogDAL,
queueService,
identityAccessTokenDAL
}: TDailyResourceCleanUpQueueServiceFactoryDep) => {
queueService.start(QueueName.DailyResourceCleanUp, async () => {
logger.info(`${QueueName.DailyResourceCleanUp}: queue task started`);
await auditLogDAL.pruneAuditLog();
await identityAccessTokenDAL.removeExpiredTokens();
logger.info(`${QueueName.DailyResourceCleanUp}: queue task completed`);
});
// we do a repeat cron job in utc timezone at 12 Midnight each day
const startCleanUp = async () => {
// TODO(akhilmhdh): remove later
await queueService.stopRepeatableJob(
QueueName.AuditLogPrune,
QueueJobs.AuditLogPrune,
{ pattern: "0 0 * * *", utc: true },
QueueName.AuditLogPrune // just a job id
);
// clear previous job
await queueService.stopRepeatableJob(
QueueName.DailyResourceCleanUp,
QueueJobs.DailyResourceCleanUp,
{ pattern: "0 0 * * *", utc: true },
QueueName.DailyResourceCleanUp // just a job id
);
await queueService.queue(QueueName.DailyResourceCleanUp, QueueJobs.DailyResourceCleanUp, undefined, {
delay: 5000,
jobId: QueueName.DailyResourceCleanUp,
repeat: { pattern: "0 0 * * *", utc: true }
});
};
queueService.listen(QueueName.DailyResourceCleanUp, "failed", (_, err) => {
logger.error(err, `${QueueName.DailyResourceCleanUp}: resource cleanup failed`);
});
return {
startCleanUp
};
};
+25
View File
@@ -0,0 +1,25 @@
---
title: "Onboarding"
sidebarTitle: "Onboarding"
description: "This handbook explains how we work at Infisical."
---
Welcome to Infisical!
The first few days of every new joiner are going to be packed with learning lots of new information, meeting new teammates, and understanding Infisical on a deeper level.
Plus, our team is remote-first and spread across the globe (from San Francisco to Philippines), so having a great onboarding experience is very important for the new joiner to feel part of the team and be excited about what we're doing as a company.
## Onboarding buddy
Every new joiner has an onboarding buddy who should ideally be in the the same timezone. The onboarding buddy should be able to help with any questions that pop up during the first few weeks. Of course, everyone is available to help, but it&apos;s good to have a dedicated person that you can go to with any questions.
## Onboarding Checklist
1. Join the weekly all-hands meeting. It typically happens on Monday's at 8:30am PT.
3. Ship something together on day one – even if tiny! It feels great to hit the ground running, with a development environment all ready to go.
4. Check out the [Areas of Responsibility (AoR) Table](https://docs.google.com/spreadsheets/d/1RnXlGFg83Sgu0dh7ycuydsSobmFfI3A0XkGw7vrVxEI/edit?usp=sharing). This is helpful to know who you can ask about particular areas of Infisical. Feel free to add yourself to the areas you'd be most interesting to dive into.
5. Read the [Infisical Strategy Doc](https://docs.google.com/document/d/1oy_NP1Q_Zt1oqxLpyNkLIGmhAI3N28AmZq6dDIOONSQ/edit?usp=sharing).
7. Update your LinkedIn profile with one of [Infisical's official banners](https://drive.google.com/drive/u/0/folders/1oSNWjbpRl9oNYwxM_98IqzKs9fAskrb2) (if you want to). You can also coordinate your social posts in the #marketing Slack channel, so that we can boost it from Infisical's official social media accounts.
8. Over the first few weeks, feel free to schedule 1:1s with folks on the team to get to know them a bit better.
2. Change your Slack username in the users channel to `[NAME] (Infisical)`.
+11
View File
@@ -0,0 +1,11 @@
---
title: "Infisical Company Handbook"
sidebarTitle: "Welcome"
description: "This handbook explains how we work at Infisical."
---
Welcome! This handbook explains how we work and what we stand for at Infisical.
Given that Infisical's core is open source, we decided to make this handbook also availably publicly to everyone.
You can treat it as a living document as more pages and information will be added over time.
+11 -25
View File
@@ -1,6 +1,5 @@
{ {
"name": "Infisical", "name": "Infisical",
"openapi": "https://app.infisical.com/api/docs/json",
"logo": { "logo": {
"dark": "/logo/dark.svg", "dark": "/logo/dark.svg",
"light": "/logo/light.svg", "light": "/logo/light.svg",
@@ -44,33 +43,20 @@
"name": "Start for Free", "name": "Start for Free",
"url": "https://app.infisical.com/signup" "url": "https://app.infisical.com/signup"
}, },
"tabs": [ "primaryTab": {
{ "name": "About"
"name": "Integrations", },
"url": "integrations"
},
{
"name": "CLI",
"url": "cli"
},
{
"name": "API Reference",
"url": "api-reference"
},
{
"name": "SDKs",
"url": "sdks"
},
{
"name": "Changelog",
"url": "changelog"
}
],
"navigation": [ "navigation": [
{ {
"group": "Getting Started", "group": "Handbook",
"pages": [ "pages": [
"documentation/getting-started/introduction" "handbook/overview"
]
},
{
"group": "How we work",
"pages": [
"handbook/onboarding"
] ]
} }
], ],
@@ -51,6 +51,8 @@ description: "How to sync secrets from Infisical to GCP Secret Manager"
<Warning> <Warning>
Using Infisical to sync secrets to GCP Secret Manager requires that you enable Using Infisical to sync secrets to GCP Secret Manager requires that you enable
the Service Usage API and Cloud Resource Manager API in the Google Cloud project you want to sync secrets to. More on that [here](https://cloud.google.com/service-usage/docs/set-up-development-environment). the Service Usage API and Cloud Resource Manager API in the Google Cloud project you want to sync secrets to. More on that [here](https://cloud.google.com/service-usage/docs/set-up-development-environment).
Additionally, ensure that your GCP account has sufficient permission to manage secret and service resources (you can assign Secret Manager Admin and Service Usage Admin roles for testing purposes)
</Warning> </Warning>
</Step> </Step>
</Steps> </Steps>
@@ -115,6 +117,7 @@ description: "How to sync secrets from Infisical to GCP Secret Manager"
</Steps> </Steps>
</Accordion> </Accordion>
</AccordionGroup> </AccordionGroup>
</Tab> </Tab>
<Tab title="Self-Hosted Setup"> <Tab title="Self-Hosted Setup">
Using the GCP Secret Manager integration (via the OAuth2 method) on a self-hosted instance of Infisical requires configuring an OAuth2 application in GCP Using the GCP Secret Manager integration (via the OAuth2 method) on a self-hosted instance of Infisical requires configuring an OAuth2 application in GCP
@@ -144,6 +147,6 @@ description: "How to sync secrets from Infisical to GCP Secret Manager"
Once added, restart your Infisical instance and use the GCP Secret Manager integration. Once added, restart your Infisical instance and use the GCP Secret Manager integration.
</Step> </Step>
</Steps> </Steps>
</Tab> </Tab>
</Tabs> </Tabs>
@@ -152,7 +152,7 @@ export const SecretDropzone = ({
e.dataTransfer.dropEffect = "copy"; e.dataTransfer.dropEffect = "copy";
setDragActive.off(); setDragActive.off();
parseFile(e.dataTransfer.files[0]); parseFile(e.dataTransfer.files[0], e.dataTransfer.files[0].type === "application/json");
}; };
const handleFileUpload = (e: ChangeEvent<HTMLInputElement>) => { const handleFileUpload = (e: ChangeEvent<HTMLInputElement>) => {