Merge remote-tracking branch 'origin/main' into secret-scan-whole-repo

This commit is contained in:
Daniel Inge
2023-09-01 22:43:20 +01:00
207 changed files with 3148 additions and 3019 deletions

View File

@@ -3203,6 +3203,9 @@
"name": {
"example": "any"
},
"tagColor": {
"example": "any"
},
"slug": {
"example": "any"
}

View File

@@ -37,6 +37,7 @@ export const getClientIdNetlify = async () => (await client.getSecret("CLIENT_ID
export const getClientIdGitHub = async () => (await client.getSecret("CLIENT_ID_GITHUB")).secretValue;
export const getClientIdGitLab = async () => (await client.getSecret("CLIENT_ID_GITLAB")).secretValue;
export const getClientIdBitBucket = async () => (await client.getSecret("CLIENT_ID_BITBUCKET")).secretValue;
export const getClientIdGCPSecretManager = async () => (await client.getSecret("CLIENT_ID_GCP_SECRET_MANAGER")).secretValue;
export const getClientSecretAzure = async () => (await client.getSecret("CLIENT_SECRET_AZURE")).secretValue;
export const getClientSecretHeroku = async () => (await client.getSecret("CLIENT_SECRET_HEROKU")).secretValue;
export const getClientSecretVercel = async () => (await client.getSecret("CLIENT_SECRET_VERCEL")).secretValue;
@@ -44,6 +45,7 @@ export const getClientSecretNetlify = async () => (await client.getSecret("CLIEN
export const getClientSecretGitHub = async () => (await client.getSecret("CLIENT_SECRET_GITHUB")).secretValue;
export const getClientSecretGitLab = async () => (await client.getSecret("CLIENT_SECRET_GITLAB")).secretValue;
export const getClientSecretBitBucket = async () => (await client.getSecret("CLIENT_SECRET_BITBUCKET")).secretValue;
export const getClientSecretGCPSecretManager = async () => (await client.getSecret("CLIENT_SECRET_GCP_SECRET_MANAGER")).secretValue;
export const getClientSlugVercel = async () => (await client.getSecret("CLIENT_SLUG_VERCEL")).secretValue;
export const getClientIdGoogleLogin = async () => (await client.getSecret("CLIENT_ID_GOOGLE_LOGIN")).secretValue;

View File

@@ -1,32 +1,20 @@
import { Request, Response } from "express";
import fs from "fs";
import path from "path";
import jwt from "jsonwebtoken";
import * as bigintConversion from "bigint-conversion";
// eslint-disable-next-line @typescript-eslint/no-var-requires
const jsrp = require("jsrp");
import {
LoginSRPDetail,
TokenVersion,
User,
} from "../../models";
import { LoginSRPDetail, TokenVersion, User } from "../../models";
import { clearTokens, createToken, issueAuthTokens } from "../../helpers/auth";
import { checkUserDevice } from "../../helpers/user";
import {
ACTION_LOGIN,
ACTION_LOGOUT,
} from "../../variables";
import {
BadRequestError,
UnauthorizedRequestError,
} from "../../utils/errors";
import { ACTION_LOGIN, ACTION_LOGOUT } from "../../variables";
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
import { EELogService } from "../../ee/services";
import { getUserAgentType } from "../../utils/posthog";
import {
getHttpsEnabled,
getJwtAuthLifetime,
getJwtAuthSecret,
getJwtRefreshSecret,
getJwtRefreshSecret
} from "../../config";
import { ActorType } from "../../ee/models";
@@ -44,13 +32,10 @@ declare module "jsonwebtoken" {
* @returns
*/
export const login1 = async (req: Request, res: Response) => {
const {
email,
clientPublicKey,
}: { email: string; clientPublicKey: string } = req.body;
const { email, clientPublicKey }: { email: string; clientPublicKey: string } = req.body;
const user = await User.findOne({
email,
email
}).select("+salt +verifier");
if (!user) throw new Error("Failed to find user");
@@ -59,21 +44,25 @@ export const login1 = async (req: Request, res: Response) => {
server.init(
{
salt: user.salt,
verifier: user.verifier,
verifier: user.verifier
},
async () => {
// generate server-side public key
const serverPublicKey = server.getPublicKey();
await LoginSRPDetail.findOneAndReplace({ email: email }, {
email: email,
clientPublicKey: clientPublicKey,
serverBInt: bigintConversion.bigintToBuf(server.bInt),
}, { upsert: true, returnNewDocument: false })
await LoginSRPDetail.findOneAndReplace(
{ email: email },
{
email: email,
clientPublicKey: clientPublicKey,
serverBInt: bigintConversion.bigintToBuf(server.bInt)
},
{ upsert: true, returnNewDocument: false }
);
return res.status(200).send({
serverPublicKey,
salt: user.salt,
salt: user.salt
});
}
);
@@ -89,15 +78,19 @@ export const login1 = async (req: Request, res: Response) => {
export const login2 = async (req: Request, res: Response) => {
const { email, clientProof } = req.body;
const user = await User.findOne({
email,
email
}).select("+salt +verifier +publicKey +encryptedPrivateKey +iv +tag");
if (!user) throw new Error("Failed to find user");
const loginSRPDetailFromDB = await LoginSRPDetail.findOneAndDelete({ email: email })
const loginSRPDetailFromDB = await LoginSRPDetail.findOneAndDelete({ email: email });
if (!loginSRPDetailFromDB) {
return BadRequestError(Error("It looks like some details from the first login are not found. Please try login one again"))
return BadRequestError(
Error(
"It looks like some details from the first login are not found. Please try login one again"
)
);
}
const server = new jsrp.server();
@@ -105,7 +98,7 @@ export const login2 = async (req: Request, res: Response) => {
{
salt: user.salt,
verifier: user.verifier,
b: loginSRPDetailFromDB.serverBInt,
b: loginSRPDetailFromDB.serverBInt
},
async () => {
server.setClientPublicKey(loginSRPDetailFromDB.clientPublicKey);
@@ -117,13 +110,13 @@ export const login2 = async (req: Request, res: Response) => {
await checkUserDevice({
user,
ip: req.realIP,
userAgent: req.headers["user-agent"] ?? "",
userAgent: req.headers["user-agent"] ?? ""
});
const tokens = await issueAuthTokens({
const tokens = await issueAuthTokens({
userId: user._id,
ip: req.realIP,
userAgent: req.headers["user-agent"] ?? "",
userAgent: req.headers["user-agent"] ?? ""
});
// store (refresh) token in httpOnly cookie
@@ -131,20 +124,21 @@ export const login2 = async (req: Request, res: Response) => {
httpOnly: true,
path: "/",
sameSite: "strict",
secure: await getHttpsEnabled(),
secure: await getHttpsEnabled()
});
const loginAction = await EELogService.createAction({
name: ACTION_LOGIN,
userId: user._id,
userId: user._id
});
loginAction && await EELogService.createLog({
userId: user._id,
actions: [loginAction],
channel: getUserAgentType(req.headers["user-agent"]),
ipAddress: req.realIP,
});
loginAction &&
(await EELogService.createLog({
userId: user._id,
actions: [loginAction],
channel: getUserAgentType(req.headers["user-agent"]),
ipAddress: req.realIP
}));
// return (access) token in response
return res.status(200).send({
@@ -152,12 +146,12 @@ export const login2 = async (req: Request, res: Response) => {
publicKey: user.publicKey,
encryptedPrivateKey: user.encryptedPrivateKey,
iv: user.iv,
tag: user.tag,
tag: user.tag
});
}
return res.status(400).send({
message: "Failed to authenticate. Try again?",
message: "Failed to authenticate. Try again?"
});
}
);
@@ -171,7 +165,7 @@ export const login2 = async (req: Request, res: Response) => {
*/
export const logout = async (req: Request, res: Response) => {
if (req.authData.actor.type === ActorType.USER && req.authData.tokenVersionId) {
await clearTokens(req.authData.tokenVersionId)
await clearTokens(req.authData.tokenVersionId);
}
// clear httpOnly cookie
@@ -179,49 +173,44 @@ export const logout = async (req: Request, res: Response) => {
httpOnly: true,
path: "/",
sameSite: "strict",
secure: (await getHttpsEnabled()) as boolean,
secure: (await getHttpsEnabled()) as boolean
});
const logoutAction = await EELogService.createAction({
name: ACTION_LOGOUT,
userId: req.user._id,
userId: req.user._id
});
logoutAction && await EELogService.createLog({
userId: req.user._id,
actions: [logoutAction],
channel: getUserAgentType(req.headers["user-agent"]),
ipAddress: req.realIP,
});
logoutAction &&
(await EELogService.createLog({
userId: req.user._id,
actions: [logoutAction],
channel: getUserAgentType(req.headers["user-agent"]),
ipAddress: req.realIP
}));
return res.status(200).send({
message: "Successfully logged out.",
message: "Successfully logged out."
});
};
export const getCommonPasswords = async (req: Request, res: Response) => {
const commonPasswords = fs.readFileSync(
path.resolve(__dirname, "../../data/" + "common_passwords.txt"),
"utf8"
).split("\n");
return res.status(200).send(commonPasswords);
}
export const revokeAllSessions = async (req: Request, res: Response) => {
await TokenVersion.updateMany({
user: req.user._id,
}, {
$inc: {
refreshVersion: 1,
accessVersion: 1,
await TokenVersion.updateMany(
{
user: req.user._id
},
});
{
$inc: {
refreshVersion: 1,
accessVersion: 1
}
}
);
return res.status(200).send({
message: "Successfully revoked all sessions.",
});
}
message: "Successfully revoked all sessions."
});
};
/**
* Return user is authenticated
@@ -231,9 +220,9 @@ export const revokeAllSessions = async (req: Request, res: Response) => {
*/
export const checkAuth = async (req: Request, res: Response) => {
return res.status(200).send({
message: "Authenticated",
message: "Authenticated"
});
}
};
/**
* Return new JWT access token by first validating the refresh token
@@ -244,47 +233,47 @@ export const checkAuth = async (req: Request, res: Response) => {
export const getNewToken = async (req: Request, res: Response) => {
const refreshToken = req.cookies.jid;
if (!refreshToken) throw BadRequestError({
message: "Failed to find refresh token in request cookies"
});
if (!refreshToken)
throw BadRequestError({
message: "Failed to find refresh token in request cookies"
});
const decodedToken = <jwt.UserIDJwtPayload>(
jwt.verify(refreshToken, await getJwtRefreshSecret())
);
const decodedToken = <jwt.UserIDJwtPayload>jwt.verify(refreshToken, await getJwtRefreshSecret());
const user = await User.findOne({
_id: decodedToken.userId,
_id: decodedToken.userId
}).select("+publicKey +refreshVersion +accessVersion");
if (!user) throw new Error("Failed to authenticate unfound user");
if (!user?.publicKey)
throw new Error("Failed to authenticate not fully set up account");
if (!user?.publicKey) throw new Error("Failed to authenticate not fully set up account");
const tokenVersion = await TokenVersion.findById(decodedToken.tokenVersionId);
if (!tokenVersion) throw UnauthorizedRequestError({
message: "Failed to validate refresh token",
});
if (!tokenVersion)
throw UnauthorizedRequestError({
message: "Failed to validate refresh token"
});
if (decodedToken.refreshVersion !== tokenVersion.refreshVersion) throw BadRequestError({
message: "Failed to validate refresh token",
});
if (decodedToken.refreshVersion !== tokenVersion.refreshVersion)
throw BadRequestError({
message: "Failed to validate refresh token"
});
const token = createToken({
payload: {
userId: decodedToken.userId,
tokenVersionId: tokenVersion._id.toString(),
accessVersion: tokenVersion.refreshVersion,
accessVersion: tokenVersion.refreshVersion
},
expiresIn: await getJwtAuthLifetime(),
secret: await getJwtAuthSecret(),
secret: await getJwtAuthSecret()
});
return res.status(200).send({
token,
token
});
};
export const handleAuthProviderCallback = (req: Request, res: Response) => {
res.redirect(`/login/provider/success?token=${encodeURIComponent(req.providerAuthToken)}`);
}
};

View File

@@ -547,6 +547,57 @@ export const getIntegrationAuthNorthflankSecretGroups = async (req: Request, res
});
}
/**
* Return list of build configs for TeamCity project with id [appId]
* @param req
* @param res
* @returns
*/
export const getIntegrationAuthTeamCityBuildConfigs = async (req: Request, res: Response) => {
const appId = req.query.appId as string;
interface TeamCityBuildConfig {
id: string;
name: string;
projectName: string;
projectId: string;
href: string;
webUrl: string;
}
interface GetTeamCityBuildConfigsRes {
count: number;
href: string;
buildType: TeamCityBuildConfig[];
}
if (appId && appId !== "") {
const { data: { buildType } } = (
await standardRequest.get<GetTeamCityBuildConfigsRes>(`${req.integrationAuth.url}/app/rest/buildTypes`, {
params: {
locator: `project:${appId}`
},
headers: {
Authorization: `Bearer ${req.accessToken}`,
Accept: "application/json",
},
})
);
return res.status(200).send({
buildConfigs: buildType.map((buildConfig) => ({
name: buildConfig.name,
buildConfigId: buildConfig.id
}))
});
}
return res.status(200).send({
buildConfigs: []
});
}
/**
* Delete integration authorization with id [integrationAuthId]
* @param req

View File

@@ -1,9 +1,8 @@
import { Request, Response } from "express";
import { Types } from "mongoose";
import { Integration } from "../../models";
import { Folder, Integration } from "../../models";
import { EventService } from "../../services";
import { eventStartIntegration } from "../../events";
import Folder from "../../models/folder";
import { getFolderByPath } from "../../services/FolderService";
import { BadRequestError } from "../../utils/errors";
import { EEAuditLogService } from "../../ee/services";
@@ -30,7 +29,8 @@ export const createIntegration = async (req: Request, res: Response) => {
owner,
path,
region,
secretPath
secretPath,
metadata
} = req.body;
const folders = await Folder.findOne({
@@ -65,7 +65,8 @@ export const createIntegration = async (req: Request, res: Response) => {
region,
secretPath,
integration: req.integrationAuth.integration,
integrationAuth: new Types.ObjectId(integrationAuthId)
integrationAuth: new Types.ObjectId(integrationAuthId),
metadata
}).save();
if (integration) {

View File

@@ -1,8 +1,6 @@
import { Request, Response } from "express";
import { isValidScope, validateMembership } from "../../helpers";
import { ServiceTokenData } from "../../models";
import Folder from "../../models/folder";
import SecretImport from "../../models/secretImports";
import { Folder, SecretImport, ServiceTokenData } from "../../models";
import { getAllImportedSecrets } from "../../services/SecretImportService";
import { getFolderWithPathFromId } from "../../services/FolderService";
import { BadRequestError, ResourceNotFoundError,UnauthorizedRequestError } from "../../utils/errors";

View File

@@ -4,8 +4,7 @@ import { EventType, FolderVersion } from "../../ee/models";
import { EEAuditLogService, EESecretService } from "../../ee/services";
import { validateMembership } from "../../helpers/membership";
import { isValidScope } from "../../helpers/secrets";
import { Secret, ServiceTokenData } from "../../models";
import Folder from "../../models/folder";
import { Folder, Secret, ServiceTokenData } from "../../models";
import {
appendFolder,
deleteFolderById,

View File

@@ -2,7 +2,7 @@ import { Request, Response } from "express";
import { Types } from "mongoose";
import { client, getRootEncryptionKey } from "../../config";
import { validateMembership } from "../../helpers";
import Webhook from "../../models/webhooks";
import { Webhook } from "../../models";
import { getWebhookPayload, triggerWebhookRequest } from "../../services/WebhookService";
import { BadRequestError, ResourceNotFoundError } from "../../utils/errors";
import { EEAuditLogService } from "../../ee/services";

View File

@@ -1,6 +1,5 @@
import { Request, Response } from "express";
import mongoose, { Types } from "mongoose";
import Secret, { ISecret } from "../../models/secret";
import {
CreateSecretRequestBody,
ModifySecretRequestBody,
@@ -20,7 +19,7 @@ import {
SECRET_SHARED
} from "../../variables";
import { TelemetryService } from "../../services";
import { User } from "../../models";
import { ISecret, Secret, User } from "../../models";
import { AccountNotFoundError } from "../../utils/errors";
/**

View File

@@ -1,6 +1,6 @@
import { Types } from "mongoose";
import { Request, Response } from "express";
import { ISecret, Secret, ServiceTokenData } from "../../models";
import { Folder, ISecret, Secret, ServiceTokenData, Tag } from "../../models";
import { AuditLog, EventType, IAction, SecretVersion } from "../../ee/models";
import {
ACTION_ADD_SECRETS,
@@ -9,6 +9,7 @@ import {
ACTION_UPDATE_SECRETS,
ALGORITHM_AES_256_GCM,
ENCODING_SCHEME_UTF8,
K8_USER_AGENT_NAME,
SECRET_PERSONAL
} from "../../variables";
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
@@ -23,10 +24,8 @@ import {
userHasWorkspaceAccess,
userHasWriteOnlyAbility
} from "../../ee/helpers/checkMembershipPermissions";
import Tag from "../../models/tag";
import _ from "lodash";
import { BatchSecret, BatchSecretRequest } from "../../types/secret";
import Folder from "../../models/folder";
import {
getFolderByPath,
getFolderIdFromServiceToken,
@@ -59,7 +58,7 @@ export const batchSecrets = async (req: Request, res: Response) => {
let secretPath = req.body.secretPath as string;
let folderId = req.body.folderId as string;
const createSecrets: BatchSecret[] = [];
const updateSecrets: BatchSecret[] = [];
const deleteSecrets: { _id: Types.ObjectId, secretName: string; }[] = [];
@@ -154,7 +153,7 @@ export const batchSecrets = async (req: Request, res: Response) => {
};
})
});
const auditLogs = await Promise.all(
createdSecrets.map((secret, index) => {
return EEAuditLogService.createAuditLog(
@@ -178,7 +177,7 @@ export const batchSecrets = async (req: Request, res: Response) => {
);
await AuditLog.insertMany(auditLogs);
const addAction = (await EELogService.createAction({
name: ACTION_ADD_SECRETS,
userId: req.user?._id,
@@ -234,6 +233,9 @@ export const batchSecrets = async (req: Request, res: Response) => {
$inc: {
version: 1
},
$unset: {
"metadata.source": true as const
},
...u,
_id: new Types.ObjectId(u._id)
}
@@ -277,7 +279,7 @@ export const batchSecrets = async (req: Request, res: Response) => {
$in: updateSecrets.map((u) => new Types.ObjectId(u._id))
}
});
const auditLogs = await Promise.all(
updateSecrets.map((secret) => {
return EEAuditLogService.createAuditLog(
@@ -329,26 +331,26 @@ export const batchSecrets = async (req: Request, res: Response) => {
// handle delete secrets
if (deleteSecrets.length > 0) {
const deleteSecretIds: Types.ObjectId[] = deleteSecrets.map((s) => s._id);
const deletedSecretsObj = (await Secret.find({
_id: {
$in: deleteSecretIds
}
}))
.reduce(
(obj: any, secret: ISecret) => ({
...obj,
[secret._id.toString()]: secret
}),
{}
);
.reduce(
(obj: any, secret: ISecret) => ({
...obj,
[secret._id.toString()]: secret
}),
{}
);
await Secret.deleteMany({
_id: {
$in: deleteSecretIds
}
});
await EESecretService.markDeletedSecretVersions({
secretIds: deleteSecretIds
});
@@ -949,7 +951,7 @@ export const getSecrets = async (req: Request, res: Response) => {
channel,
ipAddress: req.realIP
}));
await EEAuditLogService.createAuditLog(
req.authData,
{
@@ -966,21 +968,36 @@ export const getSecrets = async (req: Request, res: Response) => {
);
const postHogClient = await TelemetryService.getPostHogClient();
// reduce the number of events captured
let shouldRecordK8Event = false
if (req.authData.userAgent == K8_USER_AGENT_NAME) {
const randomNumber = Math.random();
if (randomNumber > 0.9) {
shouldRecordK8Event = true
}
}
if (postHogClient) {
postHogClient.capture({
event: "secrets pulled",
distinctId: await TelemetryService.getDistinctId({
authData: req.authData
}),
properties: {
numberOfSecrets: secrets.length,
environment,
workspaceId,
channel,
folderId,
userAgent: req.headers?.["user-agent"]
}
});
const shouldCapture = req.authData.userAgent !== K8_USER_AGENT_NAME || shouldRecordK8Event;
const approximateForNoneCapturedEvents = secrets.length * 10
if (shouldCapture) {
postHogClient.capture({
event: "secrets pulled",
distinctId: await TelemetryService.getDistinctId({
authData: req.authData
}),
properties: {
numberOfSecrets: shouldRecordK8Event ? approximateForNoneCapturedEvents : secrets.length,
environment,
workspaceId,
folderId,
channel: req.authData.userAgentType,
userAgent: req.authData.userAgent
}
});
}
}
return res.status(200).send({
@@ -1087,10 +1104,10 @@ export const updateSecrets = async (req: Request, res: Response) => {
tags,
...(secretCommentCiphertext !== undefined && secretCommentIV && secretCommentTag
? {
secretCommentCiphertext,
secretCommentIV,
secretCommentTag
}
secretCommentCiphertext,
secretCommentIV,
secretCommentTag
}
: {})
}
}

View File

@@ -1,15 +1,15 @@
import { Request, Response } from "express";
import { Types } from "mongoose";
import { Membership, Secret } from "../../models";
import Tag from "../../models/tag";
import { Membership, Secret, Tag } from "../../models";
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
export const createWorkspaceTag = async (req: Request, res: Response) => {
const { workspaceId } = req.params;
const { name, slug } = req.body;
const { name, slug, tagColor } = req.body;
const tagToCreate = {
name,
tagColor,
workspace: new Types.ObjectId(workspaceId),
slug,
user: new Types.ObjectId(req.user._id),

View File

@@ -6,10 +6,9 @@ import { BotService } from "../../services";
import { containsGlobPatterns, repackageSecretToRaw } from "../../helpers/secrets";
import { encryptSymmetric128BitHexKeyUTF8 } from "../../utils/crypto";
import { getAllImportedSecrets } from "../../services/SecretImportService";
import Folder from "../../models/folder";
import { Folder, IServiceTokenData } from "../../models";
import { getFolderByPath } from "../../services/FolderService";
import { BadRequestError } from "../../utils/errors";
import { IServiceTokenData } from "../../models";
import { requireWorkspaceAuth } from "../../middleware";
import { ADMIN, MEMBER, PERMISSION_READ_SECRETS } from "../../variables";
@@ -23,6 +22,7 @@ export const getSecretsRaw = async (req: Request, res: Response) => {
let workspaceId = req.query.workspaceId as string;
let environment = req.query.environment as string;
let secretPath = req.query.secretPath as string;
const folderId = req.query.folderId as string | undefined;
const includeImports = req.query.include_imports as string;
// if the service token has single scope, it will get all secrets for that scope by default
@@ -47,6 +47,7 @@ export const getSecretsRaw = async (req: Request, res: Response) => {
const secrets = await SecretService.getSecrets({
workspaceId: new Types.ObjectId(workspaceId),
environment,
folderId,
secretPath,
authData: req.authData
});
@@ -284,11 +285,13 @@ export const getSecrets = async (req: Request, res: Response) => {
const workspaceId = req.query.workspaceId as string;
const environment = req.query.environment as string;
const secretPath = req.query.secretPath as string;
const folderId = req.query.folderId as string | undefined;
const includeImports = req.query.include_imports as string;
const secrets = await SecretService.getSecrets({
workspaceId: new Types.ObjectId(workspaceId),
environment,
folderId,
secretPath,
authData: req.authData
});

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
import { Request, Response } from "express";
import { PipelineStage, Types } from "mongoose";
import { Membership, Secret, ServiceTokenData, User } from "../../../models";
import { Folder, Membership, Secret, ServiceTokenData, TFolderSchema, User } from "../../../models";
import {
ActorType,
AuditLog,
@@ -18,7 +18,7 @@ import {
} from "../../models";
import { EESecretService } from "../../services";
import { getLatestSecretVersionIds } from "../../helpers/secretVersion";
import Folder, { TFolderSchema } from "../../../models/folder";
// import Folder, { TFolderSchema } from "../../../models/folder";
import { searchByFolderId } from "../../../services/FolderService";
import { EEAuditLogService, EELicenseService } from "../../services";
import { extractIPDetails, isValidIpOrCidr } from "../../../utils/ip";

View File

@@ -117,7 +117,7 @@ const secretVersionSchema = new Schema<ISecretVersion>(
ref: "Tag",
type: [Schema.Types.ObjectId],
default: [],
},
}
},
{
timestamps: true,

View File

@@ -14,7 +14,7 @@ import {
} from "../variables";
import { client, getEncryptionKey, getRootEncryptionKey } from "../config";
import { InternalServerError } from "../utils/errors";
import Folder from "../models/folder";
import { Folder } from "../models";
import { getFolderByPath } from "../services/FolderService";
import { getAllImportedSecrets } from "../services/SecretImportService";
import { expandSecrets } from "./secrets";

View File

@@ -9,7 +9,6 @@ import {
INTEGRATION_VERCEL
} from "../variables";
import { UnauthorizedRequestError } from "../utils/errors";
import { syncSecretsToActiveIntegrationsQueue } from "../queues/integrations/syncSecretsToThirdPartyServices"
interface Update {
workspace: string;

View File

@@ -7,11 +7,13 @@ import {
UpdateSecretParams
} from "../interfaces/services/SecretService";
import {
Folder,
ISecret,
IServiceTokenData,
Secret,
SecretBlindIndexData,
ServiceTokenData
ServiceTokenData,
TFolderRootSchema
} from "../models";
import { EventType, SecretVersion } from "../ee/models";
import {
@@ -29,6 +31,7 @@ import {
ALGORITHM_AES_256_GCM,
ENCODING_SCHEME_BASE64,
ENCODING_SCHEME_UTF8,
K8_USER_AGENT_NAME,
SECRET_PERSONAL,
SECRET_SHARED
} from "../variables";
@@ -45,7 +48,6 @@ import { getAuthDataPayloadIdObj, getAuthDataPayloadUserObj } from "../utils/aut
import { getFolderByPath, getFolderIdFromServiceToken } from "../services/FolderService";
import picomatch from "picomatch";
import path from "path";
import Folder, { TFolderRootSchema } from "../models/folder";
export const isValidScope = (
authPayload: IServiceTokenData,
@@ -393,7 +395,8 @@ export const createSecretHelper = async ({
secretCommentTag,
folder: folderId,
algorithm: ALGORITHM_AES_256_GCM,
keyEncoding: ENCODING_SCHEME_UTF8
keyEncoding: ENCODING_SCHEME_UTF8,
metadata
}).save();
const secretVersion = new SecretVersion({
@@ -496,6 +499,7 @@ export const getSecretsHelper = async ({
workspaceId,
environment,
authData,
folderId,
secretPath = "/"
}: GetSecretsParams) => {
let secrets: ISecret[] = [];
@@ -505,7 +509,10 @@ export const getSecretsHelper = async ({
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
}
}
const folderId = await getFolderIdFromServiceToken(workspaceId, environment, secretPath);
if (!folderId) {
folderId = await getFolderIdFromServiceToken(workspaceId, environment, secretPath);
}
// get personal secrets first
secrets = await Secret.find({
@@ -567,21 +574,36 @@ export const getSecretsHelper = async ({
const postHogClient = await TelemetryService.getPostHogClient();
if (postHogClient) {
postHogClient.capture({
event: "secrets pulled",
distinctId: await TelemetryService.getDistinctId({
authData
}),
properties: {
numberOfSecrets: secrets.length,
environment,
workspaceId,
folderId,
channel: authData.userAgentType,
userAgent: authData.userAgent
}
});
// reduce the number of events captured
let shouldRecordK8Event = false
if (authData.userAgent == K8_USER_AGENT_NAME) {
const randomNumber = Math.random();
if (randomNumber > 0.9) {
shouldRecordK8Event = true
}
}
const numberOfSignupSecrets = (secrets.filter((secret) => secret?.metadata?.source === "signup")).length;
const atLeastOneNonSignUpSecret = (secrets.length - numberOfSignupSecrets > 0)
if (postHogClient && atLeastOneNonSignUpSecret) {
const shouldCapture = authData.userAgent !== K8_USER_AGENT_NAME || shouldRecordK8Event;
const approximateForNoneCapturedEvents = secrets.length * 10
if (shouldCapture) {
postHogClient.capture({
event: "secrets pulled",
distinctId: await TelemetryService.getDistinctId({ authData }),
properties: {
numberOfSecrets: shouldRecordK8Event ? approximateForNoneCapturedEvents : secrets.length,
environment,
workspaceId,
folderId,
channel: authData.userAgentType,
userAgent: authData.userAgent
}
});
}
}
return secrets;
@@ -680,7 +702,7 @@ export const getSecretHelper = async ({
if (postHogClient) {
postHogClient.capture({
event: "secrets pull",
event: "secrets pulled",
distinctId: await TelemetryService.getDistinctId({
authData
}),

View File

@@ -24,7 +24,7 @@ import {
secretSnapshot as eeSecretSnapshotRouter,
users as eeUsersRouter,
workspace as eeWorkspaceRouter,
secretScanning as v1SecretScanningRouter,
secretScanning as v1SecretScanningRouter
} from "./ee/routes/v1";
import {
auth as v1AuthRouter,
@@ -58,7 +58,7 @@ import {
signup as v2SignupRouter,
tags as v2TagsRouter,
users as v2UsersRouter,
workspace as v2WorkspaceRouter,
workspace as v2WorkspaceRouter
} from "./routes/v2";
import {
auth as v3AuthRouter,
@@ -70,14 +70,21 @@ import { healthCheck } from "./routes/status";
import { getLogger } from "./utils/logger";
import { RouteNotFoundError } from "./utils/errors";
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 { 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 () => {
await setup();
await EELicenseService.initGlobalFeatureSet();
@@ -94,11 +101,15 @@ const main = async () => {
})
);
if (await getSecretScanningGitAppId() && await getSecretScanningWebhookSecret() && await getSecretScanningPrivateKey()) {
if (
(await getSecretScanningGitAppId()) &&
(await getSecretScanningWebhookSecret()) &&
(await getSecretScanningPrivateKey())
) {
const probot = new Probot({
appId: await getSecretScanningGitAppId(),
privateKey: await getSecretScanningPrivateKey(),
secret: await getSecretScanningWebhookSecret(),
secret: await getSecretScanningWebhookSecret()
});
if ((await getNodeEnv()) != "production") {
@@ -106,12 +117,14 @@ const main = async () => {
source: await getSecretScanningWebhookProxy(),
target: "http://backend:4000/ss-webhook",
logger: console
})
});
smee.start()
smee.start();
}
app.use(createNodeMiddleware(GithubSecretScanningService, { probot, webhooksPath: "/ss-webhook" })); // secret scanning webhook
app.use(
createNodeMiddleware(GithubSecretScanningService, { probot, webhooksPath: "/ss-webhook" })
); // secret scanning webhook
}
if ((await getNodeEnv()) === "production") {
@@ -207,8 +220,8 @@ const main = async () => {
server.on("close", async () => {
await DatabaseService.closeDatabase();
syncSecretsToThirdPartyServices.close()
githubPushEventSecretScan.close()
syncSecretsToThirdPartyServices.close();
githubPushEventSecretScan.close();
});
return server;

View File

@@ -18,6 +18,10 @@ import {
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
INTEGRATION_FLYIO,
INTEGRATION_FLYIO_API_URL,
INTEGRATION_GCP_API_URL,
INTEGRATION_GCP_SECRET_MANAGER,
INTEGRATION_GCP_SECRET_MANAGER_SERVICE_NAME,
INTEGRATION_GCP_SERVICE_USAGE_URL,
INTEGRATION_GITHUB,
INTEGRATION_GITLAB,
INTEGRATION_GITLAB_API_URL,
@@ -79,6 +83,11 @@ const getApps = async ({
}) => {
let apps: App[] = [];
switch (integrationAuth.integration) {
case INTEGRATION_GCP_SECRET_MANAGER:
apps = await getAppsGCPSecretManager({
accessToken,
});
break;
case INTEGRATION_AZURE_KEY_VAULT:
apps = [];
break;
@@ -210,6 +219,96 @@ const getApps = async ({
return apps;
};
/**
* Return list of apps for GCP secret manager integration
* @param {Object} obj
* @param {String} obj.accessToken - access token for GCP API
* @returns {Object[]} apps - list of GCP projects
* @returns {String} apps.name - name of GCP project
* @returns {String} apps.appId - id of GCP project
*/
const getAppsGCPSecretManager = async ({ accessToken }: { accessToken: string }) => {
interface GCPApp {
projectNumber: string;
projectId: string;
lifecycleState: "ACTIVE" | "LIFECYCLE_STATE_UNSPECIFIED" | "DELETE_REQUESTED" | "DELETE_IN_PROGRESS";
name: string;
createTime: string;
parent: {
type: "organization" | "folder" | "project";
id: string;
}
}
interface GCPGetProjectsRes {
projects: GCPApp[];
nextPageToken?: string;
}
interface GCPGetServiceRes {
name: string;
parent: string;
state: "ENABLED" | "DISABLED" | "STATE_UNSPECIFIED"
}
let gcpApps: GCPApp[] = [];
const apps: App[] = [];
const pageSize = 100;
let pageToken: string | undefined;
let hasMorePages = true;
while (hasMorePages) {
const params = new URLSearchParams({
pageSize: String(pageSize),
...(pageToken ? { pageToken } : {})
});
const res: GCPGetProjectsRes = (await standardRequest.get(`${INTEGRATION_GCP_API_URL}/v1/projects`, {
params,
headers: {
"Authorization": `Bearer ${accessToken}`,
"Accept-Encoding": "application/json"
}
})
)
.data;
gcpApps = gcpApps.concat(res.projects);
if (!res.nextPageToken) {
hasMorePages = false;
}
pageToken = res.nextPageToken;
}
for await (const gcpApp of gcpApps) {
try {
const res: GCPGetServiceRes = (await standardRequest.get(
`${INTEGRATION_GCP_SERVICE_USAGE_URL}/v1/projects/${gcpApp.projectId}/services/${INTEGRATION_GCP_SECRET_MANAGER_SERVICE_NAME}`, {
headers: {
"Authorization": `Bearer ${accessToken}`,
"Accept-Encoding": "application/json"
}
}
)).data;
if (res.state === "ENABLED") {
apps.push({
name: gcpApp.name,
appId: gcpApp.projectId
});
}
} catch {
continue;
}
}
return apps;
};
/**
* Return list of apps for Heroku integration
* @param {Object} obj
@@ -751,7 +850,7 @@ const getAppsTeamCity = async ({
},
})
).data.project.slice(1);
const apps = res.map((a: any) => {
return {
name: a.name,

View File

@@ -4,6 +4,8 @@ import {
INTEGRATION_AZURE_TOKEN_URL,
INTEGRATION_BITBUCKET,
INTEGRATION_BITBUCKET_TOKEN_URL,
INTEGRATION_GCP_SECRET_MANAGER,
INTEGRATION_GCP_TOKEN_URL,
INTEGRATION_GITHUB,
INTEGRATION_GITHUB_TOKEN_URL,
INTEGRATION_GITLAB,
@@ -13,17 +15,19 @@ import {
INTEGRATION_NETLIFY,
INTEGRATION_NETLIFY_TOKEN_URL,
INTEGRATION_VERCEL,
INTEGRATION_VERCEL_TOKEN_URL,
INTEGRATION_VERCEL_TOKEN_URL
} from "../variables";
import {
getClientIdAzure,
getClientIdBitBucket,
getClientIdGCPSecretManager,
getClientIdGitHub,
getClientIdGitLab,
getClientIdNetlify,
getClientIdVercel,
getClientSecretAzure,
getClientSecretBitBucket,
getClientSecretGCPSecretManager,
getClientSecretGitHub,
getClientSecretGitLab,
getClientSecretHeroku,
@@ -113,6 +117,11 @@ const exchangeCode = async ({
let obj = {} as any;
switch (integration) {
case INTEGRATION_GCP_SECRET_MANAGER:
obj = await exchangeCodeGCP({
code,
});
break;
case INTEGRATION_AZURE_KEY_VAULT:
obj = await exchangeCodeAzure({
code,
@@ -153,6 +162,40 @@ const exchangeCode = async ({
return obj;
};
/**
* Return [accessToken] for GCP OAuth2 code-token exchange
* @param {Object} obj
* @param {String} obj.code - code for code-token exchange
* @returns {Object} obj2
* @returns {String} obj2.accessToken - access token for GCP API
* @returns {String} obj2.refreshToken - refresh token for GCP API
* @returns {Date} obj2.accessExpiresAt - date of expiration for access token
*/
const exchangeCodeGCP = async ({ code }: { code: string }) => {
const accessExpiresAt = new Date();
const res: ExchangeCodeAzureResponse = (
await standardRequest.post(
INTEGRATION_GCP_TOKEN_URL,
new URLSearchParams({
grant_type: "authorization_code",
code: code,
client_id: await getClientIdGCPSecretManager(),
client_secret: await getClientSecretGCPSecretManager(),
redirect_uri: `${await getSiteURL()}/integrations/gcp-secret-manager/oauth2/callback`,
} as any)
)
).data;
accessExpiresAt.setSeconds(accessExpiresAt.getSeconds() + res.expires_in);
return {
accessToken: res.access_token,
refreshToken: res.refresh_token,
accessExpiresAt,
};
};
/**
* Return [accessToken] for Azure OAuth2 code-token exchange
* @param param0

View File

@@ -26,6 +26,8 @@ import {
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
INTEGRATION_FLYIO,
INTEGRATION_FLYIO_API_URL,
INTEGRATION_GCP_SECRET_MANAGER,
INTEGRATION_GCP_SECRET_MANAGER_URL,
INTEGRATION_GITHUB,
INTEGRATION_GITLAB,
INTEGRATION_GITLAB_API_URL,
@@ -92,6 +94,13 @@ const syncSecrets = async ({
accessToken: string;
}) => {
switch (integration.integration) {
case INTEGRATION_GCP_SECRET_MANAGER:
await syncSecretsGCPSecretManager({
integration,
secrets,
accessToken
});
break;
case INTEGRATION_AZURE_KEY_VAULT:
await syncSecretsAzureKeyVault({
integration,
@@ -286,6 +295,165 @@ const syncSecrets = async ({
}
};
/**
* Sync/push [secrets] to GCP secret manager project
* @param {Object} obj
* @param {IIntegration} obj.integration - integration details
* @param {Object} obj.secrets - secrets to push to integration (object where keys are secret keys and values are secret values)
* @param {String} obj.accessToken - access token for GCP secret manager
*/
const syncSecretsGCPSecretManager = async ({
integration,
secrets,
accessToken
}: {
integration: IIntegration;
secrets: Record<string, { value: string; comment?: string }>;
accessToken: string;
}) => {
interface GCPSecret {
name: string;
createTime: string;
}
interface GCPSMListSecretsRes {
secrets?: GCPSecret[];
totalSize?: number;
nextPageToken?: string;
}
let gcpSecrets: GCPSecret[] = [];
const pageSize = 100;
let pageToken: string | undefined;
let hasMorePages = true;
while (hasMorePages) {
const params = new URLSearchParams({
pageSize: String(pageSize),
...(pageToken ? { pageToken } : {})
});
const res: GCPSMListSecretsRes = (await standardRequest.get(
`${INTEGRATION_GCP_SECRET_MANAGER_URL}/v1beta1/projects/${integration.appId}/secrets`,
{
params,
headers: {
"Authorization": `Bearer ${accessToken}`,
"Accept-Encoding": "application/json"
}
}
)).data;
if (res.secrets) {
gcpSecrets = gcpSecrets.concat(res.secrets);
}
if (!res.nextPageToken) {
hasMorePages = false;
}
pageToken = res.nextPageToken;
}
const res: { [key: string]: string; } = {};
interface GCPLatestSecretVersionAccess {
name: string;
payload: {
data: string;
}
}
for await (const gcpSecret of gcpSecrets) {
const arr = gcpSecret.name.split("/");
const key = arr[arr.length - 1];
const secretLatest: GCPLatestSecretVersionAccess = (await standardRequest.get(
`${INTEGRATION_GCP_SECRET_MANAGER_URL}/v1beta1/projects/${integration.appId}/secrets/${key}/versions/latest:access`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json"
}
}
)).data;
res[key] = Buffer.from(secretLatest.payload.data, "base64").toString("utf-8");
}
for await (const key of Object.keys(secrets)) {
if (!(key in res)) {
// case: create secret
await standardRequest.post(
`${INTEGRATION_GCP_SECRET_MANAGER_URL}/v1beta1/projects/${integration.appId}/secrets`,
{
replication: {
automatic: {}
}
},
{
params: {
secretId: key
},
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json"
}
}
);
await standardRequest.post(
`${INTEGRATION_GCP_SECRET_MANAGER_URL}/v1beta1/projects/${integration.appId}/secrets/${key}:addVersion`,
{
payload: {
data: Buffer.from(secrets[key].value).toString("base64")
}
},
{
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json"
}
}
);
}
}
for await (const key of Object.keys(res)) {
if (!(key in secrets)) {
// case: delete secret
await standardRequest.delete(
`${INTEGRATION_GCP_SECRET_MANAGER_URL}/v1beta1/projects/${integration.appId}/secrets/${key}`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json"
}
}
);
} else {
// case: update secret
if (secrets[key].value !== res[key]) {
await standardRequest.post(
`${INTEGRATION_GCP_SECRET_MANAGER_URL}/v1beta1/projects/${integration.appId}/secrets/${key}:addVersion`,
{
payload: {
data: Buffer.from(secrets[key].value).toString("base64")
}
},
{
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json"
}
}
);
}
}
}
}
/**
* Sync/push [secrets] to Azure Key Vault with vault URI [integration.app]
* @param {Object} obj
@@ -1838,7 +2006,7 @@ const syncSecretsCheckly = async ({
secrets: Record<string, { value: string; comment?: string }>;
accessToken: string;
}) => {
// get secrets from travis-ci
const getSecretsRes = (
await standardRequest.get(`${INTEGRATION_CHECKLY_API_URL}/v1/variables`, {
headers: {
@@ -1860,7 +2028,6 @@ const syncSecretsCheckly = async ({
if (!(key in getSecretsRes)) {
// case: secret does not exist in checkly
// -> add secret
await standardRequest.post(
`${INTEGRATION_CHECKLY_API_URL}/v1/variables`,
{
@@ -2019,7 +2186,7 @@ const syncSecretsTerraformCloud = async ({
};
/**
* Sync/push [secrets] to TeamCity project
* Sync/push [secrets] to TeamCity project (and optionally build config)
* @param {Object} obj
* @param {IIntegration} obj.integration - integration details
* @param {Object} obj.secrets - secrets to push to integration
@@ -2041,57 +2208,124 @@ const syncSecretsTeamCity = async ({
value: string;
}
// get secrets from Teamcity
const res = (
await standardRequest.get(
`${integrationAuth.url}/app/rest/projects/id:${integration.appId}/parameters`,
interface TeamCityBuildConfigParameter {
name: string;
value: string;
inherited: boolean;
}
interface GetTeamCityBuildConfigParametersRes {
href: string;
count: number;
property: TeamCityBuildConfigParameter[];
}
if (integration.targetEnvironment && integration.targetEnvironmentId) {
// case: sync to specific build-config in TeamCity project
const res = (await standardRequest.get<GetTeamCityBuildConfigParametersRes>(
`${integrationAuth.url}/app/rest/buildTypes/${integration.targetEnvironmentId}/parameters`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json"
}
Accept: "application/json",
},
}
)
).data.property.reduce((obj: any, secret: TeamCitySecret) => {
const secretName = secret.name.replace(/^env\./, "");
return {
...obj,
[secretName]: secret.value
};
}, {});
for await (const key of Object.keys(secrets)) {
if (!(key in res) || (key in res && secrets[key] !== res[key])) {
// case: secret does not exist in TeamCity or secret value has changed
// -> create/update secret
await standardRequest.post(
`${integrationAuth.url}/app/rest/projects/id:${integration.appId}/parameters`,
))
.data
.property
.filter((parameter) => !parameter.inherited)
.reduce((obj: any, secret: TeamCitySecret) => {
const secretName = secret.name.replace(/^env\./, "");
return {
...obj,
[secretName]: secret.value
};
}, {});
for await (const key of Object.keys(secrets)) {
if (!(key in res) || (key in res && secrets[key].value !== res[key])) {
// case: secret does not exist in TeamCity or secret value has changed
// -> create/update secret
await standardRequest.post(`${integrationAuth.url}/app/rest/buildTypes/${integration.targetEnvironmentId}/parameters`,
{
name: `env.${key}`,
value: secrets[key]
name:`env.${key}`,
value: secrets[key].value
},
{
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json"
}
}
);
Accept: "application/json",
},
});
}
}
}
for await (const key of Object.keys(res)) {
if (!(key in secrets)) {
// delete secret
await standardRequest.delete(
`${integrationAuth.url}/app/rest/projects/id:${integration.appId}/parameters/env.${key}`,
for await (const key of Object.keys(res)) {
if (!(key in secrets)) {
// delete secret
await standardRequest.delete(
`${integrationAuth.url}/app/rest/buildTypes/${integration.targetEnvironmentId}/parameters/env.${key}`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json"
}
}
);
}
}
} else {
// case: sync to TeamCity project
const res = (
await standardRequest.get(
`${integrationAuth.url}/app/rest/projects/id:${integration.appId}/parameters`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json"
}
}
);
)
).data.property.reduce((obj: any, secret: TeamCitySecret) => {
const secretName = secret.name.replace(/^env\./, "");
return {
...obj,
[secretName]: secret.value
};
}, {});
for await (const key of Object.keys(secrets)) {
if (!(key in res) || (key in res && secrets[key] !== res[key])) {
// case: secret does not exist in TeamCity or secret value has changed
// -> create/update secret
await standardRequest.post(
`${integrationAuth.url}/app/rest/projects/id:${integration.appId}/parameters`,
{
name: `env.${key}`,
value: secrets[key].value
},
{
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json"
}
}
);
}
}
for await (const key of Object.keys(res)) {
if (!(key in secrets)) {
// delete secret
await standardRequest.delete(
`${integrationAuth.url}/app/rest/projects/id:${integration.appId}/parameters/env.${key}`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json"
}
}
);
}
}
}
};

View File

@@ -25,6 +25,7 @@ export interface CreateSecretParams {
export interface GetSecretsParams {
workspaceId: Types.ObjectId;
environment: string;
folderId?: string;
secretPath: string;
authData: AuthData;
}

View File

@@ -36,6 +36,4 @@ const apiKeyDataSchema = new Schema<IAPIKeyData>(
}
);
const APIKeyData = model<IAPIKeyData>("APIKeyData", apiKeyDataSchema);
export default APIKeyData;
export const APIKeyData = model<IAPIKeyData>("APIKeyData", apiKeyDataSchema);

View File

@@ -68,9 +68,7 @@ const backupPrivateKeySchema = new Schema<IBackupPrivateKey>(
}
);
const BackupPrivateKey = model<IBackupPrivateKey>(
export const BackupPrivateKey = model<IBackupPrivateKey>(
"BackupPrivateKey",
backupPrivateKeySchema
);
export default BackupPrivateKey;

View File

@@ -74,6 +74,4 @@ const botSchema = new Schema<IBot>(
}
);
const Bot = model<IBot>("Bot", botSchema);
export default Bot;
export const Bot = model<IBot>("Bot", botSchema);

View File

@@ -40,6 +40,4 @@ const botKeySchema = new Schema<IBotKey>(
}
);
const BotKey = model<IBotKey>("BotKey", botKeySchema);
export default BotKey;
export const BotKey = model<IBotKey>("BotKey", botKeySchema);

View File

@@ -93,6 +93,4 @@ const botOrgSchema = new Schema<IBotOrg>(
}
);
const BotOrg = model<IBotOrg>("BotOrg", botOrgSchema);
export default BotOrg;
export const BotOrg = model<IBotOrg>("BotOrg", botOrgSchema);

View File

@@ -51,6 +51,4 @@ const folderRootSchema = new Schema<TFolderRootSchema>(
}
);
const Folder = model<TFolderRootSchema>("Folder", folderRootSchema);
export default Folder;
export const Folder = model<TFolderRootSchema>("Folder", folderRootSchema);

View File

@@ -23,9 +23,7 @@ const incidentContactOrgSchema = new Schema<IIncidentContactOrg>(
}
);
const IncidentContactOrg = model<IIncidentContactOrg>(
export const IncidentContactOrg = model<IIncidentContactOrg>(
"IncidentContactOrg",
incidentContactOrgSchema
);
export default IncidentContactOrg;
);

View File

@@ -1,89 +1,30 @@
import BackupPrivateKey, { IBackupPrivateKey } from "./backupPrivateKey";
import Bot, { IBot } from "./bot";
import BotOrg, { IBotOrg } from "./botOrg";
import BotKey, { IBotKey } from "./botKey";
import IncidentContactOrg, { IIncidentContactOrg } from "./incidentContactOrg";
import Integration, { IIntegration } from "./integration";
import IntegrationAuth, { IIntegrationAuth } from "./integrationAuth";
import Key, { IKey } from "./key";
import Membership, { IMembership } from "./membership";
import MembershipOrg, { IMembershipOrg } from "./membershipOrg";
import Organization, { IOrganization } from "./organization";
import Secret, { ISecret } from "./secret";
import Folder, { TFolderRootSchema, TFolderSchema } from "./folder";
import SecretImport, { ISecretImports } from "./secretImports";
import SecretBlindIndexData, { ISecretBlindIndexData } from "./secretBlindIndexData";
import ServiceToken, { IServiceToken } from "./serviceToken";
import ServiceAccount, { IServiceAccount } from "./serviceAccount"; // new
import ServiceAccountKey, { IServiceAccountKey } from "./serviceAccountKey"; // new
import ServiceAccountOrganizationPermission, { IServiceAccountOrganizationPermission } from "./serviceAccountOrganizationPermission"; // new
import ServiceAccountWorkspacePermission, { IServiceAccountWorkspacePermission } from "./serviceAccountWorkspacePermission"; // new
import TokenData, { ITokenData } from "./tokenData";
import User, { AuthMethod, IUser } from "./user";
import UserAction, { IUserAction } from "./userAction";
import Workspace, { IWorkspace } from "./workspace";
import ServiceTokenData, { IServiceTokenData } from "./serviceTokenData";
import APIKeyData, { IAPIKeyData } from "./apiKeyData";
import LoginSRPDetail, { ILoginSRPDetail } from "./loginSRPDetail";
import TokenVersion, { ITokenVersion } from "./tokenVersion";
export {
AuthMethod,
BackupPrivateKey,
IBackupPrivateKey,
Bot,
IBot,
BotOrg,
IBotOrg,
BotKey,
IBotKey,
IncidentContactOrg,
IIncidentContactOrg,
Integration,
IIntegration,
IntegrationAuth,
IIntegrationAuth,
Key,
IKey,
Membership,
IMembership,
MembershipOrg,
IMembershipOrg,
Organization,
IOrganization,
Secret,
ISecret,
Folder,
TFolderRootSchema,
TFolderSchema,
SecretImport,
ISecretImports,
SecretBlindIndexData,
ISecretBlindIndexData,
ServiceToken,
IServiceToken,
ServiceAccount,
IServiceAccount,
ServiceAccountKey,
IServiceAccountKey,
ServiceAccountOrganizationPermission,
IServiceAccountOrganizationPermission,
ServiceAccountWorkspacePermission,
IServiceAccountWorkspacePermission,
TokenData,
ITokenData,
User,
IUser,
UserAction,
IUserAction,
Workspace,
IWorkspace,
ServiceTokenData,
IServiceTokenData,
APIKeyData,
IAPIKeyData,
LoginSRPDetail,
ILoginSRPDetail,
TokenVersion,
ITokenVersion
};
export * from "./backupPrivateKey";
export * from "./bot";
export * from "./botOrg";
export * from "./botKey";
export * from "./incidentContactOrg";
export * from "./integration/integration";
export * from "./integrationAuth";
export * from "./key";
export * from "./membership";
export * from "./membershipOrg";
export * from "./organization";
export * from "./secret";
export * from "./tag";
export * from "./folder";
export * from "./secretImports";
export * from "./secretBlindIndexData";
export * from "./serviceToken";
export * from "./serviceAccount";
export * from "./serviceAccountKey";
export * from "./serviceAccountOrganizationPermission";
export * from "./serviceAccountWorkspacePermission";
export * from "./tokenData";
export * from "./user";
export * from "./userAction";
export * from "./workspace";
export * from "./serviceTokenData";
export * from "./apiKeyData";
export * from "./loginSRPDetail";
export * from "./tokenVersion";
export * from "./webhooks";

View File

@@ -0,0 +1 @@
export * from "./integration";

View File

@@ -10,6 +10,7 @@ import {
INTEGRATION_CODEFRESH,
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
INTEGRATION_FLYIO,
INTEGRATION_GCP_SECRET_MANAGER,
INTEGRATION_GITHUB,
INTEGRATION_GITLAB,
INTEGRATION_HASHICORP_VAULT,
@@ -25,8 +26,9 @@ import {
INTEGRATION_TRAVISCI,
INTEGRATION_VERCEL,
INTEGRATION_WINDMILL
} from "../variables";
} from "../../variables";
import { Schema, Types, model } from "mongoose";
import { Metadata } from "./types";
export interface IIntegration {
_id: Types.ObjectId;
@@ -70,8 +72,10 @@ export interface IIntegration {
| "digital-ocean-app-platform"
| "cloud-66"
| "northflank"
| "windmill";
| "windmill"
| "gcp-secret-manager";
integrationAuth: Types.ObjectId;
metadata: Metadata;
}
const integrationSchema = new Schema<IIntegration>(
@@ -167,7 +171,8 @@ const integrationSchema = new Schema<IIntegration>(
INTEGRATION_BITBUCKET,
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
INTEGRATION_CLOUD_66,
INTEGRATION_NORTHFLANK
INTEGRATION_NORTHFLANK,
INTEGRATION_GCP_SECRET_MANAGER
],
required: true,
},
@@ -180,6 +185,9 @@ const integrationSchema = new Schema<IIntegration>(
type: String,
required: true,
default: "/",
},
metadata: {
type: Schema.Types.Mixed
}
},
{
@@ -187,6 +195,4 @@ const integrationSchema = new Schema<IIntegration>(
}
);
const Integration = model<IIntegration>("Integration", integrationSchema);
export default Integration;
export const Integration = model<IIntegration>("Integration", integrationSchema);

View File

@@ -0,0 +1,3 @@
export type Metadata = {
secretSuffix?: string;
}

View File

@@ -12,6 +12,7 @@ import {
INTEGRATION_CODEFRESH,
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
INTEGRATION_FLYIO,
INTEGRATION_GCP_SECRET_MANAGER,
INTEGRATION_GITHUB,
INTEGRATION_GITLAB,
INTEGRATION_HASHICORP_VAULT,
@@ -58,7 +59,8 @@ export interface IIntegrationAuth extends Document {
| "terraform-cloud"
| "teamcity"
| "northflank"
| "windmill";
| "windmill"
| "gcp-secret-manager";
teamId: string;
accountId: string;
url: string;
@@ -111,7 +113,8 @@ const integrationAuthSchema = new Schema<IIntegrationAuth>(
INTEGRATION_BITBUCKET,
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
INTEGRATION_CLOUD_66,
INTEGRATION_NORTHFLANK
INTEGRATION_NORTHFLANK,
INTEGRATION_GCP_SECRET_MANAGER
],
required: true,
},
@@ -190,9 +193,7 @@ const integrationAuthSchema = new Schema<IIntegrationAuth>(
}
);
const IntegrationAuth = model<IIntegrationAuth>(
export const IntegrationAuth = model<IIntegrationAuth>(
"IntegrationAuth",
integrationAuthSchema
);
export default IntegrationAuth;
);

View File

@@ -40,6 +40,4 @@ const keySchema = new Schema<IKey>(
}
);
const Key = model<IKey>("Key", keySchema);
export default Key;
export const Key = model<IKey>("Key", keySchema);

View File

@@ -24,6 +24,4 @@ const loginSRPDetailSchema = new Schema<ILoginSRPDetail>(
}
);
const LoginSRPDetail = model("LoginSRPDetail", loginSRPDetailSchema);
export default LoginSRPDetail;
export const LoginSRPDetail = model("LoginSRPDetail", loginSRPDetailSchema);

View File

@@ -52,6 +52,4 @@ const membershipSchema = new Schema<IMembership>(
}
);
const Membership = model<IMembership>("Membership", membershipSchema);
export default Membership;
export const Membership = model<IMembership>("Membership", membershipSchema);

View File

@@ -39,9 +39,7 @@ const membershipOrgSchema = new Schema(
}
);
const MembershipOrg = model<IMembershipOrg>(
export const MembershipOrg = model<IMembershipOrg>(
"MembershipOrg",
membershipOrgSchema
);
export default MembershipOrg;
);

View File

@@ -21,6 +21,4 @@ const organizationSchema = new Schema<IOrganization>(
}
);
const Organization = model<IOrganization>("Organization", organizationSchema);
export default Organization;
export const Organization = model<IOrganization>("Organization", organizationSchema);

View File

@@ -31,6 +31,9 @@ export interface ISecret {
keyEncoding: "utf8" | "base64";
tags?: string[];
folder?: string;
metadata?: {
[key: string]: string;
}
}
const secretSchema = new Schema<ISecret>(
@@ -131,6 +134,9 @@ const secretSchema = new Schema<ISecret>(
type: String,
default: "root",
},
metadata: {
type: Schema.Types.Mixed
}
},
{
timestamps: true,
@@ -139,6 +145,4 @@ const secretSchema = new Schema<ISecret>(
secretSchema.index({ tags: 1 }, { background: true });
const Secret = model<ISecret>("Secret", secretSchema);
export default Secret;
export const Secret = model<ISecret>("Secret", secretSchema);

View File

@@ -1,5 +1,5 @@
import mongoose, { Schema, model } from "mongoose";
import Secret, { ISecret } from "./secret";
import { ISecret, Secret } from "./secret";
interface ISecretApprovalRequest {
secret: mongoose.Types.ObjectId;
@@ -78,6 +78,4 @@ const secretApprovalRequestSchema = new Schema<ISecretApprovalRequest>(
}
);
const SecretApprovalRequest = model<ISecretApprovalRequest>("SecretApprovalRequest", secretApprovalRequestSchema);
export default SecretApprovalRequest;
export const SecretApprovalRequest = model<ISecretApprovalRequest>("SecretApprovalRequest", secretApprovalRequestSchema);

View File

@@ -53,6 +53,4 @@ const secretBlindIndexDataSchema = new Schema<ISecretBlindIndexData>(
}
);
const SecretBlindIndexData = model<ISecretBlindIndexData>("SecretBlindIndexData", secretBlindIndexDataSchema);
export default SecretBlindIndexData;
export const SecretBlindIndexData = model<ISecretBlindIndexData>("SecretBlindIndexData", secretBlindIndexDataSchema);

View File

@@ -48,5 +48,4 @@ const secretImportSchema = new Schema<ISecretImports>(
}
);
const SecretImport = model<ISecretImports>("SecretImports", secretImportSchema);
export default SecretImport;
export const SecretImport = model<ISecretImports>("SecretImports", secretImportSchema);

View File

@@ -48,6 +48,4 @@ const serviceAccountSchema = new Schema<IServiceAccount>(
}
);
const ServiceAccount = model<IServiceAccount>("ServiceAccount", serviceAccountSchema);
export default ServiceAccount;
export const ServiceAccount = model<IServiceAccount>("ServiceAccount", serviceAccountSchema);

View File

@@ -39,6 +39,4 @@ const serviceAccountKeySchema = new Schema<IServiceAccountKey>(
}
);
const ServiceAccountKey = model<IServiceAccountKey>("ServiceAccountKey", serviceAccountKeySchema);
export default ServiceAccountKey;
export const ServiceAccountKey = model<IServiceAccountKey>("ServiceAccountKey", serviceAccountKeySchema);

View File

@@ -18,6 +18,4 @@ const serviceAccountOrganizationPermissionSchema = new Schema<IServiceAccountOrg
}
);
const ServiceAccountOrganizationPermission = model<IServiceAccountOrganizationPermission>("ServiceAccountOrganizationPermission", serviceAccountOrganizationPermissionSchema);
export default ServiceAccountOrganizationPermission;
export const ServiceAccountOrganizationPermission = model<IServiceAccountOrganizationPermission>("ServiceAccountOrganizationPermission", serviceAccountOrganizationPermissionSchema);

View File

@@ -39,6 +39,4 @@ const serviceAccountWorkspacePermissionSchema = new Schema<IServiceAccountWorksp
}
);
const ServiceAccountWorkspacePermission = model<IServiceAccountWorkspacePermission>("ServiceAccountWorkspacePermission", serviceAccountWorkspacePermissionSchema);
export default ServiceAccountWorkspacePermission;
export const ServiceAccountWorkspacePermission = model<IServiceAccountWorkspacePermission>("ServiceAccountWorkspacePermission", serviceAccountWorkspacePermissionSchema);

View File

@@ -56,6 +56,4 @@ const serviceTokenSchema = new Schema<IServiceToken>(
}
);
const ServiceToken = model<IServiceToken>("ServiceToken", serviceTokenSchema);
export default ServiceToken;
export const ServiceToken = model<IServiceToken>("ServiceToken", serviceTokenSchema);

View File

@@ -89,6 +89,4 @@ const serviceTokenDataSchema = new Schema<IServiceTokenData>(
}
);
const ServiceTokenData = model<IServiceTokenData>("ServiceTokenData", serviceTokenDataSchema);
export default ServiceTokenData;
export const ServiceTokenData = model<IServiceTokenData>("ServiceTokenData", serviceTokenDataSchema);

View File

@@ -3,6 +3,7 @@ import { Schema, Types, model } from "mongoose";
export interface ITag {
_id: Types.ObjectId;
name: string;
tagColor: string;
slug: string;
user: Types.ObjectId;
workspace: Types.ObjectId;
@@ -15,6 +16,11 @@ const tagSchema = new Schema<ITag>(
required: true,
trim: true,
},
tagColor: {
type: String,
required: false,
trim: true,
},
slug: {
type: String,
required: true,
@@ -44,6 +50,4 @@ const tagSchema = new Schema<ITag>(
tagSchema.index({ slug: 1, workspace: 1 }, { unique: true })
tagSchema.index({ workspace: 1 })
const Tag = model<ITag>("Tag", tagSchema);
export default Tag;
export const Tag = model<ITag>("Tag", tagSchema);

View File

@@ -27,6 +27,4 @@ const tokenSchema = new Schema<IToken>({
tokenSchema.index({ email: 1 });
const Token = model<IToken>("Token", tokenSchema);
export default Token;
export const Token = model<IToken>("Token", tokenSchema);

View File

@@ -50,6 +50,4 @@ const tokenDataSchema = new Schema<ITokenData>({
timestamps: true,
});
const TokenData = model<ITokenData>("TokenData", tokenDataSchema);
export default TokenData;
export const TokenData = model<ITokenData>("TokenData", tokenDataSchema);

View File

@@ -42,6 +42,4 @@ const tokenVersionSchema = new Schema<ITokenVersion>(
}
);
const TokenVersion = model<ITokenVersion>("TokenVersion", tokenVersionSchema);
export default TokenVersion;
export const TokenVersion = model<ITokenVersion>("TokenVersion", tokenVersionSchema);

View File

@@ -121,6 +121,4 @@ const userSchema = new Schema<IUser>(
}
);
const User = model<IUser>("User", userSchema);
export default User;
export const User = model<IUser>("User", userSchema);

View File

@@ -23,6 +23,4 @@ const userActionSchema = new Schema<IUserAction>(
}
);
const UserAction = model<IUserAction>("UserAction", userActionSchema);
export default UserAction;
export const UserAction = model<IUserAction>("UserAction", userActionSchema);

View File

@@ -80,6 +80,4 @@ const WebhookSchema = new Schema<IWebhook>(
}
);
const Webhook = model<IWebhook>("Webhook", WebhookSchema);
export default Webhook;
export const Webhook = model<IWebhook>("Webhook", WebhookSchema);

View File

@@ -49,6 +49,4 @@ const workspaceSchema = new Schema<IWorkspace>({
},
});
const Workspace = model<IWorkspace>("Workspace", workspaceSchema);
export default Workspace;
export const Workspace = model<IWorkspace>("Workspace", workspaceSchema);

View File

@@ -1,6 +1,5 @@
import Queue, { Job } from "bull";
import Integration from "../../models/integration";
import IntegrationAuth from "../../models/integrationAuth";
import { Integration, IntegrationAuth } from "../../models";
import { BotService } from "../../services";
import { getIntegrationAuthAccessHelper } from "../../helpers";
import { syncSecrets } from "../../integrations/sync"
@@ -36,6 +35,14 @@ syncSecretsToThirdPartyServices.process(async (job: Job) => {
secretPath: integration.secretPath
});
const suffixedSecrets: any = {};
if (integration.metadata?.secretSuffix) {
for (const key in secrets) {
const newKey = key + integration.metadata?.secretSuffix;
suffixedSecrets[newKey] = secrets[key];
}
}
const integrationAuth = await IntegrationAuth.findById(integration.integrationAuth);
if (!integrationAuth) throw new Error("Failed to find integration auth");
@@ -49,7 +56,7 @@ syncSecretsToThirdPartyServices.process(async (job: Job) => {
await syncSecrets({
integration,
integrationAuth,
secrets,
secrets: Object.keys(suffixedSecrets).length !== 0 ? suffixedSecrets : secrets,
accessId: access.accessId === undefined ? null : access.accessId,
accessToken: access.accessToken
});

View File

@@ -1,16 +1,16 @@
import Queue, { Job } from "bull";
import { ProbotOctokit } from "probot"
import { Commit, Committer, Repository } from "@octokit/webhooks-types";
import { Commit } 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 { ADMIN, OWNER } 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');
export const githubPushEventSecretScan = new Queue("github-push-event-secret-scanning", "redis://redis:6379");
type TScanPushEventQueueDetails = {
organizationId: string,

View File

@@ -8,7 +8,8 @@ import { AuthMode } from "../../variables";
router.post("/token", validateRequest, authController.getNewToken);
router.post( // TODO endpoint: deprecate (moved to api/v3/auth/login1)
router.post(
// TODO endpoint: deprecate (moved to api/v3/auth/login1)
"/login1",
authLimiter,
body("email").exists().trim().notEmpty().toLowerCase(),
@@ -17,7 +18,8 @@ router.post( // TODO endpoint: deprecate (moved to api/v3/auth/login1)
authController.login1
);
router.post( // TODO endpoint: deprecate (moved to api/v3/auth/login2)
router.post(
// TODO endpoint: deprecate (moved to api/v3/auth/login2)
"/login2",
authLimiter,
body("email").exists().trim().notEmpty().toLowerCase(),
@@ -30,7 +32,7 @@ router.post(
"/logout",
authLimiter,
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
acceptedAuthModes: [AuthMode.JWT]
}),
authController.logout
);
@@ -38,24 +40,19 @@ router.post(
router.post(
"/checkAuth",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
acceptedAuthModes: [AuthMode.JWT]
}),
authController.checkAuth
);
router.get(
"/common-passwords",
authLimiter,
authController.getCommonPasswords
);
router.delete( // TODO endpoint: deprecate (moved to DELETE v2/users/me/sessions)
router.delete(
// TODO endpoint: deprecate (moved to DELETE v2/users/me/sessions)
"/sessions",
authLimiter,
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
acceptedAuthModes: [AuthMode.JWT]
}),
authController.revokeAllSessions
);
export default router;
export default router;

View File

@@ -37,6 +37,8 @@ router.post(
body("owner").trim(),
body("path").trim(),
body("region").trim(),
body("metadata").optional().isObject().withMessage("Metadata should be an object"),
body("metadata.secretSuffix").optional().isString().withMessage("Suffix should be a string"),
validateRequest,
integrationController.createIntegration
);
@@ -89,4 +91,4 @@ router.post(
integrationController.manualSync
);
export default router;
export default router;

View File

@@ -168,6 +168,20 @@ router.get(
integrationAuthController.getIntegrationAuthNorthflankSecretGroups
);
router.get(
"/:integrationAuthId/teamcity/build-configs",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireIntegrationAuthorizationAuth({
acceptedRoles: [ADMIN, MEMBER],
}),
param("integrationAuthId").exists().isString(),
query("appId").exists().isString(),
validateRequest,
integrationAuthController.getIntegrationAuthTeamCityBuildConfigs
);
router.delete(
"/:integrationAuthId",
requireAuth({

View File

@@ -16,7 +16,7 @@ import {
router.post(
"/:workspaceId/environments",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY],
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
@@ -32,7 +32,7 @@ router.post(
router.put(
"/:workspaceId/environments",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY],
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
@@ -49,7 +49,7 @@ router.put(
router.patch(
"/:workspaceId/environments",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY],
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
@@ -67,7 +67,7 @@ router.patch(
router.delete(
"/:workspaceId/environments",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY],
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN],
@@ -82,7 +82,7 @@ router.delete(
router.get(
"/:workspaceId/environments",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY],
}),
requireWorkspaceAuth({
acceptedRoles: [MEMBER, ADMIN],

View File

@@ -48,6 +48,7 @@ router.post(
}),
param("workspaceId").exists().trim(),
body("name").exists().trim(),
body("tagColor").exists().trim(),
body("slug").exists().trim(),
validateRequest,
tagController.createWorkspaceTag

View File

@@ -1,4 +1,4 @@
import express, { Request, Response } from "express";
import express from "express";
const router = express.Router();
import { requireAuth, requireWorkspaceAuth, validateRequest } from "../../middleware";
import { body, param, query } from "express-validator";
@@ -17,6 +17,7 @@ router.get(
"/raw",
query("workspaceId").optional().isString().trim(),
query("environment").optional().isString().trim(),
query("folderId").optional().isString().trim(),
query("secretPath").default("/").isString().trim(),
query("include_imports").optional().isBoolean().default(false),
validateRequest,
@@ -144,6 +145,7 @@ router.get(
"/",
query("workspaceId").exists().isString().trim(),
query("environment").exists().isString().trim(),
query("folderId").optional().isString().trim(),
query("secretPath").default("/").isString().trim(),
validateRequest,
requireAuth({

View File

@@ -1,6 +1,6 @@
import { nanoid } from "nanoid";
import { Types } from "mongoose";
import Folder, { TFolderSchema } from "../models/folder";
import { Folder, TFolderSchema } from "../models";
import path from "path";
type TAppendFolderDTO = {

View File

@@ -1,7 +1,10 @@
import { Types } from "mongoose";
import Folder from "../models/folder";
import Secret, { ISecret } from "../models/secret";
import SecretImport from "../models/secretImports";
import {
Folder,
ISecret,
Secret,
SecretImport
} from "../models";
import { getFolderByPath } from "./FolderService";
type TSecretImportFid = { environment: string; folderId: string; secretPath: string };
@@ -54,6 +57,14 @@ export const getAllImportedSecrets = async (
type: "shared"
}
},
{
$lookup: {
from: "tags", // note this is the name of the collection in the database, not the Mongoose model name
localField: "tags",
foreignField: "_id",
as: "tags"
}
},
{
$group: {
_id: {

View File

@@ -3,7 +3,7 @@ import crypto from "crypto";
import { Types } from "mongoose";
import picomatch from "picomatch";
import { client, getRootEncryptionKey } from "../config";
import Webhook, { IWebhook } from "../models/webhooks";
import { IWebhook, Webhook } from "../models";
export const triggerWebhookRequest = async (
{ url, encryptedSecretKey, iv, tag }: IWebhook,

View File

@@ -540,20 +540,26 @@ export const backfillIntegration = async () => {
};
export const backfillServiceTokenMultiScope = async () => {
await ServiceTokenData.updateMany(
{
scopes: {
$exists: false
}
},
[
{
$set: {
scopes: [{ environment: "$environment", secretPath: "$secretPath" }]
const documentsToUpdate = await ServiceTokenData.find({ scopes: { $exists: false } });
for (const doc of documentsToUpdate) {
// Cast doc to any to bypass TypeScript's type checks
const anyDoc = doc as any;
const environment = anyDoc.environment;
const secretPath = anyDoc.secretPath;
if (environment && secretPath) {
const updatedScopes = [
{
environment: environment,
secretPath: secretPath
}
}
]
);
];
await ServiceTokenData.updateOne({ _id: doc._id }, { $set: { scopes: updatedScopes } });
}
}
console.log("Migration: Service token migration v2 complete");
};
@@ -649,24 +655,25 @@ export const backfillUserAuthMethods = async () => {
}
);
await User.updateMany(
{
authProvider: {
$exists: true
},
authMethods: {
$exists: false
}
},
[
{
$set: {
authMethods: ["$authProvider"]
const documentsToUpdate = await User.find({
authProvider: { $exists: true },
authMethods: { $exists: false }
});
for (const doc of documentsToUpdate) {
// Cast doc to any to bypass TypeScript's type checks
const anyDoc = doc as any;
const authProvider = anyDoc.authProvider;
const authMethods = [authProvider];
await User.updateOne(
{ _id: doc._id },
{
$set: { authMethods: authMethods },
$unset: { authProvider: 1, authId: 1 }
}
},
{
$unset: ["authProvider", "authId"]
}
]
);
);
}
}

View File

@@ -2,4 +2,6 @@ export enum AuthMode {
JWT = "jwt",
SERVICE_TOKEN = "serviceToken",
API_KEY = "apiKey"
}
}
export const K8_USER_AGENT_NAME = "k8-operator"

View File

@@ -1,17 +1,19 @@
import {
getClientIdAzure,
getClientIdBitBucket,
getClientIdGCPSecretManager,
getClientIdGitHub,
getClientIdGitLab,
getClientIdHeroku,
getClientIdNetlify,
getClientSlugVercel,
getClientSlugVercel
} from "../config";
// integrations
export const INTEGRATION_AZURE_KEY_VAULT = "azure-key-vault";
export const INTEGRATION_AWS_PARAMETER_STORE = "aws-parameter-store";
export const INTEGRATION_AWS_SECRET_MANAGER = "aws-secret-manager";
export const INTEGRATION_GCP_SECRET_MANAGER = "gcp-secret-manager";
export const INTEGRATION_HEROKU = "heroku";
export const INTEGRATION_VERCEL = "vercel";
export const INTEGRATION_NETLIFY = "netlify";
@@ -36,35 +38,37 @@ export const INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM = "digital-ocean-app-platfor
export const INTEGRATION_CLOUD_66 = "cloud-66";
export const INTEGRATION_NORTHFLANK = "northflank";
export const INTEGRATION_SET = new Set([
INTEGRATION_GCP_SECRET_MANAGER,
INTEGRATION_AZURE_KEY_VAULT,
INTEGRATION_HEROKU,
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY,
INTEGRATION_GITHUB,
INTEGRATION_GITLAB,
INTEGRATION_RENDER,
INTEGRATION_FLYIO,
INTEGRATION_CIRCLECI,
INTEGRATION_LARAVELFORGE,
INTEGRATION_TRAVISCI,
INTEGRATION_TEAMCITY,
INTEGRATION_SUPABASE,
INTEGRATION_CHECKLY,
INTEGRATION_TERRAFORM_CLOUD,
INTEGRATION_HASHICORP_VAULT,
INTEGRATION_CLOUDFLARE_PAGES,
INTEGRATION_CODEFRESH,
INTEGRATION_WINDMILL,
INTEGRATION_BITBUCKET,
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
INTEGRATION_CLOUD_66,
INTEGRATION_NORTHFLANK
INTEGRATION_HEROKU,
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY,
INTEGRATION_GITHUB,
INTEGRATION_GITLAB,
INTEGRATION_RENDER,
INTEGRATION_FLYIO,
INTEGRATION_CIRCLECI,
INTEGRATION_LARAVELFORGE,
INTEGRATION_TRAVISCI,
INTEGRATION_TEAMCITY,
INTEGRATION_SUPABASE,
INTEGRATION_CHECKLY,
INTEGRATION_TERRAFORM_CLOUD,
INTEGRATION_HASHICORP_VAULT,
INTEGRATION_CLOUDFLARE_PAGES,
INTEGRATION_CODEFRESH,
INTEGRATION_WINDMILL,
INTEGRATION_BITBUCKET,
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
INTEGRATION_CLOUD_66,
INTEGRATION_NORTHFLANK
]);
// integration types
export const INTEGRATION_OAUTH2 = "oauth2";
// integration oauth endpoints
export const INTEGRATION_GCP_TOKEN_URL = "https://accounts.google.com/o/oauth2/token";
export const INTEGRATION_AZURE_TOKEN_URL = "https://login.microsoftonline.com/common/oauth2/v2.0/token";
export const INTEGRATION_HEROKU_TOKEN_URL = "https://id.heroku.com/oauth/token";
export const INTEGRATION_VERCEL_TOKEN_URL =
@@ -76,6 +80,7 @@ export const INTEGRATION_GITLAB_TOKEN_URL = "https://gitlab.com/oauth/token";
export const INTEGRATION_BITBUCKET_TOKEN_URL = "https://bitbucket.org/site/oauth2/access_token"
// integration apps endpoints
export const INTEGRATION_GCP_API_URL = "https://cloudresourcemanager.googleapis.com";
export const INTEGRATION_HEROKU_API_URL = "https://api.heroku.com";
export const INTEGRATION_GITLAB_API_URL = "https://gitlab.com/api";
export const INTEGRATION_VERCEL_API_URL = "https://api.vercel.com";
@@ -97,6 +102,10 @@ export const INTEGRATION_DIGITAL_OCEAN_API_URL = "https://api.digitalocean.com";
export const INTEGRATION_CLOUD_66_API_URL = "https://app.cloud66.com/api";
export const INTEGRATION_NORTHFLANK_API_URL = "https://api.northflank.com";
export const INTEGRATION_GCP_SECRET_MANAGER_SERVICE_NAME = "secretmanager.googleapis.com"
export const INTEGRATION_GCP_SECRET_MANAGER_URL = `https://${INTEGRATION_GCP_SECRET_MANAGER_SERVICE_NAME}`;
export const INTEGRATION_GCP_SERVICE_USAGE_URL = "https://serviceusage.googleapis.com";
export const getIntegrationOptions = async () => {
const INTEGRATION_OPTIONS = [
{
@@ -272,12 +281,12 @@ export const getIntegrationOptions = async () => {
docsLink: "",
},
{
name: "Google Cloud Platform",
slug: "gcp",
name: "GCP Secret Manager",
slug: "gcp-secret-manager",
image: "Google Cloud Platform.png",
isAvailable: false,
type: "",
clientId: "",
isAvailable: true,
type: "oauth",
clientId: await getClientIdGCPSecretManager(),
docsLink: ""
},
{

View File

@@ -22,7 +22,7 @@ Resources:
DocumentDBCluster:
Type: "AWS::DocDB::DBCluster"
Properties:
EngineVersion: 4.0.0
EngineVersion: 5.0.0
StorageEncrypted: true
MasterUsername: !Ref DocumentDBUsername
MasterUserPassword: !Ref DocumentDBPassword
@@ -38,7 +38,7 @@ Resources:
Type: "AWS::DocDB::DBClusterParameterGroup"
Properties:
Description: "description"
Family: "docdb4.0"
Family: "docdb5.0"
Parameters:
tls: "disabled"
ttl_monitor: "disabled"
@@ -97,6 +97,7 @@ Resources:
echo "JWT_SERVICE_SECRET=${!JWT_SERVICE_SECRET}" >> .env
echo "MONGO_URL=${!DOCUMENT_DB_CONNECTION_URL}" >> .env
echo "HTTPS_ENABLED=false" >> .env
echo "REDIS_URL=redis://redis:6379" >> .env
docker-compose up -d
@@ -174,4 +175,4 @@ Metadata:
x: 270
"y": 90
z: 1
embeds: []
embeds: []

View File

@@ -0,0 +1,4 @@
---
title: "Create"
openapi: "POST /api/v2/workspace/{workspaceId}/environments"
---

View File

@@ -0,0 +1,4 @@
---
title: "Delete"
openapi: "DELETE /api/v2/workspace/{workspaceId}/environments"
---

View File

@@ -0,0 +1,4 @@
---
title: "List"
openapi: "GET /api/v2/workspace/{workspaceId}/environments"
---

View File

@@ -0,0 +1,4 @@
---
title: "Update"
openapi: "PUT /api/v2/workspace/{workspaceId}/environments"
---

View File

@@ -1,6 +1,6 @@
---
title: "Retrieve"
openapi: "GET /api/v3/secrets/{secretName}"
title: "List"
openapi: "GET /api/v3/secrets/"
---
<Tip>

View File

@@ -1,6 +1,6 @@
---
title: "Retrieve All"
openapi: "GET /api/v3/secrets/"
title: "Retrieve"
openapi: "GET /api/v3/secrets/{secretName}"
---
<Tip>

View File

@@ -9,6 +9,8 @@ The changelog below reflects new product developments and updates on a monthly b
- Release Audit Logs V2.
- Add support for GitHub SSO.
- Enable users to opt in for multiple authentication methods.
- Improved password requirements including check against [Have I Been Pwnd Password API](https://haveibeenpwned.com/Passwords).
- Added native [GCP Secret Manager integration](https://infisical.com/docs/integrations/cloud/gcp-secret-manager)
## July 2023
@@ -16,17 +18,17 @@ The changelog below reflects new product developments and updates on a monthly b
- Redesigned the project/organization experience.
- Updated the secrets overview page; users are now able to edit secrets directly from it.
- Added native [Laravel Forge integration](https://infisical.com/docs/integrations/cloud/laravel-forge).
- Added native [Codefresh integration](https://infisical.com/docs/integrations/cicd/codefresh)
- Added native [Bitbucket integration](https://infisical.com/docs/integrations/cicd/bitbucket)
- Added native [DigitalOcean App Platform integration](https://infisical.com/docs/integrations/cloud/digital-ocean-app-platform)
- Added native [Cloud66 integration](https://infisical.com/docs/integrations/cloud/cloud-66)
- Added native [Terraform Cloud integration](https://infisical.com/docs/integrations/cloud/terraform-cloud)
- Added native [Northflank integration](https://infisical.com/docs/integrations/cloud/northflank)
- Added native [Windmill integration](https://infisical.com/docs/integrations/cloud/windmill)
- Added native [Codefresh integration](https://infisical.com/docs/integrations/cicd/codefresh).
- Added native [Bitbucket integration](https://infisical.com/docs/integrations/cicd/bitbucket).
- Added native [DigitalOcean App Platform integration](https://infisical.com/docs/integrations/cloud/digital-ocean-app-platform).
- Added native [Cloud66 integration](https://infisical.com/docs/integrations/cloud/cloud-66).
- Added native [Terraform Cloud integration](https://infisical.com/docs/integrations/cloud/terraform-cloud).
- Added native [Northflank integration](https://infisical.com/docs/integrations/cloud/northflank).
- Added native [Windmill integration](https://infisical.com/docs/integrations/cloud/windmill).
- Added support for Google SSO.
- Added support for [Okta](https://infisical.com/docs/documentation/platform/sso/okta), [Azure AD](https://infisical.com/docs/documentation/platform/sso/azure), and JumpCloud [SAML](https://infisical.com/docs/documentation/platform/saml) authentication.
- Released [folders / path-based secret storage](https://infisical.com/docs/documentation/platform/folder)
- Released [webhooks](https://infisical.com/docs/documentation/platform/webhooks)
- Released [folders / path-based secret storage](https://infisical.com/docs/documentation/platform/folder).
- Released [webhooks](https://infisical.com/docs/documentation/platform/webhooks).
## June 2023
@@ -68,7 +70,7 @@ The changelog below reflects new product developments and updates on a monthly b
## Feb 2023
- Upgraded private key encryption/decryption mechanism to use Argon2id and 256-bit protected keys.
- Added preliminary emai-based 2FA capability
- Added preliminary emai-based 2FA capability.
- Added suspicious login alerting if user logs in via new device or IP address.
- Added documentation for PM2 integration.
- Added secret backups support for the CLI; it now fetches and caches secrets locally to be used in the event of future failed fetch.
@@ -93,9 +95,9 @@ The changelog below reflects new product developments and updates on a monthly b
- Added native GitHub Actions integration.
- Added custom environment names.
- Added auto-redeployment capability to the Kubernetes operator.
- (Service Token 2.0) Shortened the length of service tokens
- Added a public-facing API
- Added preliminary access control capability for users to be provisioned read/write access to environments
- (Service Token 2.0) Shortened the length of service tokens.
- Added a public-facing API.
- Added preliminary access control capability for users to be provisioned read/write access to environments.
- Performed various web UI optimizations.
## Nov 2022

Binary file not shown.

After

Width:  |  Height:  |  Size: 444 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 503 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

Before

Width:  |  Height:  |  Size: 189 KiB

After

Width:  |  Height:  |  Size: 189 KiB

View File

Before

Width:  |  Height:  |  Size: 352 KiB

After

Width:  |  Height:  |  Size: 352 KiB

View File

Before

Width:  |  Height:  |  Size: 379 KiB

After

Width:  |  Height:  |  Size: 379 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 370 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 940 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

View File

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 740 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 856 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 782 KiB

Some files were not shown because too many files have changed in this diff Show More