Merge remote-tracking branch 'origin' into email-case-sensitive

This commit is contained in:
Tuan Dang
2023-08-23 17:50:46 +07:00
73 changed files with 5642 additions and 649 deletions
+3
View File
@@ -25,6 +25,9 @@ JWT_PROVIDER_AUTH_LIFETIME=
# Required # Required
MONGO_URL=mongodb://root:example@mongo:27017/?authSource=admin MONGO_URL=mongodb://root:example@mongo:27017/?authSource=admin
# Redis
REDIS_URL=redis://redis:6379
# Optional credentials for MongoDB container instance and Mongo-Express # Optional credentials for MongoDB container instance and Mongo-Express
MONGO_USERNAME=root MONGO_USERNAME=root
MONGO_PASSWORD=example MONGO_PASSWORD=example
-3
View File
@@ -1,3 +0,0 @@
{
"workbench.editor.wrapTabs": true
}
+4245 -44
View File
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -50,7 +50,7 @@
"typescript": "^4.9.3", "typescript": "^4.9.3",
"utility-types": "^3.10.0", "utility-types": "^3.10.0",
"winston": "^3.8.2", "winston": "^3.8.2",
"winston-loki": "^6.0.6" "winston-loki": "^6.0.7"
}, },
"name": "infisical-api", "name": "infisical-api",
"version": "1.0.0", "version": "1.0.0",
@@ -84,6 +84,7 @@
"@posthog/plugin-scaffold": "^1.3.4", "@posthog/plugin-scaffold": "^1.3.4",
"@types/bcrypt": "^5.0.0", "@types/bcrypt": "^5.0.0",
"@types/bcryptjs": "^2.4.2", "@types/bcryptjs": "^2.4.2",
"@types/bull": "^4.10.0",
"@types/cookie-parser": "^1.4.3", "@types/cookie-parser": "^1.4.3",
"@types/cors": "^2.8.12", "@types/cors": "^2.8.12",
"@types/express": "^4.17.14", "@types/express": "^4.17.14",
+2
View File
@@ -68,6 +68,8 @@ export const getSecretScanningWebhookSecret = async () => (await client.getSecre
export const getSecretScanningGitAppId = async () => (await client.getSecret("SECRET_SCANNING_GIT_APP_ID")).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 getSecretScanningPrivateKey = async () => (await client.getSecret("SECRET_SCANNING_PRIVATE_KEY")).secretValue;
export const getRedisUrl = async () => (await client.getSecret("REDIS_URL")).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;
@@ -8,6 +8,7 @@ import { getFolderByPath } from "../../services/FolderService";
import { BadRequestError } from "../../utils/errors"; import { BadRequestError } from "../../utils/errors";
import { EEAuditLogService } from "../../ee/services"; import { EEAuditLogService } from "../../ee/services";
import { EventType } from "../../ee/models"; import { EventType } from "../../ee/models";
import { syncSecretsToActiveIntegrationsQueue } from "../../queues/integrations/syncSecretsToThirdPartyServices";
/** /**
* Create/initialize an (empty) integration for integration authorization * Create/initialize an (empty) integration for integration authorization
@@ -218,3 +219,15 @@ export const deleteIntegration = async (req: Request, res: Response) => {
integration integration
}); });
}; };
// Will trigger sync for all integrations within the given env and workspace id
export const manualSync = async (req: Request, res: Response) => {
const { workspaceId, environment } = req.body;
syncSecretsToActiveIntegrationsQueue({
workspaceId,
environment
})
res.status(200).send()
};
@@ -85,6 +85,43 @@ export const createWorkspaceEnvironment = async (
}); });
}; };
/**
* Swaps the ordering of two environments in the database. This is purely for aesthetic purposes.
* @param req
* @param res
* @returns
*/
export const reorderWorkspaceEnvironments = async (
req: Request,
res: Response
) => {
const { workspaceId } = req.params;
const { environmentSlug, environmentName, otherEnvironmentSlug, otherEnvironmentName } = req.body;
// atomic update the env to avoid conflict
const workspace = await Workspace.findById(workspaceId).exec();
if (!workspace) {
throw BadRequestError({message: "Couldn't load workspace"});
}
const environmentIndex = workspace.environments.findIndex((env) => env.name === environmentName && env.slug === environmentSlug)
const otherEnvironmentIndex = workspace.environments.findIndex((env) => env.name === otherEnvironmentName && env.slug === otherEnvironmentSlug)
if (environmentIndex === -1 || otherEnvironmentIndex === -1) {
throw BadRequestError({message: "environment or otherEnvironment couldn't be found"})
}
// swap the order of the environments
[workspace.environments[environmentIndex], workspace.environments[otherEnvironmentIndex]] = [workspace.environments[otherEnvironmentIndex], workspace.environments[environmentIndex]]
await workspace.save()
return res.status(200).send({
message: "Successfully reordered environments",
workspace: workspaceId,
});
};
/** /**
* Rename workspace environment with new name and slug of a workspace with [workspaceId] * Rename workspace environment with new name and slug of a workspace with [workspaceId]
* Old slug [oldEnvironmentSlug] must be provided * Old slug [oldEnvironmentSlug] must be provided
@@ -956,7 +956,7 @@ export const getSecrets = async (req: Request, res: Response) => {
type: EventType.GET_SECRETS, type: EventType.GET_SECRETS,
metadata: { metadata: {
environment, environment,
secretPath: secretPath as string, secretPath: (secretPath as string) ?? "/",
numberOfSecrets: secrets.length numberOfSecrets: secrets.length
} }
}, },
@@ -57,7 +57,6 @@ export const login1 = async (req: Request, res: Response) => {
if (!user.authMethods.includes(AuthMethod.EMAIL)) { if (!user.authMethods.includes(AuthMethod.EMAIL)) {
await validateProviderAuthToken({ await validateProviderAuthToken({
email, email,
user,
providerAuthToken, providerAuthToken,
}); });
} }
@@ -109,7 +108,6 @@ export const login2 = async (req: Request, res: Response) => {
if (!user.authMethods.includes(AuthMethod.EMAIL)) { if (!user.authMethods.includes(AuthMethod.EMAIL)) {
await validateProviderAuthToken({ await validateProviderAuthToken({
email, email,
user,
providerAuthToken, providerAuthToken,
}) })
} }
+2 -11
View File
@@ -71,8 +71,7 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
if (providerAuthToken) { if (providerAuthToken) {
await validateProviderAuthToken({ await validateProviderAuthToken({
email, email,
providerAuthToken, providerAuthToken
user,
}); });
} else { } else {
const [AUTH_TOKEN_TYPE, AUTH_TOKEN_VALUE] = <[string, string]>req.headers["authorization"]?.split(" ", 2) ?? [null, null] const [AUTH_TOKEN_TYPE, AUTH_TOKEN_VALUE] = <[string, string]>req.headers["authorization"]?.split(" ", 2) ?? [null, null]
@@ -117,15 +116,7 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
if (!user) if (!user)
throw new Error("Failed to complete account for non-existent user"); // ensure user is non-null throw new Error("Failed to complete account for non-existent user"); // ensure user is non-null
const hasSamlEnabled = user.authMethods const hasSamlEnabled = user.authMethods.some((authMethod: AuthMethod) => [AuthMethod.OKTA_SAML, AuthMethod.AZURE_SAML, AuthMethod.JUMPCLOUD_SAML].includes(authMethod));
.some(
(authMethod: AuthMethod) =>
[
AuthMethod.OKTA_SAML,
AuthMethod.AZURE_SAML,
AuthMethod.JUMPCLOUD_SAML
].includes(authMethod)
);
if (!hasSamlEnabled) { // TODO: modify this part if (!hasSamlEnabled) { // TODO: modify this part
// initialize default organization and workspace // initialize default organization and workspace
@@ -1,58 +1,29 @@
import { Probot } from "probot"; 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 from "../../models/gitRisks"; import GitRisks from "../../models/gitRisks";
import GitAppOrganizationInstallation from "../../models/gitAppOrganizationInstallation"; import GitAppOrganizationInstallation from "../../models/gitAppOrganizationInstallation";
import MembershipOrg from "../../../models/membershipOrg"; import { scanGithubPushEventForSecretLeaks } from "../../../queues/secret-scanning/githubScanPushEvent";
import { ADMIN, OWNER } from "../../../variables";
import User from "../../../models/user";
import { sendMail } from "../../../helpers";
import TelemetryService from "../../../services/TelemetryService";
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) => { export default async (app: Probot) => {
app.on("installation.deleted", async (context) => { app.on("installation.deleted", async (context) => {
const { payload } = context; const { payload } = context;
const { installation, repositories } = payload; const { installation, repositories } = payload;
if (installation.repository_selection == "all") { if (repositories) {
await GitRisks.deleteMany({ installationId: installation.id }) for (const repository of repositories) {
await GitAppOrganizationInstallation.deleteOne({ installationId: installation.id }) await GitRisks.deleteMany({ repositoryId: repository.id })
} else {
if (repositories) {
for (const repository of repositories) {
await GitRisks.deleteMany({ repositoryId: repository.id })
}
} }
await GitAppOrganizationInstallation.deleteOne({ installationId: installation.id })
} }
}) })
app.on("installation", async (context) => {
const { payload } = context;
payload.repositories
const { installation, repositories } = payload;
// TODO: start full repo scans
})
app.on("push", async (context) => { app.on("push", async (context) => {
const { payload } = context; const { payload } = context;
const { commits, repository, installation, pusher } = payload; const { commits, repository, installation, pusher } = payload;
const [owner, repo] = repository.full_name.split("/");
if (!commits || !repository || !installation || !pusher) { if (!commits || !repository || !installation || !pusher) {
return return
@@ -63,188 +34,12 @@ export default async (app: Probot) => {
return return
} }
const allFindingsByFingerprint: { [key: string]: SecretMatch; } = {} scanGithubPushEventForSecretLeaks({
commits: commits,
for (const commit of commits) { pusher: { name: pusher.name, email: pusher.email },
for (const filepath of [...commit.added, ...commit.modified]) { repository: { fullName: repository.full_name, id: repository.id },
try { organizationId: installationLinkToOrgExists.organizationId,
const fileContentsResponse = await context.octokit.repos.getContent({ installationId: installation.id
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
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()
}
// 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)
const usersToNotify = pusher?.email ? [pusher.email, ...adminOrOwnerEmails] : [...adminOrOwnerEmails]
if (Object.keys(allFindingsByFingerprint).length) {
await sendMail({
template: "secretLeakIncident.handlebars",
subjectLine: `Incident alert: leaked secrets found in Github repository ${repository.full_name}`,
recipients: usersToNotify,
substitutions: {
numberOfSecrets: Object.keys(allFindingsByFingerprint).length,
pusher_email: pusher.email,
pusher_name: pusher.name
}
});
}
const postHogClient = await TelemetryService.getPostHogClient();
if (postHogClient) {
postHogClient.capture({
event: "cloud secret scan",
distinctId: pusher.email,
properties: {
numberOfCommitsScanned: commits.length,
numberOfRisksFound: Object.keys(allFindingsByFingerprint).length,
}
});
}
}); });
}; };
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;
}
@@ -0,0 +1,125 @@
import { exec } from "child_process";
import { mkdir, readFile, rm, writeFile } from "fs";
import { tmpdir } from "os";
import { join } from "path"
import { SecretMatch } from "./types";
import { Octokit } from "@octokit/rest";
export 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);
}
}
export 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);
}
});
});
}
export function writeTextToFile(filePath: string, content: string): Promise<void> {
return new Promise((resolve, reject) => {
writeFile(filePath, content, (err) => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
}
export 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();
}
});
});
}
export function readFindingsFile(filePath: string): Promise<string> {
return new Promise((resolve, reject) => {
readFile(filePath, "utf8", (err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
}
export function deleteTempFolder(folderPath: string): Promise<void> {
return new Promise((resolve, reject) => {
rm(folderPath, { recursive: true }, (err) => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
}
export 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;
}
export async function getCommits(octokit: Octokit, owner: string, repo: string) {
let commits: { sha: string }[] = [];
let page = 1;
while (true) {
const response = await octokit.repos.listCommits({
owner,
repo,
per_page: 100,
page,
});
commits = commits.concat(response.data);
if (response.data.length == 0) break;
page++;
}
return commits;
}
export async function getFilesFromCommit(octokit: any, owner: string, repo: string, sha: string) {
const response = await octokit.repos.getCommit({
owner,
repo,
ref: sha,
});
}
@@ -0,0 +1,21 @@
export 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
};
+1 -6
View File
@@ -392,11 +392,9 @@ export const createToken = ({
export const validateProviderAuthToken = async ({ export const validateProviderAuthToken = async ({
email, email,
user,
providerAuthToken, providerAuthToken,
}: { }: {
email: string; email: string;
user: IUser,
providerAuthToken?: string; providerAuthToken?: string;
}) => { }) => {
if (!providerAuthToken) { if (!providerAuthToken) {
@@ -407,10 +405,7 @@ export const validateProviderAuthToken = async ({
jwt.verify(providerAuthToken, await getJwtProviderAuthSecret()) jwt.verify(providerAuthToken, await getJwtProviderAuthSecret())
); );
if ( if (decodedToken.email !== email) {
!user.authMethods.includes(decodedToken.authMethod) ||
decodedToken.email !== email
) {
throw new Error("Invalid authentication credentials.") throw new Error("Invalid authentication credentials.")
} }
} }
+1 -1
View File
@@ -32,7 +32,7 @@ export const handleEventHelper = async ({ event }: { event: Event }) => {
switch (event.name) { switch (event.name) {
case EVENT_PUSH_SECRETS: case EVENT_PUSH_SECRETS:
if (bot) { if (bot) {
await IntegrationService.syncIntegrations({ IntegrationService.syncIntegrations({
workspaceId, workspaceId,
environment environment
}); });
+3 -66
View File
@@ -1,6 +1,6 @@
import { Types } from "mongoose"; import { Types } from "mongoose";
import { Bot, Integration, IntegrationAuth } from "../models"; import { Bot, IntegrationAuth } from "../models";
import { exchangeCode, exchangeRefresh, syncSecrets } from "../integrations"; import { exchangeCode, exchangeRefresh } from "../integrations";
import { BotService } from "../services"; import { BotService } from "../services";
import { import {
ALGORITHM_AES_256_GCM, ALGORITHM_AES_256_GCM,
@@ -9,7 +9,7 @@ import {
INTEGRATION_VERCEL INTEGRATION_VERCEL
} from "../variables"; } from "../variables";
import { UnauthorizedRequestError } from "../utils/errors"; import { UnauthorizedRequestError } from "../utils/errors";
import * as Sentry from "@sentry/node"; import { syncSecretsToActiveIntegrationsQueue } from "../queues/integrations/syncSecretsToThirdPartyServices"
interface Update { interface Update {
workspace: string; workspace: string;
@@ -102,69 +102,6 @@ export const handleOAuthExchangeHelper = async ({
return integrationAuth; return integrationAuth;
}; };
/**
* Sync/push environment variables in workspace with id [workspaceId] to
* all active integrations for that workspace
* @param {Object} obj
* @param {Object} obj.workspaceId - id of workspace
*/
export const syncIntegrationsHelper = async ({
workspaceId,
environment
}: {
workspaceId: Types.ObjectId;
environment?: string;
}) => {
try {
const integrations = await Integration.find({
workspace: workspaceId,
...(environment
? {
environment
}
: {}),
isActive: true,
app: { $ne: null }
});
// for each workspace integration, sync/push secrets
// to that integration
for await (const integration of integrations) {
// get workspace, environment (shared) secrets
const secrets = await BotService.getSecrets({
workspaceId: integration.workspace,
environment: integration.environment,
secretPath: integration.secretPath
});
const integrationAuth = await IntegrationAuth.findById(integration.integrationAuth);
if (!integrationAuth) throw new Error("Failed to find integration auth");
// get integration auth access token
const access = await getIntegrationAuthAccessHelper({
integrationAuthId: integration.integrationAuth
});
// sync secrets to integration
await syncSecrets({
integration,
integrationAuth,
secrets,
accessId: access.accessId === undefined ? null : access.accessId,
accessToken: access.accessToken
});
}
} catch (err) {
Sentry.captureException(err);
// eslint-disable-next-line
console.log(
`syncIntegrationsHelper: failed with [workspaceId=${workspaceId}] [environment=${environment}]`,
err
); // eslint-disable-line no-use-before-define
throw err;
}
};
/** /**
* Return decrypted refresh token using the bot's copy * Return decrypted refresh token using the bot's copy
+9 -8
View File
@@ -11,7 +11,7 @@ import {
IServiceTokenData, IServiceTokenData,
Secret, Secret,
SecretBlindIndexData, SecretBlindIndexData,
ServiceTokenData, ServiceTokenData
} from "../models"; } from "../models";
import { EventType, SecretVersion } from "../ee/models"; import { EventType, SecretVersion } from "../ee/models";
import { import {
@@ -464,7 +464,7 @@ export const createSecretHelper = async ({
const postHogClient = await TelemetryService.getPostHogClient(); const postHogClient = await TelemetryService.getPostHogClient();
if (postHogClient && (metadata?.source !== "signup")) { if (postHogClient && metadata?.source !== "signup") {
postHogClient.capture({ postHogClient.capture({
event: "secrets added", event: "secrets added",
distinctId: await TelemetryService.getDistinctId({ distinctId: await TelemetryService.getDistinctId({
@@ -659,7 +659,7 @@ export const getSecretHelper = async ({
ipAddress: authData.ipAddress ipAddress: authData.ipAddress
})); }));
await EEAuditLogService.createAuditLog( await EEAuditLogService.createAuditLog(
authData, authData,
{ {
type: EventType.GET_SECRET, type: EventType.GET_SECRET,
@@ -825,7 +825,7 @@ export const updateSecretHelper = async ({
ipAddress: authData.ipAddress ipAddress: authData.ipAddress
})); }));
await EEAuditLogService.createAuditLog( await EEAuditLogService.createAuditLog(
authData, authData,
{ {
type: EventType.UPDATE_SECRET, type: EventType.UPDATE_SECRET,
@@ -908,14 +908,14 @@ export const deleteSecretHelper = async ({
if (type === SECRET_SHARED) { if (type === SECRET_SHARED) {
secrets = await Secret.find({ secrets = await Secret.find({
secretBlindIndex, secretBlindIndex,
workspaceId: new Types.ObjectId(workspaceId), workspace: new Types.ObjectId(workspaceId),
environment, environment,
folder: folderId folder: folderId
}).lean(); }).lean();
secret = await Secret.findOneAndDelete({ secret = await Secret.findOneAndDelete({
secretBlindIndex, secretBlindIndex,
workspaceId: new Types.ObjectId(workspaceId), workspace: new Types.ObjectId(workspaceId),
environment, environment,
type, type,
folder: folderId folder: folderId
@@ -931,7 +931,7 @@ export const deleteSecretHelper = async ({
secret = await Secret.findOneAndDelete({ secret = await Secret.findOneAndDelete({
secretBlindIndex, secretBlindIndex,
folder: folderId, folder: folderId,
workspaceId: new Types.ObjectId(workspaceId), workspace: new Types.ObjectId(workspaceId),
environment, environment,
type, type,
...getAuthDataPayloadUserObj(authData) ...getAuthDataPayloadUserObj(authData)
@@ -1088,7 +1088,8 @@ const recursivelyExpandSecret = async (
let interpolatedValue = interpolatedSec[key]; let interpolatedValue = interpolatedSec[key];
if (!interpolatedValue) { if (!interpolatedValue) {
throw new Error(`Couldn't find referenced value - ${key}`); console.error(`Couldn't find referenced value - ${key}`);
return "";
} }
const refs = interpolatedValue.match(INTERPOLATION_SYNTAX_REG); const refs = interpolatedValue.match(INTERPOLATION_SYNTAX_REG);
+5 -1
View File
@@ -6,7 +6,7 @@ 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 } from "./services";
import { EELicenseService, GithubSecretScanningService} from "./ee/services"; import { EELicenseService, GithubSecretScanningService } 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");
@@ -72,6 +72,8 @@ import { RouteNotFoundError } from "./utils/errors";
import { requestErrorHandler } from "./middleware/requestErrorHandler"; import { requestErrorHandler } from "./middleware/requestErrorHandler";
import { getNodeEnv, getPort, getSecretScanningGitAppId, getSecretScanningPrivateKey, getSecretScanningWebhookProxy, getSecretScanningWebhookSecret, getSiteURL } from "./config"; import { getNodeEnv, getPort, getSecretScanningGitAppId, getSecretScanningPrivateKey, getSecretScanningWebhookProxy, getSecretScanningWebhookSecret, getSiteURL } from "./config";
import { setup } from "./utils/setup"; import { setup } from "./utils/setup";
import { syncSecretsToThirdPartyServices } from "./queues/integrations/syncSecretsToThirdPartyServices";
import { githubPushEventSecretScan } from "./queues/secret-scanning/githubScanPushEvent";
const SmeeClient = require('smee-client') // eslint-disable-line const SmeeClient = require('smee-client') // eslint-disable-line
const main = async () => { const main = async () => {
@@ -205,6 +207,8 @@ const main = async () => {
server.on("close", async () => { server.on("close", async () => {
await DatabaseService.closeDatabase(); await DatabaseService.closeDatabase();
syncSecretsToThirdPartyServices.close()
githubPushEventSecretScan.close()
}); });
return server; return server;
-2
View File
@@ -2,7 +2,6 @@ import { exchangeCode } from "./exchange";
import { exchangeRefresh } from "./refresh"; import { exchangeRefresh } from "./refresh";
import { getApps } from "./apps"; import { getApps } from "./apps";
import { getTeams } from "./teams"; import { getTeams } from "./teams";
import { syncSecrets } from "./sync";
import { revokeAccess } from "./revoke"; import { revokeAccess } from "./revoke";
export { export {
@@ -10,6 +9,5 @@ export {
exchangeRefresh, exchangeRefresh,
getApps, getApps,
getTeams, getTeams,
syncSecrets,
revokeAccess, revokeAccess,
} }
@@ -0,0 +1,76 @@
import Queue, { Job } from "bull";
import Integration from "../../models/integration";
import IntegrationAuth from "../../models/integrationAuth";
import { BotService } from "../../services";
import { getIntegrationAuthAccessHelper } from "../../helpers";
import { syncSecrets } from "../../integrations/sync"
type TSyncSecretsToThirdPartyServices = {
workspaceId: string
environment?: string
}
export const syncSecretsToThirdPartyServices = new Queue("sync-secrets-to-third-party-services", process.env.REDIS_URL as string);
syncSecretsToThirdPartyServices.process(async (job: Job) => {
const { workspaceId, environment }: TSyncSecretsToThirdPartyServices = job.data
const integrations = await Integration.find({
workspace: workspaceId,
...(environment
? {
environment
}
: {}),
isActive: true,
app: { $ne: null }
});
// for each workspace integration, sync/push secrets
// to that integration
for (const integration of integrations) {
// get workspace, environment (shared) secrets
const secrets = await BotService.getSecrets({
workspaceId: integration.workspace,
environment: integration.environment,
secretPath: integration.secretPath
});
const integrationAuth = await IntegrationAuth.findById(integration.integrationAuth);
if (!integrationAuth) throw new Error("Failed to find integration auth");
// get integration auth access token
const access = await getIntegrationAuthAccessHelper({
integrationAuthId: integration.integrationAuth
});
// sync secrets to integration
await syncSecrets({
integration,
integrationAuth,
secrets,
accessId: access.accessId === undefined ? null : access.accessId,
accessToken: access.accessToken
});
}
})
syncSecretsToThirdPartyServices.on("error", (error) => {
console.log("QUEUE ERROR:", error) // eslint-disable-line
})
export const syncSecretsToActiveIntegrationsQueue = (jobDetails: TSyncSecretsToThirdPartyServices) => {
syncSecretsToThirdPartyServices.add(jobDetails, {
attempts: 5,
backoff: {
type: "exponential",
delay: 3000
},
removeOnComplete: true,
removeOnFail: {
count: 20 // keep the most recent 20 jobs
}
})
}
@@ -0,0 +1,201 @@
// import Queue, { Job } from "bull";
// import { ProbotOctokit } from "probot"
// import { Commit, Committer, Repository } from "@octokit/webhooks-types";
// import TelemetryService from "../../services/TelemetryService";
// import { sendMail } from "../../helpers";
// import GitRisks from "../../ee/models/gitRisks";
// import { MembershipOrg, User } from "../../models";
// import { OWNER, ADMIN } from "../../variables";
// import { convertKeysToLowercase, getFilesFromCommit, scanContentAndGetFindings } from "../../ee/services/GithubSecretScanning/helper";
// import { getSecretScanningGitAppId, getSecretScanningPrivateKey } from "../../config";
// const githubFullRepositoryScan = new Queue('github-historical-secret-scanning', 'redis://redis:6379');
// type TScanFullRepositoryDetails = {
// organizationId: string,
// repositories: {
// id: number;
// node_id: string;
// name: string;
// full_name: string;
// private: boolean;
// }[] | undefined
// installationId: number
// }
// 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
// };
// type Helllo = {
// url: string;
// sha: string;
// node_id: string;
// html_url: string;
// comments_url: string;
// commit: {
// url: string;
// author: {
// name?: string | undefined;
// email?: string | undefined;
// date?: string | undefined;
// } | null;
// verification?: {
// } | undefined;
// };
// files?: {}[] | undefined;
// }[]
// githubFullRepositoryScan.process(async (job: Job, done: Queue.DoneCallback) => {
// const { organizationId, repositories, installationId }: TScanFullRepositoryDetails = job.data
// const repositoryFullNamesList = repositories ? repositories.map(repoDetails => repoDetails.full_name) : []
// const octokit = new ProbotOctokit({
// auth: {
// appId: await getSecretScanningGitAppId(),
// privateKey: await getSecretScanningPrivateKey(),
// installationId: installationId
// },
// });
// for (const repositoryFullName of repositoryFullNamesList) {
// const [owner, repo] = repositoryFullName.split("/");
// let page = 1;
// while (true) {
// // octokit.repos.getco
// const { data } = await octokit.repos.listCommits({
// owner,
// repo,
// per_page: 100,
// page
// });
// await getFilesFromCommit(octokit, owner, repo, "646b386605177ed0a2cc0a596eeee0cf57666342")
// page++;
// }
// }
// done()
// // const allFindingsByFingerprint: { [key: string]: SecretMatch; } = {}
// // for (const commit of commits) {
// // for (const filepath of [...commit.added, ...commit.modified]) {
// // try {
// // const fileContentsResponse = await 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) {
// // done(new Error(`gitHubHistoricalScanning.process: unable to fetch content for [filepath=${filepath}] because [error=${error}]`), null)
// // }
// // }
// // }
// // // change to update
// // for (const key in allFindingsByFingerprint) {
// // await GitRisks.findOneAndUpdate({ fingerprint: allFindingsByFingerprint[key].Fingerprint },
// // {
// // ...convertKeysToLowercase(allFindingsByFingerprint[key]),
// // installationId: installationId,
// // organization: organizationId,
// // repositoryFullName: repository.fullName,
// // repositoryId: repository.id
// // }, {
// // upsert: true
// // }).lean()
// // }
// // // get emails of admins
// // const adminsOfWork = await MembershipOrg.find({
// // organization: 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)
// // const usersToNotify = pusher?.email ? [pusher.email, ...adminOrOwnerEmails] : [...adminOrOwnerEmails]
// // if (Object.keys(allFindingsByFingerprint).length) {
// // await sendMail({
// // template: "secretLeakIncident.handlebars",
// // subjectLine: `Incident alert: leaked secrets found in Github repository ${repository.fullName}`,
// // recipients: usersToNotify,
// // substitutions: {
// // numberOfSecrets: Object.keys(allFindingsByFingerprint).length,
// // pusher_email: pusher.email,
// // pusher_name: pusher.name
// // }
// // });
// // }
// // const postHogClient = await TelemetryService.getPostHogClient();
// // if (postHogClient) {
// // postHogClient.capture({
// // event: "cloud secret scan",
// // distinctId: pusher.email,
// // properties: {
// // numberOfCommitsScanned: commits.length,
// // numberOfRisksFound: Object.keys(allFindingsByFingerprint).length,
// // }
// // });
// // }
// // done(null, allFindingsByFingerprint)
// })
// export const scanGithubFullRepositoryForSecretLeaks = (scanFullRepositoryDetails: TScanFullRepositoryDetails) => {
// console.log("full repo scan started")
// githubFullRepositoryScan.add(scanFullRepositoryDetails)
// }
@@ -0,0 +1,148 @@
import Queue, { Job } from "bull";
import { ProbotOctokit } from "probot"
import { Commit, Committer, Repository } from "@octokit/webhooks-types";
import TelemetryService from "../../services/TelemetryService";
import { sendMail } from "../../helpers";
import GitRisks from "../../ee/models/gitRisks";
import { MembershipOrg, User } from "../../models";
import { OWNER, ADMIN } from "../../variables";
import { convertKeysToLowercase, scanContentAndGetFindings } from "../../ee/services/GithubSecretScanning/helper";
import { getSecretScanningGitAppId, getSecretScanningPrivateKey } from "../../config";
import { SecretMatch } from "../../ee/services/GithubSecretScanning/types";
export const githubPushEventSecretScan = new Queue('github-push-event-secret-scanning', 'redis://redis:6379');
type TScanPushEventQueueDetails = {
organizationId: string,
commits: Commit[]
pusher: {
name: string,
email: string | null
},
repository: {
id: number,
fullName: string,
},
installationId: number
}
githubPushEventSecretScan.process(async (job: Job, done: Queue.DoneCallback) => {
const { organizationId, commits, pusher, repository, installationId }: TScanPushEventQueueDetails = job.data
const [owner, repo] = repository.fullName.split("/");
const octokit = new ProbotOctokit({
auth: {
appId: await getSecretScanningGitAppId(),
privateKey: await getSecretScanningPrivateKey(),
installationId: installationId
},
});
const allFindingsByFingerprint: { [key: string]: SecretMatch; } = {}
for (const commit of commits) {
for (const filepath of [...commit.added, ...commit.modified]) {
try {
const fileContentsResponse = await 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) {
done(new Error(`gitHubHistoricalScanning.process: unable to fetch content for [filepath=${filepath}] because [error=${error}]`), null)
}
}
}
// change to update
for (const key in allFindingsByFingerprint) {
await GitRisks.findOneAndUpdate({ fingerprint: allFindingsByFingerprint[key].Fingerprint },
{
...convertKeysToLowercase(allFindingsByFingerprint[key]),
installationId: installationId,
organization: organizationId,
repositoryFullName: repository.fullName,
repositoryId: repository.id
}, {
upsert: true
}).lean()
}
// get emails of admins
const adminsOfWork = await MembershipOrg.find({
organization: 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)
const usersToNotify = pusher?.email ? [pusher.email, ...adminOrOwnerEmails] : [...adminOrOwnerEmails]
if (Object.keys(allFindingsByFingerprint).length) {
await sendMail({
template: "secretLeakIncident.handlebars",
subjectLine: `Incident alert: leaked secrets found in Github repository ${repository.fullName}`,
recipients: usersToNotify,
substitutions: {
numberOfSecrets: Object.keys(allFindingsByFingerprint).length,
pusher_email: pusher.email,
pusher_name: pusher.name
}
});
}
const postHogClient = await TelemetryService.getPostHogClient();
if (postHogClient) {
postHogClient.capture({
event: "cloud secret scan",
distinctId: pusher.email,
properties: {
numberOfCommitsScanned: commits.length,
numberOfRisksFound: Object.keys(allFindingsByFingerprint).length,
}
});
}
done(null, allFindingsByFingerprint)
})
export const scanGithubPushEventForSecretLeaks = (pushEventPayload: TScanPushEventQueueDetails) => {
githubPushEventSecretScan.add(pushEventPayload, {
attempts: 3,
backoff: {
type: "exponential",
delay: 5000
},
removeOnComplete: true,
removeOnFail: {
count: 20 // keep the most recent 20 jobs
}
})
}
+12 -3
View File
@@ -1,17 +1,26 @@
import express, { Request, Response } from "express"; import express, { Request, Response } from "express";
import { getInviteOnlySignup, getSecretScanningGitAppId, getSecretScanningPrivateKey, getSecretScanningWebhookSecret, getSmtpConfigured } from "../../config"; import { getInviteOnlySignup, getRedisUrl, getSecretScanningGitAppId, getSecretScanningPrivateKey, getSecretScanningWebhookSecret, getSmtpConfigured } from "../../config";
const router = express.Router(); const router = express.Router();
router.get( router.get(
"/status", "/status",
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const gitAppId = await getSecretScanningGitAppId()
const gitSecretScanningWebhookSecret = await getSecretScanningWebhookSecret()
const gitSecretScanningPrivateKey = await getSecretScanningPrivateKey()
let secretScanningConfigured = false
if (gitAppId && gitSecretScanningPrivateKey && gitSecretScanningWebhookSecret) {
secretScanningConfigured = true
}
res.status(200).json({ res.status(200).json({
date: new Date(), date: new Date(),
message: "Ok", message: "Ok",
emailConfigured: await getSmtpConfigured(), emailConfigured: await getSmtpConfigured(),
secretScanningConfigured: await getSecretScanningGitAppId() && await getSecretScanningWebhookSecret() && await getSecretScanningPrivateKey(), inviteOnlySignup: await getInviteOnlySignup(),
inviteOnlySignup: await getInviteOnlySignup() redisConfigured: await getRedisUrl() !== "" && await getRedisUrl() !== undefined,
secretScanningConfigured: secretScanningConfigured,
}) })
} }
); );
+16
View File
@@ -4,6 +4,7 @@ import {
requireAuth, requireAuth,
requireIntegrationAuth, requireIntegrationAuth,
requireIntegrationAuthorizationAuth, requireIntegrationAuthorizationAuth,
requireWorkspaceAuth,
validateRequest, validateRequest,
} from "../../middleware"; } from "../../middleware";
import { import {
@@ -73,4 +74,19 @@ router.delete(
integrationController.deleteIntegration integrationController.deleteIntegration
); );
router.post(
"/manual-sync",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "body",
}),
body("environment").isString().exists().trim(),
body("workspaceId").exists().trim(),
validateRequest,
integrationController.manualSync
);
export default router; export default router;
+18
View File
@@ -46,6 +46,24 @@ router.put(
environmentController.renameWorkspaceEnvironment environmentController.renameWorkspaceEnvironment
); );
router.patch(
"/:workspaceId/environments",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "params",
}),
param("workspaceId").exists().trim(),
body("environmentSlug").exists().isString().trim(),
body("environmentName").exists().isString().trim(),
body("otherEnvironmentSlug").exists().isString().trim(),
body("otherEnvironmentName").exists().isString().trim(),
validateRequest,
environmentController.reorderWorkspaceEnvironments
);
router.delete( router.delete(
"/:workspaceId/environments", "/:workspaceId/environments",
requireAuth({ requireAuth({
+1 -1
View File
@@ -1,4 +1,4 @@
import express from "express"; import express, { Request, Response } from "express";
const router = express.Router(); const router = express.Router();
import { requireAuth, requireWorkspaceAuth, validateRequest } from "../../middleware"; import { requireAuth, requireWorkspaceAuth, validateRequest } from "../../middleware";
import { body, param, query } from "express-validator"; import { body, param, query } from "express-validator";
+4 -6
View File
@@ -5,8 +5,8 @@ import {
handleOAuthExchangeHelper, handleOAuthExchangeHelper,
setIntegrationAuthAccessHelper, setIntegrationAuthAccessHelper,
setIntegrationAuthRefreshHelper, setIntegrationAuthRefreshHelper,
syncIntegrationsHelper,
} from "../helpers/integration"; } from "../helpers/integration";
import { syncSecretsToActiveIntegrationsQueue } from "../queues/integrations/syncSecretsToThirdPartyServices";
/** /**
* Class to handle integrations * Class to handle integrations
@@ -51,16 +51,14 @@ class IntegrationService {
* @param {Object} obj * @param {Object} obj
* @param {Object} obj.workspaceId - id of workspace * @param {Object} obj.workspaceId - id of workspace
*/ */
static async syncIntegrations({ static syncIntegrations({
workspaceId, workspaceId,
environment, environment,
}: { }: {
workspaceId: Types.ObjectId; workspaceId: Types.ObjectId;
environment?: string; environment?: string;
}) { }) {
return await syncIntegrationsHelper({ syncSecretsToActiveIntegrationsQueue({ workspaceId: workspaceId.toString(), environment: environment })
workspaceId,
});
} }
/** /**
@@ -70,7 +68,7 @@ class IntegrationService {
* @param {String} obj.integrationAuthId - id of integration auth * @param {String} obj.integrationAuthId - id of integration auth
* @param {String} refreshToken - decrypted refresh token * @param {String} refreshToken - decrypted refresh token
*/ */
static async getIntegrationAuthRefresh({ integrationAuthId }: { integrationAuthId: Types.ObjectId}) { static async getIntegrationAuthRefresh({ integrationAuthId }: { integrationAuthId: Types.ObjectId }) {
return await getIntegrationAuthRefreshHelper({ return await getIntegrationAuthRefreshHelper({
integrationAuthId, integrationAuthId,
}); });
+5
View File
@@ -27,6 +27,7 @@ import {
import { import {
getMongoURL, getMongoURL,
getNodeEnv, getNodeEnv,
getRedisUrl,
getSentryDSN getSentryDSN
} from "../../config"; } from "../../config";
import { initializePassport } from "../auth"; import { initializePassport } from "../auth";
@@ -42,6 +43,10 @@ import { initializePassport } from "../auth";
* - Re-encrypting data * - Re-encrypting data
*/ */
export const setup = async () => { export const setup = async () => {
if (await getRedisUrl() === undefined || await getRedisUrl() === "") {
console.error("WARNING: Redis is not yet configured. Infisical may not function as expected without it.")
}
await validateEncryptionKeysConfig(); await validateEncryptionKeysConfig();
await TelemetryService.logTelemetryMessage(); await TelemetryService.logTelemetryMessage();
+4
View File
@@ -2,6 +2,8 @@ import { Server } from "http";
import main from "../src"; import main from "../src";
import { afterAll, beforeAll, describe, expect, it } from "@jest/globals"; import { afterAll, beforeAll, describe, expect, it } from "@jest/globals";
import request from "supertest"; import request from "supertest";
import { githubPushEventSecretScan } from "../src/queues/secret-scanning/githubScanPushEvent";
import { syncSecretsToThirdPartyServices } from "../src/queues/integrations/syncSecretsToThirdPartyServices";
let server: Server; let server: Server;
@@ -11,6 +13,8 @@ beforeAll(async () => {
afterAll(async () => { afterAll(async () => {
server.close(); server.close();
githubPushEventSecretScan.close()
syncSecretsToThirdPartyServices.close()
}); });
describe("Healthcheck endpoint", () => { describe("Healthcheck endpoint", () => {
+6 -3
View File
@@ -22,7 +22,7 @@ require (
github.com/spf13/viper v1.8.1 github.com/spf13/viper v1.8.1
github.com/stretchr/testify v1.8.1 github.com/stretchr/testify v1.8.1
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d
golang.org/x/term v0.9.0 golang.org/x/term v0.11.0
) )
require ( require (
@@ -31,6 +31,7 @@ require (
github.com/chzyer/readline v1.5.1 // indirect github.com/chzyer/readline v1.5.1 // indirect
github.com/danieljoos/wincred v1.2.0 // indirect github.com/danieljoos/wincred v1.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dvsekhvalnov/jose2go v1.5.0 // indirect
github.com/fsnotify/fsnotify v1.4.9 // indirect github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/go-openapi/errors v0.20.2 // indirect github.com/go-openapi/errors v0.20.2 // indirect
github.com/go-openapi/strfmt v0.21.3 // indirect github.com/go-openapi/strfmt v0.21.3 // indirect
@@ -41,6 +42,7 @@ require (
github.com/mattn/go-colorable v0.1.9 // indirect github.com/mattn/go-colorable v0.1.9 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/mitchellh/mapstructure v1.4.1 // indirect github.com/mitchellh/mapstructure v1.4.1 // indirect
github.com/mtibben/percent v0.2.1 // indirect
github.com/muesli/mango v0.1.0 // indirect github.com/muesli/mango v0.1.0 // indirect
github.com/muesli/mango-pflag v0.1.0 // indirect github.com/muesli/mango-pflag v0.1.0 // indirect
github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0 // indirect github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0 // indirect
@@ -56,9 +58,8 @@ require (
go.mongodb.org/mongo-driver v1.10.0 // indirect go.mongodb.org/mongo-driver v1.10.0 // indirect
golang.org/x/net v0.7.0 // indirect golang.org/x/net v0.7.0 // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
golang.org/x/sys v0.9.0 // indirect golang.org/x/sys v0.11.0 // indirect
golang.org/x/text v0.7.0 // indirect golang.org/x/text v0.7.0 // indirect
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
gopkg.in/ini.v1 v1.62.0 // indirect gopkg.in/ini.v1 v1.62.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
@@ -73,3 +74,5 @@ require (
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/zalando/go-keyring v0.2.3 github.com/zalando/go-keyring v0.2.3
) )
replace github.com/zalando/go-keyring => github.com/Infisical/go-keyring v1.0.1
+10 -6
View File
@@ -39,6 +39,8 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Infisical/go-keyring v1.0.1 h1:E8XpqoT0H1G9C1kgxU+NeReXOeobmH7LbBHNpcOI380=
github.com/Infisical/go-keyring v1.0.1/go.mod h1:LWOnn/sw9FxDW/0VY+jHFAfOFEe03xmwBVSfJnBowto=
github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0= github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0=
github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
@@ -77,6 +79,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ= github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ=
github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI= github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI=
github.com/dvsekhvalnov/jose2go v1.5.0 h1:3j8ya4Z4kMCwT5nXIKFSV84YS+HdqSSO0VsTQxaLAeM=
github.com/dvsekhvalnov/jose2go v1.5.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@@ -251,6 +255,8 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJ
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs=
github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns=
github.com/muesli/ansi v0.0.0-20221106050444-61f0cd9a192a h1:jlDOeO5TU0pYlbc/y6PFguab5IjANI0Knrpg3u/ton4= github.com/muesli/ansi v0.0.0-20221106050444-61f0cd9a192a h1:jlDOeO5TU0pYlbc/y6PFguab5IjANI0Knrpg3u/ton4=
github.com/muesli/ansi v0.0.0-20221106050444-61f0cd9a192a/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= github.com/muesli/ansi v0.0.0-20221106050444-61f0cd9a192a/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
github.com/muesli/mango v0.1.0 h1:DZQK45d2gGbql1arsYA4vfg4d7I9Hfx5rX/GCmzsAvI= github.com/muesli/mango v0.1.0 h1:DZQK45d2gGbql1arsYA4vfg4d7I9Hfx5rX/GCmzsAvI=
@@ -348,8 +354,6 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/zalando/go-keyring v0.2.3 h1:v9CUu9phlABObO4LPWycf+zwMG7nlbb3t/B5wa97yms=
github.com/zalando/go-keyring v0.2.3/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk=
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
@@ -524,11 +528,11 @@ golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28= golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0=
golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo= golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-68
View File
@@ -1,68 +0,0 @@
package keyringwrapper
import (
"time"
"github.com/zalando/go-keyring"
)
const MAIN_KEYRING_SERVICE = "infisical-cli"
type TimeoutError struct {
message string
}
func (e *TimeoutError) Error() string {
return e.message
}
func Set(key, value string) error {
ch := make(chan error, 1)
go func() {
defer close(ch)
ch <- keyring.Set(MAIN_KEYRING_SERVICE, key, value)
}()
select {
case err := <-ch:
return err
case <-time.After(3 * time.Second):
return &TimeoutError{"timeout while trying to set secret in keyring"}
}
}
func Get(key string) (string, error) {
ch := make(chan struct {
val string
err error
}, 1)
go func() {
defer close(ch)
val, err := keyring.Get(MAIN_KEYRING_SERVICE, key)
ch <- struct {
val string
err error
}{val, err}
}()
select {
case res := <-ch:
return res.val, res.err
case <-time.After(3 * time.Second):
return "", &TimeoutError{"timeout while trying to get secret from keyring"}
}
}
func Delete(key string) error {
ch := make(chan error, 1)
go func() {
defer close(ch)
ch <- keyring.Delete(MAIN_KEYRING_SERVICE, key)
}()
select {
case err := <-ch:
return err
case <-time.After(3 * time.Second):
return &TimeoutError{"timeout while trying to delete secret from keyring"}
}
}
+6 -6
View File
@@ -57,14 +57,14 @@ func CallLogin1V2(httpClient *resty.Client, request GetLoginOneV2Request) (GetLo
SetResult(&loginOneV2Response). SetResult(&loginOneV2Response).
SetHeader("User-Agent", USER_AGENT). SetHeader("User-Agent", USER_AGENT).
SetBody(request). SetBody(request).
Post(fmt.Sprintf("%v/v2/auth/login1", config.INFISICAL_URL)) Post(fmt.Sprintf("%v/v3/auth/login1", config.INFISICAL_URL))
if err != nil { if err != nil {
return GetLoginOneV2Response{}, fmt.Errorf("CallLogin1V2: Unable to complete api request [err=%s]", err) return GetLoginOneV2Response{}, fmt.Errorf("CallLogin1V3: Unable to complete api request [err=%s]", err)
} }
if response.IsError() { if response.IsError() {
return GetLoginOneV2Response{}, fmt.Errorf("CallLogin1V2: Unsuccessful response: [response=%s]", response) return GetLoginOneV2Response{}, fmt.Errorf("CallLogin1V3: Unsuccessful response: [response=%s]", response)
} }
return loginOneV2Response, nil return loginOneV2Response, nil
@@ -115,7 +115,7 @@ func CallLogin2V2(httpClient *resty.Client, request GetLoginTwoV2Request) (GetLo
SetResult(&loginTwoV2Response). SetResult(&loginTwoV2Response).
SetHeader("User-Agent", USER_AGENT). SetHeader("User-Agent", USER_AGENT).
SetBody(request). SetBody(request).
Post(fmt.Sprintf("%v/v2/auth/login2", config.INFISICAL_URL)) Post(fmt.Sprintf("%v/v3/auth/login2", config.INFISICAL_URL))
cookies := response.Cookies() cookies := response.Cookies()
// Find a cookie by name // Find a cookie by name
@@ -134,11 +134,11 @@ func CallLogin2V2(httpClient *resty.Client, request GetLoginTwoV2Request) (GetLo
} }
if err != nil { if err != nil {
return GetLoginTwoV2Response{}, fmt.Errorf("CallLogin2V2: Unable to complete api request [err=%s]", err) return GetLoginTwoV2Response{}, fmt.Errorf("CallLogin2V3: Unable to complete api request [err=%s]", err)
} }
if response.IsError() { if response.IsError() {
return GetLoginTwoV2Response{}, fmt.Errorf("CallLogin2V2: Unsuccessful response: [response=%s]", response) return GetLoginTwoV2Response{}, fmt.Errorf("CallLogin2V3: Unsuccessful response: [response=%s]", response)
} }
return loginTwoV2Response, nil return loginTwoV2Response, nil
+31 -31
View File
@@ -107,7 +107,7 @@ var loginCmd = &cobra.Command{
//call browser login function //call browser login function
if !interactiveLogin { if !interactiveLogin {
fmt.Printf("\nLogging in via browser... Hit '%s' to cancel\n", QUIT_BROWSER_LOGIN) fmt.Println("Logging in via browser... To login via interactive mode run [infisical login -i]")
userCredentialsToBeStored, err = browserCliLogin() userCredentialsToBeStored, err = browserCliLogin()
if err != nil { if err != nil {
//default to cli login on error //default to cli login on error
@@ -540,7 +540,12 @@ func browserCliLogin() (models.UserCredentials, error) {
quit := make(chan bool) quit := make(chan bool)
//terminal state //terminal state
var oldState term.State oldState, err := term.GetState(int(os.Stdin.Fd()))
if err != nil {
return models.UserCredentials{}, err
}
defer restoreTerminal(oldState)
//create handler //create handler
c := cors.New(cors.Options{ c := cors.New(cors.Options{
@@ -553,29 +558,25 @@ func browserCliLogin() (models.UserCredentials, error) {
corsHandler := c.Handler(browserLoginHandler(success, failure)) corsHandler := c.Handler(browserLoginHandler(success, failure))
log.Debug().Msgf("Callback server listening on port %d", callbackPort) log.Debug().Msgf("Callback server listening on port %d", callbackPort)
go quitBrowserLogin(quit, &oldState)
go http.Serve(listener, corsHandler) go http.Serve(listener, corsHandler)
for { for {
select { select {
case loginResponse := <-success: case loginResponse := <-success:
err = closeListener(&listener) _ = closeListener(&listener)
restoreTerminal(&oldState)
return loginResponse, nil return loginResponse, nil
case err = <-failure: case <-failure:
err = closeListener(&listener) err = closeListener(&listener)
restoreTerminal(&oldState)
return models.UserCredentials{}, err return models.UserCredentials{}, err
case _ = <-timeout: case <-timeout:
err = closeListener(&listener) _ = closeListener(&listener)
restoreTerminal(&oldState)
return models.UserCredentials{}, errors.New("server timeout") return models.UserCredentials{}, errors.New("server timeout")
case _ = <-quit: case <-quit:
return models.UserCredentials{}, errors.New("quitting browser login, defaulting to cli...") return models.UserCredentials{}, errors.New("quitting browser login, defaulting to cli...")
} }
} }
} }
@@ -584,25 +585,24 @@ func restoreTerminal(oldState *term.State) {
term.Restore(int(os.Stdin.Fd()), oldState) term.Restore(int(os.Stdin.Fd()), oldState)
} }
// listens to 'q' input on terminal and // // listens to 'q' input on terminal and
// sends 'true' to 'quit' channel // // sends 'true' to 'quit' channel
func quitBrowserLogin(quit chan bool, oState *term.State) { // func quitBrowserLogin(quit chan bool, oState *term.State) {
// // oldState, err := term.MakeRaw(int(os.Stdin.Fd()))
oldState, err := term.MakeRaw(int(os.Stdin.Fd())) // if err != nil {
if err != nil { // return
return // }
} // *oState = *oldState
*oState = *oldState // defer restoreTerminal(oldState)
defer restoreTerminal(oldState) // b := make([]byte, 1)
b := make([]byte, 1) // for {
for { // _, _ = os.Stdin.Read(b)
_, _ = os.Stdin.Read(b) // if string(b) == QUIT_BROWSER_LOGIN {
if string(b) == QUIT_BROWSER_LOGIN { // quit <- true
quit <- true // break
break // }
} // }
} // }
}
func closeListener(listener *net.Listener) error { func closeListener(listener *net.Listener) error {
err := (*listener).Close() err := (*listener).Close()
+6
View File
@@ -18,6 +18,12 @@ var resetCmd = &cobra.Command{
Example: "infisical reset", Example: "infisical reset",
Args: cobra.NoArgs, Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
// delete keyring item of current logged in user
configFile, _ := util.GetConfigFile()
// delete from keyring
util.DeleteValueInKeyring(configFile.LoggedInUserEmail)
// delete config // delete config
_, pathToDir, err := util.GetFullConfigFilePath() _, pathToDir, err := util.GetFullConfigFilePath()
if err != nil { if err != nil {
+12
View File
@@ -64,11 +64,22 @@ var secretsCmd = &cobra.Command{
util.HandleError(err, "Unable to parse flag") util.HandleError(err, "Unable to parse flag")
} }
secretOverriding, err := cmd.Flags().GetBool("secret-overriding")
if err != nil {
util.HandleError(err, "Unable to parse flag")
}
secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs, SecretsPath: secretsPath, IncludeImport: includeImports}) secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs, SecretsPath: secretsPath, IncludeImport: includeImports})
if err != nil { if err != nil {
util.HandleError(err) util.HandleError(err)
} }
if secretOverriding {
secrets = util.OverrideSecrets(secrets, util.SECRET_TYPE_PERSONAL)
} else {
secrets = util.OverrideSecrets(secrets, util.SECRET_TYPE_SHARED)
}
if shouldExpandSecrets { if shouldExpandSecrets {
secrets = util.ExpandSecrets(secrets, infisicalToken) secrets = util.ExpandSecrets(secrets, infisicalToken)
} }
@@ -641,6 +652,7 @@ func init() {
secretsGetCmd.Flags().String("token", "", "Fetch secrets using the Infisical Token") secretsGetCmd.Flags().String("token", "", "Fetch secrets using the Infisical Token")
secretsCmd.AddCommand(secretsGetCmd) secretsCmd.AddCommand(secretsGetCmd)
secretsCmd.Flags().Bool("secret-overriding", true, "Prioritizes personal secrets, if any, with the same name over shared secrets")
secretsCmd.AddCommand(secretsSetCmd) secretsCmd.AddCommand(secretsSetCmd)
secretsSetCmd.Flags().String("path", "/", "get secrets within a folder path") secretsSetCmd.Flags().String("path", "/", "get secrets within a folder path")
+93
View File
@@ -0,0 +1,93 @@
/*
Copyright (c) 2023 Infisical Inc.
*/
package cmd
import (
"fmt"
"strings"
"github.com/Infisical/infisical-merge/packages/util"
"github.com/posthog/posthog-go"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
)
var AvailableVaultsAndDescriptions = []string{"auto (automatically select native vault on system)", "file (encrypted file vault)"}
var AvailableVaults = []string{"auto", "file"}
var vaultSetCmd = &cobra.Command{
Example: `infisical vault set pass`,
Use: "set [vault-name]",
Short: "Used to set the vault backend to store your login details securely at rest",
DisableFlagsInUseLine: true,
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
wantedVaultTypeName := args[0]
currentVaultBackend, err := util.GetCurrentVaultBackend()
if err != nil {
log.Error().Msgf("Unable to set vault to [%s] because of [err=%s]", wantedVaultTypeName, err)
return
}
if wantedVaultTypeName == string(currentVaultBackend) {
log.Error().Msgf("You are already on vault backend [%s]", currentVaultBackend)
return
}
if wantedVaultTypeName == "auto" || wantedVaultTypeName == "file" {
configFile, err := util.GetConfigFile()
if err != nil {
log.Error().Msgf("Unable to set vault to [%s] because of [err=%s]", wantedVaultTypeName, err)
return
}
configFile.VaultBackendType = wantedVaultTypeName // save selected vault
configFile.LoggedInUserEmail = "" // reset the logged in user to prompt them to re login
err = util.WriteConfigFile(&configFile)
if err != nil {
log.Error().Msgf("Unable to set vault to [%s] because an error occurred when saving the config file [err=%s]", wantedVaultTypeName, err)
return
}
fmt.Printf("\nSuccessfully, switched vault backend from [%s] to [%s]. Please login in again to store your login details in the new vault with [infisical login]\n", currentVaultBackend, wantedVaultTypeName)
Telemetry.CaptureEvent("cli-command:vault set", posthog.NewProperties().Set("currentVault", currentVaultBackend).Set("wantedVault", wantedVaultTypeName).Set("version", util.CLI_VERSION))
} else {
log.Error().Msgf("The requested vault type [%s] is not available on this system. Only the following vault backends are available for you system: %s", wantedVaultTypeName, strings.Join(AvailableVaults, ", "))
}
},
}
// runCmd represents the run command
var vaultCmd = &cobra.Command{
Use: "vault",
Short: "Used to manage where your Infisical login token is saved on your machine",
DisableFlagsInUseLine: true,
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
printAvailableVaultBackends()
},
}
func printAvailableVaultBackends() {
fmt.Printf("Vaults are used to securely store your login details locally. Available vaults:")
for _, backend := range AvailableVaultsAndDescriptions {
fmt.Printf("\n- %s", backend)
}
currentVaultBackend, err := util.GetCurrentVaultBackend()
if err != nil {
log.Error().Msgf("printAvailableVaultBackends: unable to print the available vault backend because of error [err=%s]", err)
}
Telemetry.CaptureEvent("cli-command:vault", posthog.NewProperties().Set("currentVault", currentVaultBackend).Set("version", util.CLI_VERSION))
fmt.Printf("\n\nYou are currently using [%s] vault to store your login credentials\n", string(currentVaultBackend))
}
func init() {
vaultCmd.AddCommand(vaultSetCmd)
rootCmd.AddCommand(vaultCmd)
}
+1
View File
@@ -12,6 +12,7 @@ type ConfigFile struct {
LoggedInUserEmail string `json:"loggedInUserEmail"` LoggedInUserEmail string `json:"loggedInUserEmail"`
LoggedInUserDomain string `json:"LoggedInUserDomain,omitempty"` LoggedInUserDomain string `json:"LoggedInUserDomain,omitempty"`
LoggedInUsers []LoggedInUser `json:"loggedInUsers,omitempty"` LoggedInUsers []LoggedInUser `json:"loggedInUsers,omitempty"`
VaultBackendType string `json:"vaultBackendType,omitempty"`
} }
type LoggedInUser struct { type LoggedInUser struct {
+1
View File
@@ -53,6 +53,7 @@ func WriteInitalConfig(userCredentials *models.UserCredentials) error {
LoggedInUserEmail: userCredentials.Email, LoggedInUserEmail: userCredentials.Email,
LoggedInUserDomain: config.INFISICAL_URL, LoggedInUserDomain: config.INFISICAL_URL,
LoggedInUsers: existingConfigFile.LoggedInUsers, LoggedInUsers: existingConfigFile.LoggedInUsers,
VaultBackendType: existingConfigFile.VaultBackendType,
} }
configFileMarshalled, err := json.Marshal(configFile) configFileMarshalled, err := json.Marshal(configFile)
+2 -3
View File
@@ -6,7 +6,6 @@ import (
"fmt" "fmt"
"strings" "strings"
keyringwrapper "github.com/Infisical/infisical-merge/internal"
"github.com/Infisical/infisical-merge/packages/api" "github.com/Infisical/infisical-merge/packages/api"
"github.com/Infisical/infisical-merge/packages/config" "github.com/Infisical/infisical-merge/packages/config"
"github.com/Infisical/infisical-merge/packages/models" "github.com/Infisical/infisical-merge/packages/models"
@@ -26,7 +25,7 @@ func StoreUserCredsInKeyRing(userCred *models.UserCredentials) error {
return fmt.Errorf("StoreUserCredsInKeyRing: something went wrong when marshalling user creds [err=%s]", err) return fmt.Errorf("StoreUserCredsInKeyRing: something went wrong when marshalling user creds [err=%s]", err)
} }
err = keyringwrapper.Set(userCred.Email, string(userCredMarshalled)) err = SetValueInKeyring(userCred.Email, string(userCredMarshalled))
if err != nil { if err != nil {
return fmt.Errorf("StoreUserCredsInKeyRing: unable to store user credentials because [err=%s]", err) return fmt.Errorf("StoreUserCredsInKeyRing: unable to store user credentials because [err=%s]", err)
} }
@@ -35,7 +34,7 @@ func StoreUserCredsInKeyRing(userCred *models.UserCredentials) error {
} }
func GetUserCredsFromKeyRing(userEmail string) (credentials models.UserCredentials, err error) { func GetUserCredsFromKeyRing(userEmail string) (credentials models.UserCredentials, err error) {
credentialsValue, err := keyringwrapper.Get(userEmail) credentialsValue, err := GetValueInKeyring(userEmail)
if err != nil { if err != nil {
if err == keyring.ErrUnsupportedPlatform { if err == keyring.ErrUnsupportedPlatform {
return models.UserCredentials{}, errors.New("your OS does not support keyring. Consider using a service token https://infisical.com/docs/documentation/platform/token") return models.UserCredentials{}, errors.New("your OS does not support keyring. Consider using a service token https://infisical.com/docs/documentation/platform/token")
+42
View File
@@ -0,0 +1,42 @@
package util
import (
"github.com/zalando/go-keyring"
)
const MAIN_KEYRING_SERVICE = "infisical-cli"
type TimeoutError struct {
message string
}
func (e *TimeoutError) Error() string {
return e.message
}
func SetValueInKeyring(key, value string) error {
currentVaultBackend, err := GetCurrentVaultBackend()
if err != nil {
PrintErrorAndExit(1, err, "Unable to get current vault. Tip: run [infisical rest] then try again")
}
return keyring.Set(currentVaultBackend, MAIN_KEYRING_SERVICE, key, value)
}
func GetValueInKeyring(key string) (string, error) {
currentVaultBackend, err := GetCurrentVaultBackend()
if err != nil {
PrintErrorAndExit(1, err, "Unable to get current vault. Tip: run [infisical rest] then try again")
}
return keyring.Get(currentVaultBackend, MAIN_KEYRING_SERVICE, key)
}
func DeleteValueInKeyring(key string) error {
currentVaultBackend, err := GetCurrentVaultBackend()
if err != nil {
return err
}
return keyring.Delete(currentVaultBackend, MAIN_KEYRING_SERVICE, key)
}
+22
View File
@@ -0,0 +1,22 @@
package util
import (
"fmt"
)
func GetCurrentVaultBackend() (string, error) {
configFile, err := GetConfigFile()
if err != nil {
return "", fmt.Errorf("getCurrentVaultBackend: unable to get config file [err=%s]", err)
}
if configFile.VaultBackendType == "" {
return "auto", nil
}
if configFile.VaultBackendType != "auto" && configFile.VaultBackendType != "file" {
return "auto", nil
}
return configFile.VaultBackendType, nil
}
+28
View File
@@ -21,6 +21,7 @@ services:
depends_on: depends_on:
- mongo - mongo
- smtp-server - smtp-server
- redis
build: build:
context: ./backend context: ./backend
dockerfile: Dockerfile dockerfile: Dockerfile
@@ -99,9 +100,36 @@ services:
networks: networks:
- infisical-dev - infisical-dev
redis:
image: redis
container_name: infisical-dev-redis
environment:
- ALLOW_EMPTY_PASSWORD=yes
ports:
- 6379:6379
volumes:
- redis_data:/data
networks:
- infisical-dev
redis-commander:
container_name: infisical-dev-redis-commander
image: rediscommander/redis-commander
restart: always
depends_on:
- redis
environment:
- REDIS_HOSTS=local:redis:6379
ports:
- "8085:8081"
networks:
- infisical-dev
volumes: volumes:
mongo-data: mongo-data:
driver: local driver: local
redis_data:
driver: local
networks: networks:
infisical-dev: infisical-dev:
+13 -13
View File
@@ -41,19 +41,17 @@ services:
networks: networks:
- infisical - infisical
# secret-scanning-git-app: redis:
# container_name: infisical-secret-scanning-git-app image: redis
# restart: unless-stopped container_name: infisical-dev-redis
# depends_on: environment:
# - backend - ALLOW_EMPTY_PASSWORD=yes
# - frontend ports:
# - mongo - 6379:6379
# ports: networks:
# - "3000:3001" - infisical
# image: infisical/staging_deployment_secret-scanning-git-app volumes:
# env_file: .env - redis_data:/data
# networks:
# - infisical
mongo: mongo:
container_name: infisical-mongo container_name: infisical-mongo
@@ -71,6 +69,8 @@ services:
volumes: volumes:
mongo-data: mongo-data:
driver: local driver: local
redis_data:
driver: local
networks: networks:
infisical: infisical:
+5 -19
View File
@@ -9,12 +9,11 @@ description: "Change the vault type in Infisical"
infisical vault infisical vault
# Example output # Example output
The following vaults are available on your system: Vaults are used to securely store your login details locally. Available vaults:
- keychain - auto (automatically select native vault on system)
- pass - file (encrypted file vault)
- file
You are currently using [keychain] vault to store your login credentials You are currently using [file] vault to store your login credentials
``` ```
</Tab> </Tab>
@@ -31,20 +30,7 @@ description: "Change the vault type in Infisical"
## Description ## Description
To ensure secure storage of your login credentials when using the CLI, Infisical stores login credentials securely in a system vault or encrypted text file with a passphrase known only by the user. To safeguard your login details when using the CLI, Infisical places them in a system vault or an encrypted text file, protected by a passphrase that only the user knows.
<Accordion title="Supported vaults">
By default, the most appropriate vault is chosen to store your login credentials.
For example, if you are on macOS, KeyChain will be automatically selected.
- [macOS Keychain](https://support.apple.com/en-au/guide/keychain-access/welcome/mac)
- [Windows Credential Manager](https://support.microsoft.com/en-au/help/4026814/windows-accessing-credential-manager)
- Secret Service ([Gnome Keyring](https://wiki.gnome.org/Projects/GnomeKeyring), [KWallet](https://kde.org/applications/system/org.kde.kwalletmanager5))
- [KWallet](https://kde.org/applications/system/org.kde.kwalletmanager5)
- [Pass](https://www.passwordstore.org/)
- [KeyCtl]()
- Encrypted file (JWT)
</Accordion>
<Tip>To avoid constantly entering your passphrase when using the `file` vault type, set the `INFISICAL_VAULT_FILE_PASSPHRASE` environment variable with your password in your shell</Tip> <Tip>To avoid constantly entering your passphrase when using the `file` vault type, set the `INFISICAL_VAULT_FILE_PASSPHRASE` environment variable with your password in your shell</Tip>
+1 -2
View File
@@ -144,13 +144,12 @@
"self-hosting/deployment-options/aws-ec2", "self-hosting/deployment-options/aws-ec2",
"self-hosting/deployment-options/docker-compose", "self-hosting/deployment-options/docker-compose",
"self-hosting/deployment-options/standalone-infisical", "self-hosting/deployment-options/standalone-infisical",
"self-hosting/deployment-options/fly.io",
"self-hosting/deployment-options/render",
"self-hosting/deployment-options/digital-ocean-marketplace" "self-hosting/deployment-options/digital-ocean-marketplace"
] ]
}, },
"self-hosting/configuration/envars", "self-hosting/configuration/envars",
"self-hosting/configuration/email", "self-hosting/configuration/email",
"self-hosting/configuration/redis",
"self-hosting/faq" "self-hosting/faq"
] ]
}, },
+9 -4
View File
@@ -49,10 +49,15 @@ Other environment variables are listed below to increase the functionality of yo
16` 16`
</ParamField> </ParamField>
<ParamField query="MONGO_URL" type="string" default="none" required> <ParamField query="MONGO_URL" type="string" default="none" required>
*TLS based connection string is not yet supported *TLS based connection string is not yet supported
</ParamField> </ParamField>
</Tab>
<ParamField query="REDIS_URL" type="string" default="none" required>
Redis connection string
</ParamField>
</Tab>
<Tab title="Email service"> <Tab title="Email service">
<Info>When email service is not configured, Infisical will have limited functionality</Info> <Info>When email service is not configured, Infisical will have limited functionality</Info>
+83
View File
@@ -0,0 +1,83 @@
---
title: "Configure Redis"
description: "Learn to configure Redis with your self hosted Infisical"
---
## Why Redis?
As the features and use case of Infisical have grown, the need for a fast and reliable in-memory data storage has become clear.
By adding Redis to Infisical, we can now support more complex workflows such as queuing system to run long running asynchronous tasks, cron jobs, and access reliable cache to speed up frequently used resources.
<Info>
Starting with Infisical version v0.31.0, Redis will be required to fully use Infisical
</Info>
### Adding Redis to your self hosted instance of Infisical
To add Redis to your self hosted instance, follow the instructions for the deployment method you used.
<Tabs>
<Tab title="Kubernetes Helm chart">
### In cluster Redis
By default, new versions of the Infisical Helm chart already comes with an in-cluster Redis instance. To deploy a in-cluster Redis instance along with your Infisical instance, update your Infisical chart then redeploy/upgrade your release.
This will spin up a Redis instance and automatically configure it with your Infisical backend.
1. Update Infisical Helm chart
```bash
helm repo update
```
2. Upgrade Infisical release
```bash
helm upgrade <infisical release name> infisical-helm-charts/infisical --values <path to your values file>
```
### External Redis
If you want to use an external Redis instance, please add a Redis connection URL under the backend environments variables and then upgrade/redeploy your Infisical instance.
1. Update your helm values file
```yaml your-values.yaml
backendEnvironmentVariables:
REDIS_URL=<your redis connection string>
```
2. Upgrade Infisical release
```bash
helm upgrade <infisical release name> infisical-helm-charts/infisical --values <path to your values file>
```
</Tab>
<Tab title="Docker compose">
### Internal Redis service
By default, new versions of the docker compose file already comes with a Redis service. To use the pre-configured Redis service, please update your docker compose file to the latest version.
1. Download the new docker compose file
```
wget -O docker-compose.yml https://raw.githubusercontent.com/Infisical/infisical/main/docker-compose.yml
```
2. Add Redis environment variable to your .env file
```.env .env
REDIS_URL=redis://redis:6379
```
3. Restart your docker compose services
</Tab>
<Tab title="Standalone Docker image">
This standalone version of Infisical does not have an internal Redis service. To configure Redis with your Infisical instance, you must connect to a external Redis service by setting the connection string as an environment variable.
Example:
```bash
docker run -p 80:80 \
-e ENCRYPTION_KEY=f40c9178624764ad85a6830b37ce239a \
-e JWT_SIGNUP_SECRET=38ea90fb7998b92176080f457d890392 \
-e JWT_REFRESH_SECRET=7764c7bbf3928ad501591a3e005eb364 \
-e JWT_AUTH_SECRET=5239fea3a4720c0e524f814a540e14a2 \
-e JWT_SERVICE_SECRET=8509fb8b90c9b53e9e61d1e35826dcb5 \
-e REDIS_URL=<> \
-e MONGO_URL="<>" \
infisical/infisical:latest
```
Redis environment variable name: `REDIS_URL`
</Tab>
</Tabs>
## Support
If you have questions or need support, please join our [slack channel](https://infisical-users.slack.com) and one of our teammates will be happy to guide you.
@@ -32,7 +32,7 @@ However, it's important to specify a particular version of Infisical during inst
View [properties for frontend and backend](https://github.com/Infisical/infisical/tree/main/helm-charts/infisical#parameters). View [properties for frontend and backend](https://github.com/Infisical/infisical/tree/main/helm-charts/infisical#parameters).
<Tip> <Tip>
To find the latest version number of Infisical, follow the links bellow To find the latest version number of Infisical, follow the links below
- [frontend Docker image](https://hub.docker.com/r/infisical/frontend/tags) - [frontend Docker image](https://hub.docker.com/r/infisical/frontend/tags)
- [backend Docker image](https://hub.docker.com/r/infisical/backend/tags) - [backend Docker image](https://hub.docker.com/r/infisical/backend/tags)
</Tip> </Tip>
@@ -92,7 +92,7 @@ ingress:
#### Database #### Database
Infisical uses a document database as its persistence layer. With this Helm chart, you spin up a MongoDB instance power by Bitnami along side other Infisical services in your cluster. Infisical uses a document database as its persistence layer. With this Helm chart, you spin up a MongoDB instance power by Bitnami along side other Infisical services in your cluster.
When persistence is enabled, the data will be stored a Kubernetes Persistence Volume. View all [properties for mongodb](https://github.com/Infisical/infisical/tree/main/helm-charts/infisical). When persistence is enabled, the data will be stored as Kubernetes Persistence Volume. View all [properties for mongodb](https://github.com/Infisical/infisical/tree/main/helm-charts/infisical).
```yaml simple-values-example.yaml ```yaml simple-values-example.yaml
mongodb: mongodb:
@@ -215,3 +215,6 @@ Allow 3-5 minutes for the deployment to complete. Once done, you should now be a
<Info> <Info>
Once installation is complete, you will have to create the first account. No default account is provided. Once installation is complete, you will have to create the first account. No default account is provided.
</Info> </Info>
## Related blogs
- [Set up Infisical in a development cluster](https://iamunnip.hashnode.dev/infisical-open-source-secretops-kubernetes-setup)
@@ -46,6 +46,10 @@ Add the required environment variables listed below to your docker run command.
Must be a random 16 byte hex string. Can be generated with `openssl rand -hex 16` Must be a random 16 byte hex string. Can be generated with `openssl rand -hex 16`
</ParamField> </ParamField>
<ParamField query="REDIS_URL" type="string" default="none" required>
Redis connection string
</ParamField>
<ParamField query="MONGO_URL" type="string" default="none" required> <ParamField query="MONGO_URL" type="string" default="none" required>
*TLS based connection string is not yet supported *TLS based connection string is not yet supported
</ParamField> </ParamField>
@@ -61,6 +65,7 @@ docker run -p 80:80 \
-e JWT_AUTH_SECRET=5239fea3a4720c0e524f814a540e14a2 \ -e JWT_AUTH_SECRET=5239fea3a4720c0e524f814a540e14a2 \
-e JWT_SERVICE_SECRET=8509fb8b90c9b53e9e61d1e35826dcb5 \ -e JWT_SERVICE_SECRET=8509fb8b90c9b53e9e61d1e35826dcb5 \
-e MONGO_URL="<>" \ -e MONGO_URL="<>" \
-e REDIS_URL="<>" \
infisical/infisical:latest infisical/infisical:latest
``` ```
-6
View File
@@ -22,12 +22,6 @@ Choose from a variety of deployment options listed below to get started.
> >
Automatically create and deploy Infisical on to a Kubernetes cluster Automatically create and deploy Infisical on to a Kubernetes cluster
</Card> </Card>
<Card title="Fly.io" color="#dc2626" href="deployment-options/fly.io">
Use our standalone docker image to deploy on Fly.io
</Card>
<Card title="Render.com" color="#dc2626" href="deployment-options/render">
Install on Render using our standalone docker image
</Card>
<Card title="AWS EC2" color="#0285c7" href="deployment-options/aws-ec2"> <Card title="AWS EC2" color="#0285c7" href="deployment-options/aws-ec2">
Install infisical with just a few clicks using our Cloud Formation template Install infisical with just a few clicks using our Cloud Formation template
</Card> </Card>
+2 -1
View File
@@ -57,7 +57,8 @@ COPY --chown=nextjs:nodejs --chmod=555 scripts ./scripts
COPY --from=builder /app/public ./public COPY --from=builder /app/public ./public
RUN chown nextjs:nodejs ./public/data RUN chown nextjs:nodejs ./public/data
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static COPY --from=builder --chown=nextjs:nodejs --chmod=777 /app/.next/static ./.next/static
RUN chmod -R 777 /app/.next/server
USER nextjs USER nextjs
+1 -1
View File
@@ -29,7 +29,7 @@ export const ModalContent = forwardRef<HTMLDivElement, ModalContentProps>(
<Card <Card
isRounded isRounded
className={twMerge( className={twMerge(
"fixed top-1/2 left-1/2 z-[90] max-w-lg -translate-y-2/4 -translate-x-2/4 animate-popIn border border-mineshaft-600 drop-shadow-2xl", "fixed top-1/2 left-1/2 z-[90] dark:[color-scheme:dark] max-h-screen overflow-y-auto thin-scrollbar max-w-lg -translate-y-2/4 -translate-x-2/4 animate-popIn border border-mineshaft-600 drop-shadow-2xl",
className className
)} )}
> >
@@ -4,4 +4,5 @@ export type ServerStatus = {
emailConfigured: boolean; emailConfigured: boolean;
inviteOnlySignup: boolean; inviteOnlySignup: boolean;
secretScanningConfigured: boolean secretScanningConfigured: boolean
redisConfigured: boolean
}; };
@@ -16,6 +16,7 @@ export {
useGetWorkspaceUsers, useGetWorkspaceUsers,
useNameWorkspaceSecrets, useNameWorkspaceSecrets,
useRenameWorkspace, useRenameWorkspace,
useReorderWsEnvironment,
useToggleAutoCapitalization, useToggleAutoCapitalization,
useUpdateUserWorkspaceRole, useUpdateUserWorkspaceRole,
useUpdateWsEnvironment} from "./queries"; useUpdateWsEnvironment} from "./queries";
@@ -13,6 +13,7 @@ import {
GetWsEnvironmentDTO, GetWsEnvironmentDTO,
NameWorkspaceSecretsDTO, NameWorkspaceSecretsDTO,
RenameWorkspaceDTO, RenameWorkspaceDTO,
ReorderEnvironmentsDTO,
ToggleAutoCapitalizationDTO, ToggleAutoCapitalizationDTO,
UpdateEnvironmentDTO, UpdateEnvironmentDTO,
Workspace, Workspace,
@@ -244,6 +245,21 @@ export const useCreateWsEnvironment = () => {
}); });
}; };
export const useReorderWsEnvironment = () => {
const queryClient = useQueryClient();
return useMutation<{}, {}, ReorderEnvironmentsDTO>({
mutationFn: ({ workspaceID, environmentSlug, environmentName, otherEnvironmentSlug, otherEnvironmentName}) => {
return apiRequest.patch(`/api/v2/workspace/${workspaceID}/environments`, {
environmentSlug, environmentName, otherEnvironmentSlug, otherEnvironmentName
});
},
onSuccess: () => {
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
}
});
};
export const useUpdateWsEnvironment = () => { export const useUpdateWsEnvironment = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
@@ -46,6 +46,15 @@ export type CreateEnvironmentDTO = {
environmentName: string; environmentName: string;
}; };
export type ReorderEnvironmentsDTO = {
workspaceID: string;
environmentSlug: string;
environmentName: string;
otherEnvironmentSlug: string;
otherEnvironmentName: string;
};
export type UpdateEnvironmentDTO = { export type UpdateEnvironmentDTO = {
workspaceID: string; workspaceID: string;
oldEnvironmentSlug: string; oldEnvironmentSlug: string;
+5 -5
View File
@@ -483,7 +483,7 @@ export const AppLayout = ({ children }: LayoutProps) => {
</MenuItem> </MenuItem>
</a> </a>
</Link> </Link>
{/* <Link href={`/project/${currentWorkspace?._id}/audit-logs`} passHref> <Link href={`/project/${currentWorkspace?._id}/audit-logs`} passHref>
<a> <a>
<MenuItem <MenuItem
isSelected={ isSelected={
@@ -491,11 +491,11 @@ export const AppLayout = ({ children }: LayoutProps) => {
} }
icon="system-outline-168-view-headline" icon="system-outline-168-view-headline"
> >
Audit Logs V2 Audit Logs
</MenuItem> </MenuItem>
</a> </a>
</Link> */} </Link>
<Link href={`/project/${currentWorkspace?._id}/logs`} passHref> {/* <Link href={`/project/${currentWorkspace?._id}/logs`} passHref>
<a> <a>
<MenuItem <MenuItem
isSelected={ isSelected={
@@ -506,7 +506,7 @@ export const AppLayout = ({ children }: LayoutProps) => {
Audit Logs Audit Logs
</MenuItem> </MenuItem>
</a> </a>
</Link> </Link> */}
{/* <Link href={`/project/${currentWorkspace?._id}/secret-scanning`} passHref> {/* <Link href={`/project/${currentWorkspace?._id}/secret-scanning`} passHref>
<a> <a>
<MenuItem <MenuItem
@@ -4,6 +4,7 @@ import { useEffect, useState } from "react";
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import Head from "next/head"; import Head from "next/head";
import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { IconProp } from "@fortawesome/fontawesome-svg-core"; import { IconProp } from "@fortawesome/fontawesome-svg-core";
import { faSlack } from "@fortawesome/free-brands-svg-icons"; import { faSlack } from "@fortawesome/free-brands-svg-icons";
@@ -11,6 +12,7 @@ import { faFolderOpen } from "@fortawesome/free-regular-svg-icons";
import { import {
faArrowRight, faArrowRight,
faCheckCircle, faCheckCircle,
faExclamationCircle,
faHandPeace, faHandPeace,
faMagnifyingGlass, faMagnifyingGlass,
faNetworkWired, faNetworkWired,
@@ -37,6 +39,7 @@ import {
import { TabsObject } from "@app/components/v2/Tabs"; import { TabsObject } from "@app/components/v2/Tabs";
import { useSubscription, useUser, useWorkspace } from "@app/context"; import { useSubscription, useUser, useWorkspace } from "@app/context";
import { fetchOrgUsers, useAddUserToWs, useCreateWorkspace, useRegisterUserAction,useUploadWsKey } from "@app/hooks/api"; import { fetchOrgUsers, useAddUserToWs, useCreateWorkspace, useRegisterUserAction,useUploadWsKey } from "@app/hooks/api";
import { useFetchServerStatus } from "@app/hooks/api/serverDetails";
import { usePopUp } from "@app/hooks/usePopUp"; import { usePopUp } from "@app/hooks/usePopUp";
import { encryptAssymmetric } from "../../../../components/utilities/cryptography/crypto"; import { encryptAssymmetric } from "../../../../components/utilities/cryptography/crypto";
@@ -269,6 +272,8 @@ export default function Organization() {
const createWs = useCreateWorkspace(); const createWs = useCreateWorkspace();
const { user } = useUser(); const { user } = useUser();
const uploadWsKey = useUploadWsKey(); const uploadWsKey = useUploadWsKey();
const { data: serverDetails } = useFetchServerStatus();
const onCreateProject = async ({ name, addMembers }: TAddProjectFormData) => { const onCreateProject = async ({ name, addMembers }: TAddProjectFormData) => {
// type check // type check
@@ -340,6 +345,18 @@ export default function Organization() {
<title>{t("common.head-title", { title: t("settings.members.title") })}</title> <title>{t("common.head-title", { title: t("settings.members.title") })}</title>
<link rel="icon" href="/infisical.ico" /> <link rel="icon" href="/infisical.ico" />
</Head> </Head>
{!serverDetails?.redisConfigured && <div className="mb-4 flex flex-col items-start justify-start px-6 py-6 pb-0 text-3xl">
<p className="mr-4 mb-4 font-semibold text-white">Announcements</p>
<div className="w-full border border-blue-400/70 rounded-md bg-blue-900/70 p-2 text-base text-mineshaft-100 flex items-center">
<FontAwesomeIcon icon={faExclamationCircle} className="text-2xl mr-4 p-4 text-mineshaft-50"/>
Attention: Updated versions of Infisical now require Redis for full functionality. Learn how to configure it
<Link href="https://infisical.com/docs/self-hosting/configuration/redis" target="_blank">
<span className="pl-1 text-white underline underline-offset-2 hover:decoration-blue-400 hover:text-blue-200 duration-100 cursor-pointer">
here
</span>
</Link>.
</div>
</div>}
<div className="mb-4 flex flex-col items-start justify-start px-6 py-6 pb-0 text-3xl"> <div className="mb-4 flex flex-col items-start justify-start px-6 py-6 pb-0 text-3xl">
<p className="mr-4 font-semibold text-white">Projects</p> <p className="mr-4 font-semibold text-white">Projects</p>
<div className="mt-6 flex w-full flex-row"> <div className="mt-6 flex w-full flex-row">
@@ -9,7 +9,7 @@ const Logs = () => {
return ( return (
<div className="h-full bg-bunker-800"> <div className="h-full bg-bunker-800">
<Head> <Head>
<title>{t("common.head-title", { title: t("billing.title") })}</title> <title>{t("common.head-title", { title: t("settings.project.title") })}</title>
<link rel="icon" href="/infisical.ico" /> <link rel="icon" href="/infisical.ico" />
<meta property="og:image" content="/images/message.png" /> <meta property="og:image" content="/images/message.png" />
</Head> </Head>
@@ -110,6 +110,11 @@ export const SecretInputRow = memo(
append append
} = useFieldArray({ control, name: `secrets.${index}.tags` }); } = useFieldArray({ control, name: `secrets.${index}.tags` });
const tagColorByTagId = new Map((wsTags || []).map((wsTag, i) => [wsTag._id, tagColors[i % tagColors.length]]))
// display the tags in alphabetical order
secretTags.sort((a, b) => a.name.localeCompare(b.name))
// to get details on a secret // to get details on a secret
const overrideAction = useWatch({ const overrideAction = useWatch({
control, control,
@@ -321,19 +326,22 @@ export const SecretInputRow = memo(
</td> </td>
<td className="min-w-sm flex"> <td className="min-w-sm flex">
<div className="flex h-8 items-center pl-2"> <div className="flex h-8 items-center pl-2">
{secretTags.map(({ id, slug }, i) => ( {secretTags.map(({ id, _id, slug }, i) => {
<Tag // This map lookup shouldn't ever fail, but if it does we default to the first color
className={cx( const tagColor = tagColorByTagId.get(_id) || tagColors[0]
tagColors[i % tagColors.length].bg, return (
tagColors[i % tagColors.length].text <Tag
)} className={cx(
isDisabled={isReadOnly || isAddOnly || isRollbackMode} tagColor.bg,
onClose={() => remove(i)} tagColor.text
key={id} )}
> isDisabled={isReadOnly || isAddOnly || isRollbackMode}
{slug} onClose={() => remove(i)}
</Tag> key={id}
))} >
{slug}
</Tag>)
})}
<div className="w-0 overflow-hidden group-hover:w-6"> <div className="w-0 overflow-hidden group-hover:w-6">
<Tooltip content="Copy value"> <Tooltip content="Copy value">
<IconButton <IconButton
@@ -40,7 +40,8 @@ export const PasswordStep = ({
authMethod authMethod
} = jwt_decode(providerAuthToken) as any; } = jwt_decode(providerAuthToken) as any;
const handleLogin = async () => { const handleLogin = async (e:React.FormEvent) => {
e.preventDefault()
try { try {
setIsLoading(true); setIsLoading(true);
@@ -120,9 +121,11 @@ export const PasswordStep = ({
} }
}; };
return ( return (
<form <form
onSubmit={(e) => e.preventDefault()} onSubmit={handleLogin}
className="h-full mx-auto w-full max-w-md px-6 pt-8" className="h-full mx-auto w-full max-w-md px-6 pt-8"
> >
<div className="mb-8"> <div className="mb-8">
@@ -153,9 +156,9 @@ export const PasswordStep = ({
</div> </div>
<div className='lg:w-1/6 w-1/4 w-full mx-auto flex items-center justify-center min-w-[22rem] text-center rounded-md mt-4'> <div className='lg:w-1/6 w-1/4 w-full mx-auto flex items-center justify-center min-w-[22rem] text-center rounded-md mt-4'>
<Button <Button
type="submit"
colorSchema="primary" colorSchema="primary"
variant="outline_bg" variant="outline_bg"
onClick={async () => handleLogin()}
isFullWidth isFullWidth
isLoading={isLoading} isLoading={isLoading}
className="h-14" className="h-14"
@@ -15,11 +15,19 @@ export const SAMLSSOStep = ({
const queryParams = new URLSearchParams(window.location.search); const queryParams = new URLSearchParams(window.location.search);
const handleSubmission = (e:React.FormEvent) => {
e.preventDefault()
const callbackPort = queryParams.get("callback_port");
window.open(`/api/v1/sso/redirect/saml2/${ssoIdentifier}${callbackPort ? `?callback_port=${callbackPort}` : ""}`);
window.close();
}
return ( return (
<div className="mx-auto w-full max-w-md md:px-6"> <div className="mx-auto w-full max-w-md md:px-6">
<p className="mx-auto mb-6 flex w-max justify-center text-xl font-medium text-transparent bg-clip-text bg-gradient-to-b from-white to-bunker-200 text-center mb-8"> <p className="mx-auto mb-6 flex w-max justify-center text-xl font-medium text-transparent bg-clip-text bg-gradient-to-b from-white to-bunker-200 text-center mb-8">
What&apos;s your SSO Identifier? What&apos;s your SSO Identifier?
</p> </p>
<form onSubmit={handleSubmission}>
<div className="relative flex items-center justify-center lg:w-1/6 w-1/4 min-w-[20rem] md:min-w-[22rem] mx-auto w-full rounded-lg max-h-24 md:max-h-28"> <div className="relative flex items-center justify-center lg:w-1/6 w-1/4 min-w-[20rem] md:min-w-[22rem] mx-auto w-full rounded-lg max-h-24 md:max-h-28">
<div className="flex items-center justify-center w-full rounded-lg max-h-24 md:max-h-28"> <div className="flex items-center justify-center w-full rounded-lg max-h-24 md:max-h-28">
<Input <Input
@@ -36,19 +44,16 @@ export const SAMLSSOStep = ({
</div> </div>
<div className='lg:w-1/6 w-1/4 w-full mx-auto flex items-center justify-center min-w-[20rem] md:min-w-[22rem] text-center rounded-md mt-4'> <div className='lg:w-1/6 w-1/4 w-full mx-auto flex items-center justify-center min-w-[20rem] md:min-w-[22rem] text-center rounded-md mt-4'>
<Button <Button
type="submit"
colorSchema="primary" colorSchema="primary"
variant="outline_bg" variant="outline_bg"
onClick={() => {
const callbackPort = queryParams.get("callback_port");
window.open(`/api/v1/sso/redirect/saml2/${ssoIdentifier}${callbackPort ? `?callback_port=${callbackPort}` : ""}`);
window.close();
}}
isFullWidth isFullWidth
className="h-14" className="h-14"
> >
{t("login.login")} {t("login.login")}
</Button> </Button>
</div> </div>
</form>
<div className="flex flex-row items-center justify-center mt-4"> <div className="flex flex-row items-center justify-center mt-4">
<button <button
onClick={() => { onClick={() => {
@@ -74,8 +74,7 @@ export const LogsFilter = ({
className="w-40 mr-4" className="w-40 mr-4"
> >
<Select <Select
placeholder="Select" {...(field.value ? { value: field.value } : { placeholder: "Select" })}
// {...(field.value ? { value: field.value } : { placeholder: "Select" })}
{...field} {...field}
onValueChange={(e) => onChange(e)} onValueChange={(e) => onChange(e)}
className="w-full bg-mineshaft-700 border border-mineshaft-500 text-mineshaft-100" className="w-full bg-mineshaft-700 border border-mineshaft-500 text-mineshaft-100"
@@ -1,13 +1,25 @@
import { useEffect } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { useRouter } from "next/router";
import { yupResolver } from "@hookform/resolvers/yup"; import { yupResolver } from "@hookform/resolvers/yup";
import { UpgradePlanModal } from "@app/components/v2";
import { useSubscription } from "@app/context";
import { EventType, UserAgentType } from "@app/hooks/api/auditLogs/enums"; import { EventType, UserAgentType } from "@app/hooks/api/auditLogs/enums";
import { usePopUp } from "@app/hooks/usePopUp";
import { LogsFilter } from "./LogsFilter"; import { LogsFilter } from "./LogsFilter";
import { LogsTable } from "./LogsTable"; import { LogsTable } from "./LogsTable";
import { AuditLogFilterFormData, auditLogFilterFormSchema } from "./types"; import { AuditLogFilterFormData, auditLogFilterFormSchema } from "./types";
export const LogsSection = () => { export const LogsSection = () => {
const { subscription } = useSubscription();
const router = useRouter();
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([
"upgradePlan"
] as const);
const { const {
control, control,
reset, reset,
@@ -21,6 +33,12 @@ export const LogsSection = () => {
} }
}); });
useEffect(() => {
if (subscription && !subscription.auditLogs) {
handlePopUpOpen("upgradePlan");
}
}, [subscription]);
const eventType = watch("eventType") as EventType | undefined; const eventType = watch("eventType") as EventType | undefined;
const userAgentType = watch("userAgentType") as UserAgentType | undefined; const userAgentType = watch("userAgentType") as UserAgentType | undefined;
const actor = watch("actor"); const actor = watch("actor");
@@ -54,6 +72,19 @@ export const LogsSection = () => {
perPage={perPage} perPage={perPage}
setValue={setValue} setValue={setValue}
/> />
<UpgradePlanModal
isOpen={popUp.upgradePlan.isOpen}
onOpenChange={(isOpen) => {
if (!isOpen) {
router.back();
return;
}
handlePopUpToggle("upgradePlan", isOpen)
}}
text="You can use audit logs if you switch to a paid Infisical plan."
/>
</div> </div>
); );
} }
@@ -52,7 +52,7 @@ export const SecretOverviewTableRow = ({
<div className="text-blue-300/70"> <div className="text-blue-300/70">
<FontAwesomeIcon icon={isFormExpanded ? faAngleDown : faKey} /> <FontAwesomeIcon icon={isFormExpanded ? faAngleDown : faKey} />
</div> </div>
<div>{secretKey}</div> <div title={secretKey}>{secretKey}</div>
</div> </div>
</div> </div>
</Td> </Td>
@@ -73,7 +73,9 @@ export const SecretOverviewTableRow = ({
> >
<div className="h-full w-full border-r border-mineshaft-600 py-[0.85rem] px-5"> <div className="h-full w-full border-r border-mineshaft-600 py-[0.85rem] px-5">
<div className="flex justify-center"> <div className="flex justify-center">
{!isSecretEmpty && <FontAwesomeIcon icon={isSecretPresent ? faCheck : faXmark} />} {!isSecretEmpty && <Tooltip content={isSecretPresent ? "Present secret" : "Missing secret"}>
<FontAwesomeIcon icon={isSecretPresent ? faCheck : faXmark} />
</Tooltip>}
{isSecretEmpty && ( {isSecretEmpty && (
<Tooltip content="Empty value"> <Tooltip content="Empty value">
<FontAwesomeIcon icon={faCircle} /> <FontAwesomeIcon icon={faCircle} />
@@ -1,6 +1,7 @@
import { faPencil, faXmark } from "@fortawesome/free-solid-svg-icons"; import { faArrowDown,faArrowUp, faPencil, faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
import { import {
EmptyState, EmptyState,
IconButton, IconButton,
@@ -14,6 +15,9 @@ import {
Tr Tr
} from "@app/components/v2"; } from "@app/components/v2";
import { useWorkspace } from "@app/context"; import { useWorkspace } from "@app/context";
import {
useReorderWsEnvironment
} from "@app/hooks/api";
import { UsePopUpState } from "@app/hooks/usePopUp"; import { UsePopUpState } from "@app/hooks/usePopUp";
type Props = { type Props = {
@@ -31,6 +35,43 @@ type Props = {
export const EnvironmentTable = ({ handlePopUpOpen }: Props) => { export const EnvironmentTable = ({ handlePopUpOpen }: Props) => {
const { currentWorkspace, isLoading } = useWorkspace(); const { currentWorkspace, isLoading } = useWorkspace();
const { createNotification } = useNotificationContext();
const reorderWsEnvironment = useReorderWsEnvironment();
const handleReorderEnv= async (shouldMoveUp: boolean, name: string, slug: string) => {
try {
if (!currentWorkspace?._id) return;
const indexOfEnv = currentWorkspace.environments.findIndex((env) => env.name === name && env.slug === slug);
// check that this reordering is possible
if (indexOfEnv === 0 && shouldMoveUp || indexOfEnv === currentWorkspace.environments.length - 1 && !shouldMoveUp) {
return
}
const indexToSwap = shouldMoveUp ? indexOfEnv - 1 : indexOfEnv + 1
await reorderWsEnvironment.mutateAsync({
workspaceID: currentWorkspace._id,
environmentSlug: slug,
environmentName: name,
otherEnvironmentSlug: currentWorkspace.environments[indexToSwap].slug,
otherEnvironmentName: currentWorkspace.environments[indexToSwap].name
});
createNotification({
text: "Successfully re-ordered environments",
type: "success"
});
} catch (err) {
console.error(err);
createNotification({
text: "Failed to re-order environments",
type: "error"
});
}
};
return ( return (
<TableContainer> <TableContainer>
<Table> <Table>
@@ -45,11 +86,35 @@ export const EnvironmentTable = ({ handlePopUpOpen }: Props) => {
{isLoading && <TableSkeleton columns={3} innerKey="project-envs" />} {isLoading && <TableSkeleton columns={3} innerKey="project-envs" />}
{!isLoading && {!isLoading &&
currentWorkspace && currentWorkspace &&
currentWorkspace.environments.map(({ name, slug }) => ( currentWorkspace.environments.map(({ name, slug }, pos) => (
<Tr key={name}> <Tr key={name}>
<Td>{name}</Td> <Td>{name}</Td>
<Td>{slug}</Td> <Td>{slug}</Td>
<Td className="flex items-center justify-end"> <Td className="flex items-center justify-end">
<IconButton
className="mr-3 py-2"
onClick={() => {
handleReorderEnv(false, name, slug)
}}
colorSchema="primary"
variant="plain"
ariaLabel="update"
isDisabled={pos === currentWorkspace.environments.length - 1}
>
<FontAwesomeIcon icon={faArrowDown} />
</IconButton>
<IconButton
className="mr-3 py-2"
onClick={() => {
handleReorderEnv(true, name, slug)
}}
colorSchema="primary"
variant="plain"
ariaLabel="update"
isDisabled={pos === 0}
>
<FontAwesomeIcon icon={faArrowUp} />
</IconButton>
<IconButton <IconButton
className="mr-3 py-2" className="mr-3 py-2"
onClick={() => { onClick={() => {
@@ -11,9 +11,6 @@ import {
useNameWorkspaceSecrets useNameWorkspaceSecrets
} from "@app/hooks/api"; } from "@app/hooks/api";
// TODO: add check so that this only shows up if user is
// an admin in the workspace
export const ProjectIndexSecretsSection = () => { export const ProjectIndexSecretsSection = () => {
const { currentWorkspace } = useWorkspace(); const { currentWorkspace } = useWorkspace();
const { data: isBlindIndexed, isLoading: isBlindIndexedLoading } = useGetWorkspaceIndexStatus(currentWorkspace?._id ?? ""); const { data: isBlindIndexed, isLoading: isBlindIndexedLoading } = useGetWorkspaceIndexStatus(currentWorkspace?._id ?? "");
@@ -53,7 +50,7 @@ export const ProjectIndexSecretsSection = () => {
}); });
}; };
return (!isBlindIndexedLoading && !isBlindIndexed) ? ( return (!isBlindIndexedLoading && (isBlindIndexed === false)) ? (
<div className="mb-6 p-4 bg-mineshaft-900 rounded-lg border border-mineshaft-600"> <div className="mb-6 p-4 bg-mineshaft-900 rounded-lg border border-mineshaft-600">
<p className="mb-3 text-xl font-semibold">Blind Indices</p> <p className="mb-3 text-xl font-semibold">Blind Indices</p>
<p className="text-gray-400 mb-8"> <p className="text-gray-400 mb-8">
+5 -2
View File
@@ -5,8 +5,11 @@ dependencies:
- name: mailhog - name: mailhog
repository: https://codecentric.github.io/helm-charts repository: https://codecentric.github.io/helm-charts
version: 5.2.3 version: 5.2.3
- name: redis
repository: https://charts.bitnami.com/bitnami
version: 17.15.0
- name: ingress-nginx - name: ingress-nginx
repository: https://kubernetes.github.io/ingress-nginx repository: https://kubernetes.github.io/ingress-nginx
version: 4.0.13 version: 4.0.13
digest: sha256:d1a679e6c30e37da96b7a4b6115e285f61e6ce0dd921ffbe2cf557418c229f33 digest: sha256:1762132c45000bb6d410c6da2291ac5c65f91331550a473b370374ba042d0744
generated: "2023-04-08T15:59:12.950942-07:00" generated: "2023-08-10T15:03:12.219788-04:00"
+5 -1
View File
@@ -7,7 +7,7 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes # This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version. # to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/) # Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.2.1 version: 0.3.1
# This is the version number of the application being deployed. This version number should be # This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to # incremented each time you make changes to the application. Versions are not expected to
@@ -24,6 +24,10 @@ dependencies:
version: "~5.2.3" version: "~5.2.3"
repository: "https://codecentric.github.io/helm-charts" repository: "https://codecentric.github.io/helm-charts"
condition: mailhog.enabled condition: mailhog.enabled
- name: redis
version: 17.15.0
repository: https://charts.bitnami.com/bitnami
condition: redis.enabled
- name: ingress-nginx - name: ingress-nginx
version: 4.0.13 version: 4.0.13
repository: https://kubernetes.github.io/ingress-nginx repository: https://kubernetes.github.io/ingress-nginx
+39 -36
View File
@@ -7,6 +7,7 @@ This is the Infisical application Helm chart. This chart includes the following
| `frontend` | Infisical's Web UI | | `frontend` | Infisical's Web UI |
| `backend` | Infisical's API | | `backend` | Infisical's API |
| `mongodb` | Infisical's database | | `mongodb` | Infisical's database |
| `redis` | Infisical's cache service |
| `mailhog` | Infisical's development SMTP server | | `mailhog` | Infisical's development SMTP server |
## Installation ## Installation
@@ -58,7 +59,6 @@ kubectl get secrets -n <namespace> <secret-name> \
| `nameOverride` | Override release name | `""` | | `nameOverride` | Override release name | `""` |
| `fullnameOverride` | Override release fullname | `""` | | `fullnameOverride` | Override release fullname | `""` |
### Infisical frontend parameters ### Infisical frontend parameters
| Name | Description | Value | | Name | Description | Value |
@@ -78,41 +78,41 @@ kubectl get secrets -n <namespace> <secret-name> \
| `frontend.service.nodePort` | Backend service nodePort (used if above type is `NodePort`) | `""` | | `frontend.service.nodePort` | Backend service nodePort (used if above type is `NodePort`) | `""` |
| `frontendEnvironmentVariables.SITE_URL` | Absolute URL including the protocol (e.g. https://app.infisical.com) | `infisical.local` | | `frontendEnvironmentVariables.SITE_URL` | Absolute URL including the protocol (e.g. https://app.infisical.com) | `infisical.local` |
### Infisical backend parameters ### Infisical backend parameters
| Name | Description | Value | | Name | Description | Value |
| ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------- | | ------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- |
| `backend.enabled` | Enable backend | `true` | | `backend.enabled` | Enable backend | `true` |
| `backend.name` | Backend name | `backend` | | `backend.name` | Backend name | `backend` |
| `backend.fullnameOverride` | Backend fullnameOverride | `""` | | `backend.fullnameOverride` | Backend fullnameOverride | `""` |
| `backend.podAnnotations` | Backend pod annotations | `{}` | | `backend.podAnnotations` | Backend pod annotations | `{}` |
| `backend.deploymentAnnotations` | Backend deployment annotations | `{}` | | `backend.deploymentAnnotations` | Backend deployment annotations | `{}` |
| `backend.replicaCount` | Backend replica count | `2` | | `backend.replicaCount` | Backend replica count | `2` |
| `backend.image.repository` | Backend image repository | `infisical/backend` | | `backend.image.repository` | Backend image repository | `infisical/backend` |
| `backend.image.tag` | Backend image tag | `latest` | | `backend.image.tag` | Backend image tag | `latest` |
| `backend.image.pullPolicy` | Backend image pullPolicy | `IfNotPresent` | | `backend.image.pullPolicy` | Backend image pullPolicy | `IfNotPresent` |
| `backend.kubeSecretRef` | Backend secret resource reference name (containing required [backend configuration variables](https://infisical.com/docs/self-hosting/configuration/envars)) | `""` | | `backend.kubeSecretRef` | Backend secret resource reference name (containing required [backend configuration variables](https://infisical.com/docs/self-hosting/configuration/envars)) | `""` |
| `backend.service.annotations` | Backend service annotations | `{}` | | `backend.service.annotations` | Backend service annotations | `{}` |
| `backend.service.type` | Backend service type | `ClusterIP` | | `backend.service.type` | Backend service type | `ClusterIP` |
| `backend.service.nodePort` | Backend service nodePort (used if above type is `NodePort`) | `""` | | `backend.service.nodePort` | Backend service nodePort (used if above type is `NodePort`) | `""` |
| `backendEnvironmentVariables.ENCRYPTION_KEY` | **Required** Backend encryption key (128-bit hex value, 32-characters hex, [example](https://stackoverflow.com/a/34329057))</br><kbd>auto-generated</kbd> variable (if not provided, and not found in an existing secret) | `""` | | `backendEnvironmentVariables.ENCRYPTION_KEY` | **Required** Backend encryption key (128-bit hex value, 32-characters hex, [example](https://stackoverflow.com/a/34329057))</br><kbd>auto-generated</kbd> variable (if not provided, and not found in an existing secret) | `""` |
| `backendEnvironmentVariables.JWT_SIGNUP_SECRET` | **Required** Secrets to sign JWT tokens (128-bit hex value, 32-characters hex, [example](https://stackoverflow.com/a/34329057))</br><kbd>auto-generated</kbd> variable (if not provided, and not found in an existing secret) | `""` | | `backendEnvironmentVariables.JWT_SIGNUP_SECRET` | **Required** Secrets to sign JWT tokens (128-bit hex value, 32-characters hex, [example](https://stackoverflow.com/a/34329057))</br><kbd>auto-generated</kbd> variable (if not provided, and not found in an existing secret) | `""` |
| `backendEnvironmentVariables.JWT_REFRESH_SECRET` | **Required** Secrets to sign JWT tokens (128-bit hex value, 32-characters hex, [example](https://stackoverflow.com/a/34329057))</br><kbd>auto-generated</kbd> variable (if not provided, and not found in an existing secret) | `""` | | `backendEnvironmentVariables.JWT_REFRESH_SECRET` | **Required** Secrets to sign JWT tokens (128-bit hex value, 32-characters hex, [example](https://stackoverflow.com/a/34329057))</br><kbd>auto-generated</kbd> variable (if not provided, and not found in an existing secret) | `""` |
| `backendEnvironmentVariables.JWT_AUTH_SECRET` | **Required** Secrets to sign JWT tokens (128-bit hex value, 32-characters hex, [example](https://stackoverflow.com/a/34329057))</br><kbd>auto-generated</kbd> variable (if not provided, and not found in an existing secret) | `""` | | `backendEnvironmentVariables.JWT_AUTH_SECRET` | **Required** Secrets to sign JWT tokens (128-bit hex value, 32-characters hex, [example](https://stackoverflow.com/a/34329057))</br><kbd>auto-generated</kbd> variable (if not provided, and not found in an existing secret) | `""` |
| `backendEnvironmentVariables.JWT_SERVICE_SECRET` | **Required** Secrets to sign JWT tokens (128-bit hex value, 32-characters hex, [example](https://stackoverflow.com/a/34329057))</br><kbd>auto-generated</kbd> variable (if not provided, and not found in an existing secret) | `""` | | `backendEnvironmentVariables.JWT_SERVICE_SECRET` | **Required** Secrets to sign JWT tokens (128-bit hex value, 32-characters hex, [example](https://stackoverflow.com/a/34329057))</br><kbd>auto-generated</kbd> variable (if not provided, and not found in an existing secret) | `""` |
| `backendEnvironmentVariables.JWT_MFA_SECRET` | **Required** Secrets to sign JWT tokens (128-bit hex value, 32-characters hex, [example](https://stackoverflow.com/a/34329057))</br><kbd>auto-generated</kbd> variable (if not provided, and not found in an existing secret) | `""` | | `backendEnvironmentVariables.JWT_MFA_SECRET` | **Required** Secrets to sign JWT tokens (128-bit hex value, 32-characters hex, [example](https://stackoverflow.com/a/34329057))</br><kbd>auto-generated</kbd> variable (if not provided, and not found in an existing secret) | `""` |
| `backendEnvironmentVariables.SMTP_HOST` | **Required** Hostname to connect to for establishing SMTP connections | `""` | | `backendEnvironmentVariables.JWT_PROVIDER_AUTH_SECRET` | **Required** Secrets to sign JWT OAuth tokens (128-bit hex value, 32-characters hex, [example](https://stackoverflow.com/a/34329057))</br><kbd>auto-generated</kbd> variable (if not provided, and not found in an existing secret) | `""` |
| `backendEnvironmentVariables.SMTP_PORT` | Port to connect to for establishing SMTP connections | `587` | | `backendEnvironmentVariables.SMTP_HOST` | **Required** Hostname to connect to for establishing SMTP connections | `""` |
| `backendEnvironmentVariables.SMTP_SECURE` | If true, use TLS when connecting to host. If false, TLS will be used if STARTTLS is supported | `false` | | `backendEnvironmentVariables.SMTP_PORT` | Port to connect to for establishing SMTP connections | `587` |
| `backendEnvironmentVariables.SMTP_FROM_NAME` | Name label to be used in From field (e.g. Infisical) | `Infisical` | | `backendEnvironmentVariables.SMTP_SECURE` | If true, use TLS when connecting to host. If false, TLS will be used if STARTTLS is supported | `false` |
| `backendEnvironmentVariables.SMTP_FROM_ADDRESS` | **Required** Email address to be used for sending emails (e.g. dev@infisical.com) | `""` | | `backendEnvironmentVariables.SMTP_FROM_NAME` | Name label to be used in From field (e.g. Infisical) | `Infisical` |
| `backendEnvironmentVariables.SMTP_USERNAME` | **Required** Credential to connect to host (e.g. team@infisical.com) | `""` | | `backendEnvironmentVariables.SMTP_FROM_ADDRESS` | **Required** Email address to be used for sending emails (e.g. dev@infisical.com) | `""` |
| `backendEnvironmentVariables.SMTP_PASSWORD` | **Required** Credential to connect to host | `""` | | `backendEnvironmentVariables.SMTP_USERNAME` | **Required** Credential to connect to host (e.g. team@infisical.com) | `""` |
| `backendEnvironmentVariables.SITE_URL` | Absolute URL including the protocol (e.g. https://app.infisical.com) | `infisical.local` | | `backendEnvironmentVariables.SMTP_PASSWORD` | **Required** Credential to connect to host | `""` |
| `backendEnvironmentVariables.INVITE_ONLY_SIGNUP` | To disable account creation from the login page (invites only) | `false` | | `backendEnvironmentVariables.SITE_URL` | Absolute URL including the protocol (e.g. https://app.infisical.com) | `infisical.local` |
| `backendEnvironmentVariables.MONGO_URL` | MongoDB connection string (external or internal)</br>Leave it empty for auto-generated connection string | `""` | | `backendEnvironmentVariables.INVITE_ONLY_SIGNUP` | To disable account creation from the login page (invites only) | `false` |
| `backendEnvironmentVariables.MONGO_URL` | MongoDB connection string (external or internal)</br>Leave it empty for auto-generated connection string | `""` |
| `backendEnvironmentVariables.REDIS_URL` | | `redis://redis-master:6379` |
### MongoDB(&reg;) parameters ### MongoDB(&reg;) parameters
@@ -154,18 +154,17 @@ kubectl get secrets -n <namespace> <secret-name> \
| `mongodb.persistence.size` | Persistent storage request size | `8Gi` | | `mongodb.persistence.size` | Persistent storage request size | `8Gi` |
| `mongodbConnection.externalMongoDBConnectionString` | Deprecated :warning: External MongoDB connection string</br>Use backendEnvironmentVariables.MONGO_URL instead | `""` | | `mongodbConnection.externalMongoDBConnectionString` | Deprecated :warning: External MongoDB connection string</br>Use backendEnvironmentVariables.MONGO_URL instead | `""` |
### Ingress parameters ### Ingress parameters
| Name | Description | Value | | Name | Description | Value |
| -------------------------- | ------------------------------------------------------------------------ | ------- | | -------------------------- | ------------------------------------------------------------------------ | ------- |
| `ingress.enabled` | Enable ingress | `true` | | `ingress.enabled` | Enable ingress | `true` |
| `ingress.ingressClassName` | Ingress class name | `nginx` | | `ingress.ingressClassName` | Ingress class name | `nginx` |
| `ingress.nginx.enabled` | Ingress controller | `false` |
| `ingress.annotations` | Ingress annotations | `{}` | | `ingress.annotations` | Ingress annotations | `{}` |
| `ingress.hostName` | Ingress hostname (your custom domain name, e.g. `infisical.example.org`) | `""` | | `ingress.hostName` | Ingress hostname (your custom domain name, e.g. `infisical.example.org`) | `""` |
| `ingress.tls` | Ingress TLS hosts (matching above hostName) | `[]` | | `ingress.tls` | Ingress TLS hosts (matching above hostName) | `[]` |
### Mailhog parameters ### Mailhog parameters
| Name | Description | Value | | Name | Description | Value |
@@ -184,6 +183,10 @@ kubectl get secrets -n <namespace> <secret-name> \
| `mailhog.ingress.labels` | Ingress labels | `{}` | | `mailhog.ingress.labels` | Ingress labels | `{}` |
| `mailhog.ingress.hosts[0].host` | Mailhog host | `mailhog.infisical.local` | | `mailhog.ingress.hosts[0].host` | Mailhog host | `mailhog.infisical.local` |
### Redis parameters
## Persistence ## Persistence
@@ -126,5 +126,4 @@ Create the mongodb connection string.
{{- if .Values.mongodbConnection.externalMongoDBConnectionString -}} {{- if .Values.mongodbConnection.externalMongoDBConnectionString -}}
{{- $connectionString = .Values.mongodbConnection.externalMongoDBConnectionString -}} {{- $connectionString = .Values.mongodbConnection.externalMongoDBConnectionString -}}
{{- end -}} {{- end -}}
{{- printf "%s" $connectionString -}}
{{- end -}} {{- end -}}
+17
View File
@@ -169,6 +169,10 @@ backendEnvironmentVariables:
## ##
MONGO_URL: "" MONGO_URL: ""
## @param backendEnvironmentVariables.REDIS_URL
## By default, the backend will use the Redis that is auto deployed along with Infisical
REDIS_URL: "redis://redis-master:6379"
## @section MongoDB(&reg;) parameters ## @section MongoDB(&reg;) parameters
## Documentation : https://github.com/bitnami/charts/blob/main/bitnami/mongodb/values.yaml ## Documentation : https://github.com/bitnami/charts/blob/main/bitnami/mongodb/values.yaml
## ##
@@ -419,3 +423,16 @@ mailhog:
paths: paths:
- path: "/" - path: "/"
pathType: Prefix pathType: Prefix
## @section Redis parameters
## Documentation : https://github.com/bitnami/charts/tree/main/bitnami/redis#parameters
##
## @skip redis
##
redis:
name: "redis"
fullnameOverride: "redis"
enabled: true
architecture: standalone
auth:
enabled: false