mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 15:27:27 +00:00
move secret scanning to main container
This commit is contained in:
+1
-7
@@ -10,7 +10,6 @@
|
|||||||
"plugin:@typescript-eslint/recommended"
|
"plugin:@typescript-eslint/recommended"
|
||||||
],
|
],
|
||||||
"rules": {
|
"rules": {
|
||||||
"@typescript-eslint/no-empty-function": "off",
|
|
||||||
"no-console": 2,
|
"no-console": 2,
|
||||||
"quotes": [
|
"quotes": [
|
||||||
"error",
|
"error",
|
||||||
@@ -35,11 +34,6 @@
|
|||||||
"argsIgnorePattern": "^_"
|
"argsIgnorePattern": "^_"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"sort-imports": [
|
"sort-imports": 1
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"ignoreDeclarationSort": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -19,6 +19,10 @@ RUN npm ci --only-production
|
|||||||
|
|
||||||
COPY --from=build /app .
|
COPY --from=build /app .
|
||||||
|
|
||||||
|
RUN apk add --no-cache bash curl && curl -1sLf \
|
||||||
|
'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.alpine.sh' | bash \
|
||||||
|
&& apk add infisical=0.8.1
|
||||||
|
|
||||||
HEALTHCHECK --interval=10s --timeout=3s --start-period=10s \
|
HEALTHCHECK --interval=10s --timeout=3s --start-period=10s \
|
||||||
CMD node healthcheck.js
|
CMD node healthcheck.js
|
||||||
|
|
||||||
|
|||||||
Generated
+4856
-1548
File diff suppressed because it is too large
Load Diff
@@ -36,6 +36,7 @@
|
|||||||
"passport": "^0.6.0",
|
"passport": "^0.6.0",
|
||||||
"passport-google-oauth20": "^2.0.0",
|
"passport-google-oauth20": "^2.0.0",
|
||||||
"posthog-node": "^2.6.0",
|
"posthog-node": "^2.6.0",
|
||||||
|
"probot": "^12.3.1",
|
||||||
"query-string": "^7.1.3",
|
"query-string": "^7.1.3",
|
||||||
"rate-limit-mongo": "^2.3.2",
|
"rate-limit-mongo": "^2.3.2",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
@@ -103,6 +104,7 @@
|
|||||||
"jest-junit": "^15.0.0",
|
"jest-junit": "^15.0.0",
|
||||||
"nodemon": "^2.0.19",
|
"nodemon": "^2.0.19",
|
||||||
"npm": "^8.19.3",
|
"npm": "^8.19.3",
|
||||||
|
"smee-client": "^1.2.3",
|
||||||
"supertest": "^6.3.3",
|
"supertest": "^6.3.3",
|
||||||
"ts-jest": "^29.0.3",
|
"ts-jest": "^29.0.3",
|
||||||
"ts-node": "^10.9.1"
|
"ts-node": "^10.9.1"
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export const getEncryptionKey = async () => {
|
|||||||
return secretValue === "" ? undefined : secretValue;
|
return secretValue === "" ? undefined : secretValue;
|
||||||
}
|
}
|
||||||
export const getRootEncryptionKey = async () => {
|
export const getRootEncryptionKey = async () => {
|
||||||
const secretValue = (await client.getSecret("ROOT_ENCRYPTION_KEY")).secretValue;
|
const secretValue = (await client.getSecret("ROOT_ENCRYPTION_KEY")).secretValue;
|
||||||
return secretValue === "" ? undefined : secretValue;
|
return secretValue === "" ? undefined : secretValue;
|
||||||
}
|
}
|
||||||
export const getInviteOnlySignup = async () => (await client.getSecret("INVITE_ONLY_SIGNUP")).secretValue === "true"
|
export const getInviteOnlySignup = async () => (await client.getSecret("INVITE_ONLY_SIGNUP")).secretValue === "true"
|
||||||
@@ -57,6 +57,11 @@ export const getSmtpPassword = async () => (await client.getSecret("SMTP_PASSWOR
|
|||||||
export const getSmtpFromAddress = async () => (await client.getSecret("SMTP_FROM_ADDRESS")).secretValue;
|
export const getSmtpFromAddress = async () => (await client.getSecret("SMTP_FROM_ADDRESS")).secretValue;
|
||||||
export const getSmtpFromName = async () => (await client.getSecret("SMTP_FROM_NAME")).secretValue || "Infisical";
|
export const getSmtpFromName = async () => (await client.getSecret("SMTP_FROM_NAME")).secretValue || "Infisical";
|
||||||
|
|
||||||
|
export const getSecretScanningWebhookProxy = async () => (await client.getSecret("SECRET_SCANNING_WEBHOOK_PROXY")).secretValue;
|
||||||
|
export const getSecretScanningWebhookSecret = async () => (await client.getSecret("SECRET_SCANNING_WEBHOOK_SECRET")).secretValue;
|
||||||
|
export const getSecretScanningGitAppId = async () => (await client.getSecret("SECRET_SCANNING_GIT_APP_ID")).secretValue;
|
||||||
|
export const getSecretScanningPrivateKey = async () => (await client.getSecret("SECRET_SCANNING_PRIVATE_KEY")).secretValue;
|
||||||
|
|
||||||
export const getLicenseKey = async () => {
|
export const getLicenseKey = async () => {
|
||||||
const secretValue = (await client.getSecret("LICENSE_KEY")).secretValue;
|
const secretValue = (await client.getSecret("LICENSE_KEY")).secretValue;
|
||||||
return secretValue === "" ? undefined : secretValue;
|
return secretValue === "" ? undefined : secretValue;
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ export const getCurrentOrganizationInstallationStatus = async (req: Request, res
|
|||||||
|
|
||||||
export const getRisksForOrganization = async (req: Request, res: Response) => {
|
export const getRisksForOrganization = async (req: Request, res: Response) => {
|
||||||
const { organizationId } = req.params
|
const { organizationId } = req.params
|
||||||
const risks = await GitRisks.find({ organization: organizationId, status: STATUS_UNRESOLVED }).lean()
|
const risks = await GitRisks.find({ organization: organizationId, status: STATUS_UNRESOLVED }).sort({ createdAt: -1 }).lean()
|
||||||
res.json({
|
res.json({
|
||||||
risks: risks
|
risks: risks
|
||||||
})
|
})
|
||||||
|
|||||||
+26
-3
@@ -5,11 +5,12 @@ import express from "express";
|
|||||||
require("express-async-errors");
|
require("express-async-errors");
|
||||||
import helmet from "helmet";
|
import helmet from "helmet";
|
||||||
import cors from "cors";
|
import cors from "cors";
|
||||||
import { DatabaseService } from "./services";
|
import { DatabaseService, GithubSecretScanningService } from "./services";
|
||||||
import { EELicenseService } from "./ee/services";
|
import { EELicenseService } from "./ee/services";
|
||||||
import { setUpHealthEndpoint } from "./services/health";
|
import { setUpHealthEndpoint } from "./services/health";
|
||||||
import cookieParser from "cookie-parser";
|
import cookieParser from "cookie-parser";
|
||||||
import swaggerUi = require("swagger-ui-express");
|
import swaggerUi = require("swagger-ui-express");
|
||||||
|
import { Probot, createNodeMiddleware } from "probot";
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
const swaggerFile = require("../spec.json");
|
const swaggerFile = require("../spec.json");
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
@@ -44,9 +45,9 @@ import {
|
|||||||
} from "./routes/v1";
|
} from "./routes/v1";
|
||||||
import {
|
import {
|
||||||
auth as v2AuthRouter,
|
auth as v2AuthRouter,
|
||||||
|
organizations as v2OrganizationsRouter,
|
||||||
signup as v2SignupRouter,
|
signup as v2SignupRouter,
|
||||||
users as v2UsersRouter,
|
users as v2UsersRouter,
|
||||||
organizations as v2OrganizationsRouter,
|
|
||||||
workspace as v2WorkspaceRouter,
|
workspace as v2WorkspaceRouter,
|
||||||
secret as v2SecretRouter, // begin to phase out
|
secret as v2SecretRouter, // begin to phase out
|
||||||
secrets as v2SecretsRouter,
|
secrets as v2SecretsRouter,
|
||||||
@@ -65,10 +66,12 @@ import { healthCheck } from "./routes/status";
|
|||||||
import { getLogger } from "./utils/logger";
|
import { getLogger } from "./utils/logger";
|
||||||
import { RouteNotFoundError } from "./utils/errors";
|
import { RouteNotFoundError } from "./utils/errors";
|
||||||
import { requestErrorHandler } from "./middleware/requestErrorHandler";
|
import { requestErrorHandler } from "./middleware/requestErrorHandler";
|
||||||
import { getNodeEnv, getPort, getSiteURL } from "./config";
|
import { getNodeEnv, getPort, getSecretScanningGitAppId, getSecretScanningPrivateKey, getSecretScanningWebhookProxy, getSecretScanningWebhookSecret, getSiteURL } from "./config";
|
||||||
import { setup } from "./utils/setup";
|
import { setup } from "./utils/setup";
|
||||||
|
const SmeeClient = require('smee-client') // eslint-disable-line
|
||||||
|
|
||||||
const main = async () => {
|
const main = async () => {
|
||||||
|
|
||||||
await setup();
|
await setup();
|
||||||
|
|
||||||
await EELicenseService.initGlobalFeatureSet();
|
await EELicenseService.initGlobalFeatureSet();
|
||||||
@@ -84,6 +87,26 @@ const main = async () => {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (await getSecretScanningGitAppId()) {
|
||||||
|
const probot = new Probot({
|
||||||
|
appId: await getSecretScanningGitAppId(),
|
||||||
|
privateKey: await getSecretScanningPrivateKey(),
|
||||||
|
secret: await getSecretScanningWebhookSecret(),
|
||||||
|
});
|
||||||
|
|
||||||
|
if ((await getNodeEnv()) != "production") {
|
||||||
|
const smee = new SmeeClient({
|
||||||
|
source: await getSecretScanningWebhookProxy(),
|
||||||
|
target: "http://backend:4000/ss-webhook",
|
||||||
|
logger: console
|
||||||
|
})
|
||||||
|
|
||||||
|
smee.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
app.use(createNodeMiddleware(GithubSecretScanningService, { probot, webhooksPath: "/ss-webhook" })); // secret scanning webhook
|
||||||
|
}
|
||||||
|
|
||||||
if ((await getNodeEnv()) === "production") {
|
if ((await getNodeEnv()) === "production") {
|
||||||
// enable app-wide rate-limiting + helmet security
|
// enable app-wide rate-limiting + helmet security
|
||||||
// in production
|
// in production
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ export type GitRisks = {
|
|||||||
tags: string[];
|
tags: string[];
|
||||||
ruleID: string;
|
ruleID: string;
|
||||||
fingerprint: string;
|
fingerprint: string;
|
||||||
|
fingerPrintWithoutCommitId: string
|
||||||
|
|
||||||
isFalsePositive: boolean; // New field for marking risks as false positives
|
isFalsePositive: boolean; // New field for marking risks as false positives
|
||||||
isResolved: boolean; // New field for marking risks as resolved
|
isResolved: boolean; // New field for marking risks as resolved
|
||||||
@@ -94,6 +95,9 @@ const gitRisks = new Schema<GitRisks>({
|
|||||||
type: String,
|
type: String,
|
||||||
unique: true
|
unique: true
|
||||||
},
|
},
|
||||||
|
fingerPrintWithoutCommitId: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
isFalsePositive: {
|
isFalsePositive: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
|
|||||||
@@ -16,13 +16,14 @@ import ServiceAccountKey, { IServiceAccountKey } from "./serviceAccountKey"; //
|
|||||||
import ServiceAccountOrganizationPermission, { IServiceAccountOrganizationPermission } from "./serviceAccountOrganizationPermission"; // new
|
import ServiceAccountOrganizationPermission, { IServiceAccountOrganizationPermission } from "./serviceAccountOrganizationPermission"; // new
|
||||||
import ServiceAccountWorkspacePermission, { IServiceAccountWorkspacePermission } from "./serviceAccountWorkspacePermission"; // new
|
import ServiceAccountWorkspacePermission, { IServiceAccountWorkspacePermission } from "./serviceAccountWorkspacePermission"; // new
|
||||||
import TokenData, { ITokenData } from "./tokenData";
|
import TokenData, { ITokenData } from "./tokenData";
|
||||||
import User,{ AuthProvider, IUser } from "./user";
|
import User, { AuthProvider, IUser } from "./user";
|
||||||
import UserAction, { IUserAction } from "./userAction";
|
import UserAction, { IUserAction } from "./userAction";
|
||||||
import Workspace, { IWorkspace } from "./workspace";
|
import Workspace, { IWorkspace } from "./workspace";
|
||||||
import ServiceTokenData, { IServiceTokenData } from "./serviceTokenData";
|
import ServiceTokenData, { IServiceTokenData } from "./serviceTokenData";
|
||||||
import APIKeyData, { IAPIKeyData } from "./apiKeyData";
|
import APIKeyData, { IAPIKeyData } from "./apiKeyData";
|
||||||
import LoginSRPDetail, { ILoginSRPDetail } from "./loginSRPDetail";
|
import LoginSRPDetail, { ILoginSRPDetail } from "./loginSRPDetail";
|
||||||
import TokenVersion, { ITokenVersion } from "./tokenVersion";
|
import TokenVersion, { ITokenVersion } from "./tokenVersion";
|
||||||
|
import GitRisks, { STATUS_RESOLVED_FALSE_POSITIVE } from "./gitRisks";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
AuthProvider,
|
AuthProvider,
|
||||||
@@ -76,4 +77,6 @@ export {
|
|||||||
ILoginSRPDetail,
|
ILoginSRPDetail,
|
||||||
TokenVersion,
|
TokenVersion,
|
||||||
ITokenVersion,
|
ITokenVersion,
|
||||||
|
GitRisks,
|
||||||
|
STATUS_RESOLVED_FALSE_POSITIVE
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,250 @@
|
|||||||
|
import { Probot } from "probot";
|
||||||
|
import { exec } from "child_process";
|
||||||
|
import { mkdir, readFile, rm, writeFile } from "fs";
|
||||||
|
import { tmpdir } from "os";
|
||||||
|
import { join } from "path"
|
||||||
|
import GitRisks, { STATUS_RESOLVED_FALSE_POSITIVE } from "../models/gitRisks";
|
||||||
|
import GitAppOrganizationInstallation from "../models/gitAppOrganizationInstallation";
|
||||||
|
import MembershipOrg from "../models/membershipOrg";
|
||||||
|
import { ADMIN, OWNER } from "../variables";
|
||||||
|
import User from "../models/user";
|
||||||
|
import { sendMail } from "../helpers";
|
||||||
|
|
||||||
|
type SecretMatch = {
|
||||||
|
Description: string;
|
||||||
|
StartLine: number;
|
||||||
|
EndLine: number;
|
||||||
|
StartColumn: number;
|
||||||
|
EndColumn: number;
|
||||||
|
Match: string;
|
||||||
|
Secret: string;
|
||||||
|
File: string;
|
||||||
|
SymlinkFile: string;
|
||||||
|
Commit: string;
|
||||||
|
Entropy: number;
|
||||||
|
Author: string;
|
||||||
|
Email: string;
|
||||||
|
Date: string;
|
||||||
|
Message: string;
|
||||||
|
Tags: string[];
|
||||||
|
RuleID: string;
|
||||||
|
Fingerprint: string;
|
||||||
|
FingerPrintWithoutCommitId: string
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async (app: Probot) => {
|
||||||
|
app.on("installation.deleted", async (context) => {
|
||||||
|
const { payload } = context;
|
||||||
|
const { installation, repositories } = payload;
|
||||||
|
if (installation.repository_selection == "all") {
|
||||||
|
await GitRisks.deleteMany({ installationId: installation.id })
|
||||||
|
await GitAppOrganizationInstallation.deleteOne({ installationId: installation.id })
|
||||||
|
} else {
|
||||||
|
if (repositories) {
|
||||||
|
for (const repository of repositories) {
|
||||||
|
await GitRisks.deleteMany({ repositoryId: repository.id })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
app.on("push", async (context) => {
|
||||||
|
const { payload } = context;
|
||||||
|
const { commits, repository, installation, pusher } = payload;
|
||||||
|
const [owner, repo] = repository.full_name.split("/");
|
||||||
|
|
||||||
|
if (!commits || !repository || !installation || !pusher) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const installationLinkToOrgExists = await GitAppOrganizationInstallation.findOne({ installationId: installation?.id }).lean()
|
||||||
|
if (!installationLinkToOrgExists) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const allFindingsByFingerprint: { [key: string]: SecretMatch; } = {}
|
||||||
|
|
||||||
|
for (const commit of commits) {
|
||||||
|
for (const filepath of [...commit.added, ...commit.modified]) {
|
||||||
|
try {
|
||||||
|
const fileContentsResponse = await context.octokit.repos.getContent({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
path: filepath,
|
||||||
|
});
|
||||||
|
|
||||||
|
const data: any = fileContentsResponse.data;
|
||||||
|
const fileContent = Buffer.from(data.content, "base64").toString();
|
||||||
|
|
||||||
|
const findings = await scanContentAndGetFindings(`\n${fileContent}`) // extra line to count lines correctly
|
||||||
|
|
||||||
|
for (const finding of findings) {
|
||||||
|
const fingerPrintWithCommitId = `${commit.id}:${filepath}:${finding.RuleID}:${finding.StartLine}`
|
||||||
|
const fingerPrintWithoutCommitId = `${filepath}:${finding.RuleID}:${finding.StartLine}`
|
||||||
|
finding.Fingerprint = fingerPrintWithCommitId
|
||||||
|
finding.FingerPrintWithoutCommitId = fingerPrintWithoutCommitId
|
||||||
|
finding.Commit = commit.id
|
||||||
|
finding.File = filepath
|
||||||
|
finding.Author = commit.author.name
|
||||||
|
finding.Email = commit?.author?.email ? commit?.author?.email : ""
|
||||||
|
|
||||||
|
allFindingsByFingerprint[fingerPrintWithCommitId] = finding
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error fetching content for ${filepath}`, error); // eslint-disable-line
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// change to update
|
||||||
|
const noneFalsePositiveFindings: { [key: string]: SecretMatch; } = {}
|
||||||
|
|
||||||
|
for (const key in allFindingsByFingerprint) {
|
||||||
|
const risk = await GitRisks.findOneAndUpdate({ fingerprint: allFindingsByFingerprint[key].Fingerprint },
|
||||||
|
{
|
||||||
|
...convertKeysToLowercase(allFindingsByFingerprint[key]),
|
||||||
|
installationId: installation.id,
|
||||||
|
organization: installationLinkToOrgExists.organizationId,
|
||||||
|
repositoryFullName: repository.full_name,
|
||||||
|
repositoryId: repository.id
|
||||||
|
}, {
|
||||||
|
upsert: true
|
||||||
|
}).lean()
|
||||||
|
|
||||||
|
if (risk?.status == STATUS_RESOLVED_FALSE_POSITIVE) {
|
||||||
|
noneFalsePositiveFindings[key] = { ...convertKeysToLowercase(allFindingsByFingerprint[key]) }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
// get emails of admins
|
||||||
|
const adminsOfWork = await MembershipOrg.find({
|
||||||
|
organization: installationLinkToOrgExists.organizationId,
|
||||||
|
$or: [
|
||||||
|
{ role: OWNER },
|
||||||
|
{ role: ADMIN }
|
||||||
|
]
|
||||||
|
}).lean()
|
||||||
|
|
||||||
|
const userEmails = await User.find({
|
||||||
|
_id: {
|
||||||
|
$in: [adminsOfWork.map(orgMembership => orgMembership.user)]
|
||||||
|
}
|
||||||
|
}).select("email").lean()
|
||||||
|
|
||||||
|
const adminOrOwnerEmails = userEmails.map(userObject => userObject.email)
|
||||||
|
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
// don't notify if the risk is marked as false positive
|
||||||
|
|
||||||
|
// loop through each finding and check if the finger print without commit has a status of false positive, if so don't add it to the list of risks that need to be notified
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
await sendMail({
|
||||||
|
template: "secretLeakIncident.handlebars",
|
||||||
|
subjectLine: `Incident alert: leaked secrets found in Github repository ${repository.full_name}`,
|
||||||
|
recipients: ["pusher.email", ...adminOrOwnerEmails],
|
||||||
|
substitutions: {
|
||||||
|
numberOfSecrets: Object.keys(allFindingsByFingerprint).length,
|
||||||
|
pusher_email: pusher.email,
|
||||||
|
pusher_name: pusher.name
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
async function scanContentAndGetFindings(textContent: string): Promise<SecretMatch[]> {
|
||||||
|
const tempFolder = await createTempFolder();
|
||||||
|
const filePath = join(tempFolder, "content.txt");
|
||||||
|
const findingsPath = join(tempFolder, "findings.json");
|
||||||
|
|
||||||
|
try {
|
||||||
|
await writeTextToFile(filePath, textContent);
|
||||||
|
await runInfisicalScan(filePath, findingsPath);
|
||||||
|
const findingsData = await readFindingsFile(findingsPath);
|
||||||
|
return JSON.parse(findingsData);
|
||||||
|
} finally {
|
||||||
|
await deleteTempFolder(tempFolder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createTempFolder(): Promise<string> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const tempDir = tmpdir()
|
||||||
|
const tempFolderName = Math.random().toString(36).substring(2);
|
||||||
|
const tempFolderPath = join(tempDir, tempFolderName);
|
||||||
|
|
||||||
|
mkdir(tempFolderPath, (err: any) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve(tempFolderPath);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeTextToFile(filePath: string, content: string): Promise<void> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
writeFile(filePath, content, (err) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function runInfisicalScan(inputPath: string, outputPath: string): Promise<void> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const command = `cat "${inputPath}" | infisical scan --exit-code=77 --pipe -r "${outputPath}"`;
|
||||||
|
exec(command, (error) => {
|
||||||
|
if (error && error.code != 77) {
|
||||||
|
reject(error);
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function readFindingsFile(filePath: string): Promise<string> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
readFile(filePath, "utf8", (err, data) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteTempFolder(folderPath: string): Promise<void> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
rm(folderPath, { recursive: true }, (err) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertKeysToLowercase<T>(obj: T): T {
|
||||||
|
const convertedObj = {} as T;
|
||||||
|
|
||||||
|
for (const key in obj) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
||||||
|
const lowercaseKey = key.charAt(0).toLowerCase() + key.slice(1);
|
||||||
|
convertedObj[lowercaseKey as keyof T] = obj[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return convertedObj;
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import EventService from "./EventService";
|
|||||||
import IntegrationService from "./IntegrationService";
|
import IntegrationService from "./IntegrationService";
|
||||||
import TokenService from "./TokenService";
|
import TokenService from "./TokenService";
|
||||||
import SecretService from "./SecretService";
|
import SecretService from "./SecretService";
|
||||||
|
import GithubSecretScanningService from "./GithubSecretScanningService"
|
||||||
|
|
||||||
export {
|
export {
|
||||||
TelemetryService,
|
TelemetryService,
|
||||||
@@ -15,4 +16,5 @@ export {
|
|||||||
IntegrationService,
|
IntegrationService,
|
||||||
TokenService,
|
TokenService,
|
||||||
SecretService,
|
SecretService,
|
||||||
|
GithubSecretScanningService
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||||
|
<title>Incident alert: secret leaked</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h3>Infisical has uncovered {{numberOfSecrets}} secret(s) from your recent push</h3>
|
||||||
|
<p><a href="https://app.infisical.com/secret-scanning"><strong>View leaked secrets</strong></a></p>
|
||||||
|
<p>One or more secret leaks have been detected in a recent commit pushed by {{pusher_name}} ({{pusher_email}}). If
|
||||||
|
the secrets are test secrets, please mark them as false positives in the <a
|
||||||
|
href="https://app.infisical.com/">Infisical dashboard</a>.
|
||||||
|
Otherwise, please rotate the secrets immediately.</p>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -58,30 +58,6 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- infisical-dev
|
- infisical-dev
|
||||||
|
|
||||||
git-app:
|
|
||||||
container_name: infisical-dev-git-app
|
|
||||||
restart: unless-stopped
|
|
||||||
depends_on:
|
|
||||||
- mongo
|
|
||||||
- smtp-server
|
|
||||||
- backend
|
|
||||||
- frontend
|
|
||||||
volumes:
|
|
||||||
- ./secret-engine/src:/app/src/ # mounted whole src to avoid missing reload on new files
|
|
||||||
ports:
|
|
||||||
- "3005:3005"
|
|
||||||
build:
|
|
||||||
context: ./secret-engine
|
|
||||||
dockerfile: Dockerfile.dev
|
|
||||||
env_file: ./secret-engine/.env
|
|
||||||
environment:
|
|
||||||
- NODE_ENV=development
|
|
||||||
- MONGO_URL=mongodb://root:example@mongo:27017/?authSource=admin
|
|
||||||
networks:
|
|
||||||
- infisical-dev
|
|
||||||
extra_hosts:
|
|
||||||
- "host.docker.internal:host-gateway"
|
|
||||||
|
|
||||||
mongo:
|
mongo:
|
||||||
image: mongo
|
image: mongo
|
||||||
container_name: infisical-dev-mongo
|
container_name: infisical-dev-mongo
|
||||||
|
|||||||
@@ -51,16 +51,6 @@ component: {{ .Values.frontend.name | quote }}
|
|||||||
{{ include "infisical.common.matchLabels" . }}
|
{{ include "infisical.common.matchLabels" . }}
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
|
|
||||||
{{- define "infisical.secretScanningGitApp.labels" -}}
|
|
||||||
{{ include "infisical.secretScanningGitApp.matchLabels" . }}
|
|
||||||
{{ include "infisical.common.metaLabels" . }}
|
|
||||||
{{- end -}}
|
|
||||||
|
|
||||||
{{- define "infisical.secretScanningGitApp.matchLabels" -}}
|
|
||||||
component: {{ .Values.secretScanningGitApp.name | quote }}
|
|
||||||
{{ include "infisical.common.matchLabels" . }}
|
|
||||||
{{- end -}}
|
|
||||||
|
|
||||||
{{- define "infisical.mongodb.labels" -}}
|
{{- define "infisical.mongodb.labels" -}}
|
||||||
{{ include "infisical.mongodb.matchLabels" . }}
|
{{ include "infisical.mongodb.matchLabels" . }}
|
||||||
{{ include "infisical.common.metaLabels" . }}
|
{{ include "infisical.common.metaLabels" . }}
|
||||||
@@ -122,24 +112,6 @@ We truncate at 63 chars because some Kubernetes name fields are limited to this
|
|||||||
{{- end -}}
|
{{- end -}}
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
|
|
||||||
|
|
||||||
{{/*
|
|
||||||
Create a fully qualified secretScanningGitApp name.
|
|
||||||
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
|
||||||
*/}}
|
|
||||||
{{- define "infisical.secretScanningGitApp.fullname" -}}
|
|
||||||
{{- if .Values.secretScanningGitApp.fullnameOverride -}}
|
|
||||||
{{- .Values.secretScanningGitApp.fullnameOverride | trunc 63 | trimSuffix "-" -}}
|
|
||||||
{{- else -}}
|
|
||||||
{{- $name := default .Chart.Name .Values.nameOverride -}}
|
|
||||||
{{- if contains $name .Release.Name -}}
|
|
||||||
{{- printf "%s-%s" .Release.Name .Values.secretScanningGitApp.name | trunc 63 | trimSuffix "-" -}}
|
|
||||||
{{- else -}}
|
|
||||||
{{- printf "%s-%s-%s" .Release.Name $name .Values.secretScanningGitApp.name | trunc 63 | trimSuffix "-" -}}
|
|
||||||
{{- end -}}
|
|
||||||
{{- end -}}
|
|
||||||
{{- end -}}
|
|
||||||
|
|
||||||
{{/*
|
{{/*
|
||||||
Create the mongodb connection string.
|
Create the mongodb connection string.
|
||||||
*/}}
|
*/}}
|
||||||
|
|||||||
@@ -44,13 +44,6 @@ spec:
|
|||||||
name: {{ include "infisical.backend.fullname" . }}
|
name: {{ include "infisical.backend.fullname" . }}
|
||||||
port:
|
port:
|
||||||
number: 4000
|
number: 4000
|
||||||
- path: {{ $ingress.secretScanningGitApp.path }}
|
|
||||||
pathType: {{ $ingress.secretScanningGitApp.pathType }}
|
|
||||||
backend:
|
|
||||||
service:
|
|
||||||
name: {{ include "infisical.secretScanningGitApp.fullname" . }}
|
|
||||||
port:
|
|
||||||
number: 3001
|
|
||||||
{{- if $ingress.hostName }}
|
{{- if $ingress.hostName }}
|
||||||
host: {{ $ingress.hostName }}
|
host: {{ $ingress.hostName }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|||||||
@@ -1,69 +0,0 @@
|
|||||||
{{- $secretScanningGitApp := .Values.secretScanningGitApp }}
|
|
||||||
{{- $backend := .Values.backend }}
|
|
||||||
{{- if .Values.secretScanningGitApp.enabled }}
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: {{ include "infisical.secretScanningGitApp.fullname" . }}
|
|
||||||
annotations:
|
|
||||||
updatedAt: {{ now | date "2006-01-01 MST 15:04:05" | quote }}
|
|
||||||
{{- with $secretScanningGitApp.deploymentAnnotations }}
|
|
||||||
{{- toYaml . | nindent 4 }}
|
|
||||||
{{- end }}
|
|
||||||
labels:
|
|
||||||
{{- include "infisical.secretScanningGitApp.labels" . | nindent 4 }}
|
|
||||||
spec:
|
|
||||||
replicas: {{ $secretScanningGitApp.replicaCount }}
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
{{- include "infisical.secretScanningGitApp.matchLabels" . | nindent 6 }}
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
{{- include "infisical.secretScanningGitApp.matchLabels" . | nindent 8 }}
|
|
||||||
annotations:
|
|
||||||
updatedAt: {{ now | date "2006-01-01 MST 15:04:05" | quote }}
|
|
||||||
{{- with $secretScanningGitApp.podAnnotations }}
|
|
||||||
{{- toYaml . | nindent 8 }}
|
|
||||||
{{- end }}
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: {{ template "infisical.name" . }}-{{ $secretScanningGitApp.name }}
|
|
||||||
image: "{{ $secretScanningGitApp.image.repository }}:{{ $secretScanningGitApp.image.tag | default "latest" }}"
|
|
||||||
imagePullPolicy: {{ $secretScanningGitApp.image.pullPolicy }}
|
|
||||||
readinessProbe:
|
|
||||||
httpGet:
|
|
||||||
path: /
|
|
||||||
port: 3000
|
|
||||||
initialDelaySeconds: 10
|
|
||||||
periodSeconds: 10
|
|
||||||
ports:
|
|
||||||
- containerPort: 3000
|
|
||||||
envFrom:
|
|
||||||
- secretRef:
|
|
||||||
name: {{ $backend.kubeSecretRef | default (include "infisical.backend.fullname" .) }}
|
|
||||||
{{- end }}
|
|
||||||
---
|
|
||||||
{{- if .Values.secretScanningGitApp.enabled }}
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: {{ include "infisical.secretScanningGitApp.fullname" . }}
|
|
||||||
labels:
|
|
||||||
{{- include "infisical.secretScanningGitApp.labels" . | nindent 4 }}
|
|
||||||
{{- with $secretScanningGitApp.service.annotations }}
|
|
||||||
annotations:
|
|
||||||
{{- toYaml . | nindent 4 }}
|
|
||||||
{{- end }}
|
|
||||||
spec:
|
|
||||||
type: {{ $secretScanningGitApp.service.type }}
|
|
||||||
selector:
|
|
||||||
{{- include "infisical.secretScanningGitApp.matchLabels" . | nindent 8 }}
|
|
||||||
ports:
|
|
||||||
- protocol: TCP
|
|
||||||
port: 3001
|
|
||||||
targetPort: 3000 # container port
|
|
||||||
{{- if eq $secretScanningGitApp.service.type "NodePort" }}
|
|
||||||
nodePort: {{ $secretScanningGitApp.service.nodePort }}
|
|
||||||
{{- end }}
|
|
||||||
{{- end }}
|
|
||||||
@@ -13,20 +13,6 @@ server {
|
|||||||
|
|
||||||
proxy_cookie_path / "/; secure; HttpOnly; SameSite=strict";
|
proxy_cookie_path / "/; secure; HttpOnly; SameSite=strict";
|
||||||
}
|
}
|
||||||
|
|
||||||
location /git-app-api {
|
|
||||||
proxy_set_header X-Real-RIP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
|
|
||||||
proxy_set_header Host $http_host;
|
|
||||||
proxy_set_header X-NginX-Proxy true;
|
|
||||||
|
|
||||||
proxy_pass http://git-app:3005/;
|
|
||||||
proxy_redirect off;
|
|
||||||
# proxy_redirect http://localhost:8080/ http://frontend.example.com/;
|
|
||||||
|
|
||||||
proxy_cookie_path / "/; secure; HttpOnly; SameSite=strict";
|
|
||||||
}
|
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
include /etc/nginx/mime.types;
|
include /etc/nginx/mime.types;
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ type SecretMatch = {
|
|||||||
Fingerprint: string;
|
Fingerprint: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export = async (app: Probot) => {
|
export const GithubSecretScanningApp = async (app: Probot) => {
|
||||||
// connect to DB
|
// connect to DB
|
||||||
initDatabase()
|
initDatabase()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user