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
+3
View File
@@ -3203,6 +3203,9 @@
"name": { "name": {
"example": "any" "example": "any"
}, },
"tagColor": {
"example": "any"
},
"slug": { "slug": {
"example": "any" "example": "any"
} }
+2
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 getClientIdGitHub = async () => (await client.getSecret("CLIENT_ID_GITHUB")).secretValue;
export const getClientIdGitLab = async () => (await client.getSecret("CLIENT_ID_GITLAB")).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 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 getClientSecretAzure = async () => (await client.getSecret("CLIENT_SECRET_AZURE")).secretValue;
export const getClientSecretHeroku = async () => (await client.getSecret("CLIENT_SECRET_HEROKU")).secretValue; export const getClientSecretHeroku = async () => (await client.getSecret("CLIENT_SECRET_HEROKU")).secretValue;
export const getClientSecretVercel = async () => (await client.getSecret("CLIENT_SECRET_VERCEL")).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 getClientSecretGitHub = async () => (await client.getSecret("CLIENT_SECRET_GITHUB")).secretValue;
export const getClientSecretGitLab = async () => (await client.getSecret("CLIENT_SECRET_GITLAB")).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 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 getClientSlugVercel = async () => (await client.getSecret("CLIENT_SLUG_VERCEL")).secretValue;
export const getClientIdGoogleLogin = async () => (await client.getSecret("CLIENT_ID_GOOGLE_LOGIN")).secretValue; export const getClientIdGoogleLogin = async () => (await client.getSecret("CLIENT_ID_GOOGLE_LOGIN")).secretValue;
+69 -80
View File
@@ -1,32 +1,20 @@
import { Request, Response } from "express"; import { Request, Response } from "express";
import fs from "fs";
import path from "path";
import jwt from "jsonwebtoken"; import jwt from "jsonwebtoken";
import * as bigintConversion from "bigint-conversion"; import * as bigintConversion from "bigint-conversion";
// eslint-disable-next-line @typescript-eslint/no-var-requires // eslint-disable-next-line @typescript-eslint/no-var-requires
const jsrp = require("jsrp"); const jsrp = require("jsrp");
import { import { LoginSRPDetail, TokenVersion, User } from "../../models";
LoginSRPDetail,
TokenVersion,
User,
} from "../../models";
import { clearTokens, createToken, issueAuthTokens } from "../../helpers/auth"; import { clearTokens, createToken, issueAuthTokens } from "../../helpers/auth";
import { checkUserDevice } from "../../helpers/user"; import { checkUserDevice } from "../../helpers/user";
import { import { ACTION_LOGIN, ACTION_LOGOUT } from "../../variables";
ACTION_LOGIN, import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
ACTION_LOGOUT,
} from "../../variables";
import {
BadRequestError,
UnauthorizedRequestError,
} from "../../utils/errors";
import { EELogService } from "../../ee/services"; import { EELogService } from "../../ee/services";
import { getUserAgentType } from "../../utils/posthog"; import { getUserAgentType } from "../../utils/posthog";
import { import {
getHttpsEnabled, getHttpsEnabled,
getJwtAuthLifetime, getJwtAuthLifetime,
getJwtAuthSecret, getJwtAuthSecret,
getJwtRefreshSecret, getJwtRefreshSecret
} from "../../config"; } from "../../config";
import { ActorType } from "../../ee/models"; import { ActorType } from "../../ee/models";
@@ -44,13 +32,10 @@ declare module "jsonwebtoken" {
* @returns * @returns
*/ */
export const login1 = async (req: Request, res: Response) => { export const login1 = async (req: Request, res: Response) => {
const { const { email, clientPublicKey }: { email: string; clientPublicKey: string } = req.body;
email,
clientPublicKey,
}: { email: string; clientPublicKey: string } = req.body;
const user = await User.findOne({ const user = await User.findOne({
email, email
}).select("+salt +verifier"); }).select("+salt +verifier");
if (!user) throw new Error("Failed to find user"); if (!user) throw new Error("Failed to find user");
@@ -59,21 +44,25 @@ export const login1 = async (req: Request, res: Response) => {
server.init( server.init(
{ {
salt: user.salt, salt: user.salt,
verifier: user.verifier, verifier: user.verifier
}, },
async () => { async () => {
// generate server-side public key // generate server-side public key
const serverPublicKey = server.getPublicKey(); const serverPublicKey = server.getPublicKey();
await LoginSRPDetail.findOneAndReplace({ email: email }, { await LoginSRPDetail.findOneAndReplace(
{ email: email },
{
email: email, email: email,
clientPublicKey: clientPublicKey, clientPublicKey: clientPublicKey,
serverBInt: bigintConversion.bigintToBuf(server.bInt), serverBInt: bigintConversion.bigintToBuf(server.bInt)
}, { upsert: true, returnNewDocument: false }) },
{ upsert: true, returnNewDocument: false }
);
return res.status(200).send({ return res.status(200).send({
serverPublicKey, 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) => { export const login2 = async (req: Request, res: Response) => {
const { email, clientProof } = req.body; const { email, clientProof } = req.body;
const user = await User.findOne({ const user = await User.findOne({
email, email
}).select("+salt +verifier +publicKey +encryptedPrivateKey +iv +tag"); }).select("+salt +verifier +publicKey +encryptedPrivateKey +iv +tag");
if (!user) throw new Error("Failed to find user"); 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) { 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(); const server = new jsrp.server();
@@ -105,7 +98,7 @@ export const login2 = async (req: Request, res: Response) => {
{ {
salt: user.salt, salt: user.salt,
verifier: user.verifier, verifier: user.verifier,
b: loginSRPDetailFromDB.serverBInt, b: loginSRPDetailFromDB.serverBInt
}, },
async () => { async () => {
server.setClientPublicKey(loginSRPDetailFromDB.clientPublicKey); server.setClientPublicKey(loginSRPDetailFromDB.clientPublicKey);
@@ -117,13 +110,13 @@ export const login2 = async (req: Request, res: Response) => {
await checkUserDevice({ await checkUserDevice({
user, user,
ip: req.realIP, ip: req.realIP,
userAgent: req.headers["user-agent"] ?? "", userAgent: req.headers["user-agent"] ?? ""
}); });
const tokens = await issueAuthTokens({ const tokens = await issueAuthTokens({
userId: user._id, userId: user._id,
ip: req.realIP, ip: req.realIP,
userAgent: req.headers["user-agent"] ?? "", userAgent: req.headers["user-agent"] ?? ""
}); });
// store (refresh) token in httpOnly cookie // store (refresh) token in httpOnly cookie
@@ -131,20 +124,21 @@ export const login2 = async (req: Request, res: Response) => {
httpOnly: true, httpOnly: true,
path: "/", path: "/",
sameSite: "strict", sameSite: "strict",
secure: await getHttpsEnabled(), secure: await getHttpsEnabled()
}); });
const loginAction = await EELogService.createAction({ const loginAction = await EELogService.createAction({
name: ACTION_LOGIN, name: ACTION_LOGIN,
userId: user._id, userId: user._id
}); });
loginAction && await EELogService.createLog({ loginAction &&
(await EELogService.createLog({
userId: user._id, userId: user._id,
actions: [loginAction], actions: [loginAction],
channel: getUserAgentType(req.headers["user-agent"]), channel: getUserAgentType(req.headers["user-agent"]),
ipAddress: req.realIP, ipAddress: req.realIP
}); }));
// return (access) token in response // return (access) token in response
return res.status(200).send({ return res.status(200).send({
@@ -152,12 +146,12 @@ export const login2 = async (req: Request, res: Response) => {
publicKey: user.publicKey, publicKey: user.publicKey,
encryptedPrivateKey: user.encryptedPrivateKey, encryptedPrivateKey: user.encryptedPrivateKey,
iv: user.iv, iv: user.iv,
tag: user.tag, tag: user.tag
}); });
} }
return res.status(400).send({ 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) => { export const logout = async (req: Request, res: Response) => {
if (req.authData.actor.type === ActorType.USER && req.authData.tokenVersionId) { if (req.authData.actor.type === ActorType.USER && req.authData.tokenVersionId) {
await clearTokens(req.authData.tokenVersionId) await clearTokens(req.authData.tokenVersionId);
} }
// clear httpOnly cookie // clear httpOnly cookie
@@ -179,49 +173,44 @@ export const logout = async (req: Request, res: Response) => {
httpOnly: true, httpOnly: true,
path: "/", path: "/",
sameSite: "strict", sameSite: "strict",
secure: (await getHttpsEnabled()) as boolean, secure: (await getHttpsEnabled()) as boolean
}); });
const logoutAction = await EELogService.createAction({ const logoutAction = await EELogService.createAction({
name: ACTION_LOGOUT, name: ACTION_LOGOUT,
userId: req.user._id, userId: req.user._id
}); });
logoutAction && await EELogService.createLog({ logoutAction &&
(await EELogService.createLog({
userId: req.user._id, userId: req.user._id,
actions: [logoutAction], actions: [logoutAction],
channel: getUserAgentType(req.headers["user-agent"]), channel: getUserAgentType(req.headers["user-agent"]),
ipAddress: req.realIP, ipAddress: req.realIP
}); }));
return res.status(200).send({ 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) => { export const revokeAllSessions = async (req: Request, res: Response) => {
await TokenVersion.updateMany({ await TokenVersion.updateMany(
user: req.user._id, {
}, { user: req.user._id
},
{
$inc: { $inc: {
refreshVersion: 1, refreshVersion: 1,
accessVersion: 1, accessVersion: 1
}, }
}); }
);
return res.status(200).send({ return res.status(200).send({
message: "Successfully revoked all sessions.", message: "Successfully revoked all sessions."
}); });
} };
/** /**
* Return user is authenticated * Return user is authenticated
@@ -231,9 +220,9 @@ export const revokeAllSessions = async (req: Request, res: Response) => {
*/ */
export const checkAuth = async (req: Request, res: Response) => { export const checkAuth = async (req: Request, res: Response) => {
return res.status(200).send({ return res.status(200).send({
message: "Authenticated", message: "Authenticated"
}); });
} };
/** /**
* Return new JWT access token by first validating the refresh token * 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) => { export const getNewToken = async (req: Request, res: Response) => {
const refreshToken = req.cookies.jid; const refreshToken = req.cookies.jid;
if (!refreshToken) throw BadRequestError({ if (!refreshToken)
throw BadRequestError({
message: "Failed to find refresh token in request cookies" message: "Failed to find refresh token in request cookies"
}); });
const decodedToken = <jwt.UserIDJwtPayload>( const decodedToken = <jwt.UserIDJwtPayload>jwt.verify(refreshToken, await getJwtRefreshSecret());
jwt.verify(refreshToken, await getJwtRefreshSecret())
);
const user = await User.findOne({ const user = await User.findOne({
_id: decodedToken.userId, _id: decodedToken.userId
}).select("+publicKey +refreshVersion +accessVersion"); }).select("+publicKey +refreshVersion +accessVersion");
if (!user) throw new Error("Failed to authenticate unfound user"); if (!user) throw new Error("Failed to authenticate unfound user");
if (!user?.publicKey) if (!user?.publicKey) throw new Error("Failed to authenticate not fully set up account");
throw new Error("Failed to authenticate not fully set up account");
const tokenVersion = await TokenVersion.findById(decodedToken.tokenVersionId); const tokenVersion = await TokenVersion.findById(decodedToken.tokenVersionId);
if (!tokenVersion) throw UnauthorizedRequestError({ if (!tokenVersion)
message: "Failed to validate refresh token", throw UnauthorizedRequestError({
message: "Failed to validate refresh token"
}); });
if (decodedToken.refreshVersion !== tokenVersion.refreshVersion) throw BadRequestError({ if (decodedToken.refreshVersion !== tokenVersion.refreshVersion)
message: "Failed to validate refresh token", throw BadRequestError({
message: "Failed to validate refresh token"
}); });
const token = createToken({ const token = createToken({
payload: { payload: {
userId: decodedToken.userId, userId: decodedToken.userId,
tokenVersionId: tokenVersion._id.toString(), tokenVersionId: tokenVersion._id.toString(),
accessVersion: tokenVersion.refreshVersion, accessVersion: tokenVersion.refreshVersion
}, },
expiresIn: await getJwtAuthLifetime(), expiresIn: await getJwtAuthLifetime(),
secret: await getJwtAuthSecret(), secret: await getJwtAuthSecret()
}); });
return res.status(200).send({ return res.status(200).send({
token, token
}); });
}; };
export const handleAuthProviderCallback = (req: Request, res: Response) => { export const handleAuthProviderCallback = (req: Request, res: Response) => {
res.redirect(`/login/provider/success?token=${encodeURIComponent(req.providerAuthToken)}`); res.redirect(`/login/provider/success?token=${encodeURIComponent(req.providerAuthToken)}`);
} };
@@ -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] * Delete integration authorization with id [integrationAuthId]
* @param req * @param req
@@ -1,9 +1,8 @@
import { Request, Response } from "express"; import { Request, Response } from "express";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { Integration } from "../../models"; import { Folder, Integration } from "../../models";
import { EventService } from "../../services"; import { EventService } from "../../services";
import { eventStartIntegration } from "../../events"; import { eventStartIntegration } from "../../events";
import Folder from "../../models/folder";
import { getFolderByPath } from "../../services/FolderService"; 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";
@@ -30,7 +29,8 @@ export const createIntegration = async (req: Request, res: Response) => {
owner, owner,
path, path,
region, region,
secretPath secretPath,
metadata
} = req.body; } = req.body;
const folders = await Folder.findOne({ const folders = await Folder.findOne({
@@ -65,7 +65,8 @@ export const createIntegration = async (req: Request, res: Response) => {
region, region,
secretPath, secretPath,
integration: req.integrationAuth.integration, integration: req.integrationAuth.integration,
integrationAuth: new Types.ObjectId(integrationAuthId) integrationAuth: new Types.ObjectId(integrationAuthId),
metadata
}).save(); }).save();
if (integration) { if (integration) {
@@ -1,8 +1,6 @@
import { Request, Response } from "express"; import { Request, Response } from "express";
import { isValidScope, validateMembership } from "../../helpers"; import { isValidScope, validateMembership } from "../../helpers";
import { ServiceTokenData } from "../../models"; import { Folder, SecretImport, ServiceTokenData } from "../../models";
import Folder from "../../models/folder";
import SecretImport from "../../models/secretImports";
import { getAllImportedSecrets } from "../../services/SecretImportService"; import { getAllImportedSecrets } from "../../services/SecretImportService";
import { getFolderWithPathFromId } from "../../services/FolderService"; import { getFolderWithPathFromId } from "../../services/FolderService";
import { BadRequestError, ResourceNotFoundError,UnauthorizedRequestError } from "../../utils/errors"; import { BadRequestError, ResourceNotFoundError,UnauthorizedRequestError } from "../../utils/errors";
@@ -4,8 +4,7 @@ import { EventType, FolderVersion } from "../../ee/models";
import { EEAuditLogService, EESecretService } from "../../ee/services"; import { EEAuditLogService, EESecretService } from "../../ee/services";
import { validateMembership } from "../../helpers/membership"; import { validateMembership } from "../../helpers/membership";
import { isValidScope } from "../../helpers/secrets"; import { isValidScope } from "../../helpers/secrets";
import { Secret, ServiceTokenData } from "../../models"; import { Folder, Secret, ServiceTokenData } from "../../models";
import Folder from "../../models/folder";
import { import {
appendFolder, appendFolder,
deleteFolderById, deleteFolderById,
@@ -2,7 +2,7 @@ import { Request, Response } from "express";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { client, getRootEncryptionKey } from "../../config"; import { client, getRootEncryptionKey } from "../../config";
import { validateMembership } from "../../helpers"; import { validateMembership } from "../../helpers";
import Webhook from "../../models/webhooks"; import { Webhook } from "../../models";
import { getWebhookPayload, triggerWebhookRequest } from "../../services/WebhookService"; import { getWebhookPayload, triggerWebhookRequest } from "../../services/WebhookService";
import { BadRequestError, ResourceNotFoundError } from "../../utils/errors"; import { BadRequestError, ResourceNotFoundError } from "../../utils/errors";
import { EEAuditLogService } from "../../ee/services"; import { EEAuditLogService } from "../../ee/services";
@@ -1,6 +1,5 @@
import { Request, Response } from "express"; import { Request, Response } from "express";
import mongoose, { Types } from "mongoose"; import mongoose, { Types } from "mongoose";
import Secret, { ISecret } from "../../models/secret";
import { import {
CreateSecretRequestBody, CreateSecretRequestBody,
ModifySecretRequestBody, ModifySecretRequestBody,
@@ -20,7 +19,7 @@ import {
SECRET_SHARED SECRET_SHARED
} from "../../variables"; } from "../../variables";
import { TelemetryService } from "../../services"; import { TelemetryService } from "../../services";
import { User } from "../../models"; import { ISecret, Secret, User } from "../../models";
import { AccountNotFoundError } from "../../utils/errors"; import { AccountNotFoundError } from "../../utils/errors";
/** /**
@@ -1,6 +1,6 @@
import { Types } from "mongoose"; import { Types } from "mongoose";
import { Request, Response } from "express"; 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 { AuditLog, EventType, IAction, SecretVersion } from "../../ee/models";
import { import {
ACTION_ADD_SECRETS, ACTION_ADD_SECRETS,
@@ -9,6 +9,7 @@ import {
ACTION_UPDATE_SECRETS, ACTION_UPDATE_SECRETS,
ALGORITHM_AES_256_GCM, ALGORITHM_AES_256_GCM,
ENCODING_SCHEME_UTF8, ENCODING_SCHEME_UTF8,
K8_USER_AGENT_NAME,
SECRET_PERSONAL SECRET_PERSONAL
} from "../../variables"; } from "../../variables";
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors"; import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
@@ -23,10 +24,8 @@ import {
userHasWorkspaceAccess, userHasWorkspaceAccess,
userHasWriteOnlyAbility userHasWriteOnlyAbility
} from "../../ee/helpers/checkMembershipPermissions"; } from "../../ee/helpers/checkMembershipPermissions";
import Tag from "../../models/tag";
import _ from "lodash"; import _ from "lodash";
import { BatchSecret, BatchSecretRequest } from "../../types/secret"; import { BatchSecret, BatchSecretRequest } from "../../types/secret";
import Folder from "../../models/folder";
import { import {
getFolderByPath, getFolderByPath,
getFolderIdFromServiceToken, getFolderIdFromServiceToken,
@@ -234,6 +233,9 @@ export const batchSecrets = async (req: Request, res: Response) => {
$inc: { $inc: {
version: 1 version: 1
}, },
$unset: {
"metadata.source": true as const
},
...u, ...u,
_id: new Types.ObjectId(u._id) _id: new Types.ObjectId(u._id)
} }
@@ -966,22 +968,37 @@ export const getSecrets = async (req: Request, res: Response) => {
); );
const postHogClient = await TelemetryService.getPostHogClient(); 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) { if (postHogClient) {
const shouldCapture = req.authData.userAgent !== K8_USER_AGENT_NAME || shouldRecordK8Event;
const approximateForNoneCapturedEvents = secrets.length * 10
if (shouldCapture) {
postHogClient.capture({ postHogClient.capture({
event: "secrets pulled", event: "secrets pulled",
distinctId: await TelemetryService.getDistinctId({ distinctId: await TelemetryService.getDistinctId({
authData: req.authData authData: req.authData
}), }),
properties: { properties: {
numberOfSecrets: secrets.length, numberOfSecrets: shouldRecordK8Event ? approximateForNoneCapturedEvents : secrets.length,
environment, environment,
workspaceId, workspaceId,
channel,
folderId, folderId,
userAgent: req.headers?.["user-agent"] channel: req.authData.userAgentType,
userAgent: req.authData.userAgent
} }
}); });
} }
}
return res.status(200).send({ return res.status(200).send({
secrets, secrets,
+3 -3
View File
@@ -1,15 +1,15 @@
import { Request, Response } from "express"; import { Request, Response } from "express";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { Membership, Secret } from "../../models"; import { Membership, Secret, Tag } from "../../models";
import Tag from "../../models/tag";
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors"; import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
export const createWorkspaceTag = async (req: Request, res: Response) => { export const createWorkspaceTag = async (req: Request, res: Response) => {
const { workspaceId } = req.params; const { workspaceId } = req.params;
const { name, slug } = req.body; const { name, slug, tagColor } = req.body;
const tagToCreate = { const tagToCreate = {
name, name,
tagColor,
workspace: new Types.ObjectId(workspaceId), workspace: new Types.ObjectId(workspaceId),
slug, slug,
user: new Types.ObjectId(req.user._id), user: new Types.ObjectId(req.user._id),
@@ -6,10 +6,9 @@ import { BotService } from "../../services";
import { containsGlobPatterns, repackageSecretToRaw } from "../../helpers/secrets"; import { containsGlobPatterns, repackageSecretToRaw } from "../../helpers/secrets";
import { encryptSymmetric128BitHexKeyUTF8 } from "../../utils/crypto"; import { encryptSymmetric128BitHexKeyUTF8 } from "../../utils/crypto";
import { getAllImportedSecrets } from "../../services/SecretImportService"; import { getAllImportedSecrets } from "../../services/SecretImportService";
import Folder from "../../models/folder"; import { Folder, IServiceTokenData } from "../../models";
import { getFolderByPath } from "../../services/FolderService"; import { getFolderByPath } from "../../services/FolderService";
import { BadRequestError } from "../../utils/errors"; import { BadRequestError } from "../../utils/errors";
import { IServiceTokenData } from "../../models";
import { requireWorkspaceAuth } from "../../middleware"; import { requireWorkspaceAuth } from "../../middleware";
import { ADMIN, MEMBER, PERMISSION_READ_SECRETS } from "../../variables"; 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 workspaceId = req.query.workspaceId as string;
let environment = req.query.environment as string; let environment = req.query.environment as string;
let secretPath = req.query.secretPath 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; 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 // 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({ const secrets = await SecretService.getSecrets({
workspaceId: new Types.ObjectId(workspaceId), workspaceId: new Types.ObjectId(workspaceId),
environment, environment,
folderId,
secretPath, secretPath,
authData: req.authData authData: req.authData
}); });
@@ -284,11 +285,13 @@ export const getSecrets = async (req: Request, res: Response) => {
const workspaceId = req.query.workspaceId as string; const workspaceId = req.query.workspaceId as string;
const environment = req.query.environment as string; const environment = req.query.environment as string;
const secretPath = req.query.secretPath 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 includeImports = req.query.include_imports as string;
const secrets = await SecretService.getSecrets({ const secrets = await SecretService.getSecrets({
workspaceId: new Types.ObjectId(workspaceId), workspaceId: new Types.ObjectId(workspaceId),
environment, environment,
folderId,
secretPath, secretPath,
authData: req.authData authData: req.authData
}); });
File diff suppressed because it is too large Load Diff
@@ -1,6 +1,6 @@
import { Request, Response } from "express"; import { Request, Response } from "express";
import { PipelineStage, Types } from "mongoose"; import { PipelineStage, Types } from "mongoose";
import { Membership, Secret, ServiceTokenData, User } from "../../../models"; import { Folder, Membership, Secret, ServiceTokenData, TFolderSchema, User } from "../../../models";
import { import {
ActorType, ActorType,
AuditLog, AuditLog,
@@ -18,7 +18,7 @@ import {
} from "../../models"; } from "../../models";
import { EESecretService } from "../../services"; import { EESecretService } from "../../services";
import { getLatestSecretVersionIds } from "../../helpers/secretVersion"; import { getLatestSecretVersionIds } from "../../helpers/secretVersion";
import Folder, { TFolderSchema } from "../../../models/folder"; // import Folder, { TFolderSchema } from "../../../models/folder";
import { searchByFolderId } from "../../../services/FolderService"; import { searchByFolderId } from "../../../services/FolderService";
import { EEAuditLogService, EELicenseService } from "../../services"; import { EEAuditLogService, EELicenseService } from "../../services";
import { extractIPDetails, isValidIpOrCidr } from "../../../utils/ip"; import { extractIPDetails, isValidIpOrCidr } from "../../../utils/ip";
+1 -1
View File
@@ -117,7 +117,7 @@ const secretVersionSchema = new Schema<ISecretVersion>(
ref: "Tag", ref: "Tag",
type: [Schema.Types.ObjectId], type: [Schema.Types.ObjectId],
default: [], default: [],
}, }
}, },
{ {
timestamps: true, timestamps: true,
+1 -1
View File
@@ -14,7 +14,7 @@ import {
} from "../variables"; } from "../variables";
import { client, getEncryptionKey, getRootEncryptionKey } from "../config"; import { client, getEncryptionKey, getRootEncryptionKey } from "../config";
import { InternalServerError } from "../utils/errors"; import { InternalServerError } from "../utils/errors";
import Folder from "../models/folder"; import { Folder } from "../models";
import { getFolderByPath } from "../services/FolderService"; import { getFolderByPath } from "../services/FolderService";
import { getAllImportedSecrets } from "../services/SecretImportService"; import { getAllImportedSecrets } from "../services/SecretImportService";
import { expandSecrets } from "./secrets"; import { expandSecrets } from "./secrets";
-1
View File
@@ -9,7 +9,6 @@ import {
INTEGRATION_VERCEL INTEGRATION_VERCEL
} from "../variables"; } from "../variables";
import { UnauthorizedRequestError } from "../utils/errors"; import { UnauthorizedRequestError } from "../utils/errors";
import { syncSecretsToActiveIntegrationsQueue } from "../queues/integrations/syncSecretsToThirdPartyServices"
interface Update { interface Update {
workspace: string; workspace: string;
+32 -10
View File
@@ -7,11 +7,13 @@ import {
UpdateSecretParams UpdateSecretParams
} from "../interfaces/services/SecretService"; } from "../interfaces/services/SecretService";
import { import {
Folder,
ISecret, ISecret,
IServiceTokenData, IServiceTokenData,
Secret, Secret,
SecretBlindIndexData, SecretBlindIndexData,
ServiceTokenData ServiceTokenData,
TFolderRootSchema
} from "../models"; } from "../models";
import { EventType, SecretVersion } from "../ee/models"; import { EventType, SecretVersion } from "../ee/models";
import { import {
@@ -29,6 +31,7 @@ import {
ALGORITHM_AES_256_GCM, ALGORITHM_AES_256_GCM,
ENCODING_SCHEME_BASE64, ENCODING_SCHEME_BASE64,
ENCODING_SCHEME_UTF8, ENCODING_SCHEME_UTF8,
K8_USER_AGENT_NAME,
SECRET_PERSONAL, SECRET_PERSONAL,
SECRET_SHARED SECRET_SHARED
} from "../variables"; } from "../variables";
@@ -45,7 +48,6 @@ import { getAuthDataPayloadIdObj, getAuthDataPayloadUserObj } from "../utils/aut
import { getFolderByPath, getFolderIdFromServiceToken } from "../services/FolderService"; import { getFolderByPath, getFolderIdFromServiceToken } from "../services/FolderService";
import picomatch from "picomatch"; import picomatch from "picomatch";
import path from "path"; import path from "path";
import Folder, { TFolderRootSchema } from "../models/folder";
export const isValidScope = ( export const isValidScope = (
authPayload: IServiceTokenData, authPayload: IServiceTokenData,
@@ -393,7 +395,8 @@ export const createSecretHelper = async ({
secretCommentTag, secretCommentTag,
folder: folderId, folder: folderId,
algorithm: ALGORITHM_AES_256_GCM, algorithm: ALGORITHM_AES_256_GCM,
keyEncoding: ENCODING_SCHEME_UTF8 keyEncoding: ENCODING_SCHEME_UTF8,
metadata
}).save(); }).save();
const secretVersion = new SecretVersion({ const secretVersion = new SecretVersion({
@@ -496,6 +499,7 @@ export const getSecretsHelper = async ({
workspaceId, workspaceId,
environment, environment,
authData, authData,
folderId,
secretPath = "/" secretPath = "/"
}: GetSecretsParams) => { }: GetSecretsParams) => {
let secrets: ISecret[] = []; let secrets: ISecret[] = [];
@@ -505,7 +509,10 @@ export const getSecretsHelper = async ({
throw UnauthorizedRequestError({ message: "Folder Permission Denied" }); 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 // get personal secrets first
secrets = await Secret.find({ secrets = await Secret.find({
@@ -567,14 +574,28 @@ export const getSecretsHelper = async ({
const postHogClient = await TelemetryService.getPostHogClient(); const postHogClient = await TelemetryService.getPostHogClient();
if (postHogClient) { // 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({ postHogClient.capture({
event: "secrets pulled", event: "secrets pulled",
distinctId: await TelemetryService.getDistinctId({ distinctId: await TelemetryService.getDistinctId({ authData }),
authData
}),
properties: { properties: {
numberOfSecrets: secrets.length, numberOfSecrets: shouldRecordK8Event ? approximateForNoneCapturedEvents : secrets.length,
environment, environment,
workspaceId, workspaceId,
folderId, folderId,
@@ -583,6 +604,7 @@ export const getSecretsHelper = async ({
} }
}); });
} }
}
return secrets; return secrets;
}; };
@@ -680,7 +702,7 @@ export const getSecretHelper = async ({
if (postHogClient) { if (postHogClient) {
postHogClient.capture({ postHogClient.capture({
event: "secrets pull", event: "secrets pulled",
distinctId: await TelemetryService.getDistinctId({ distinctId: await TelemetryService.getDistinctId({
authData authData
}), }),
+25 -12
View File
@@ -24,7 +24,7 @@ import {
secretSnapshot as eeSecretSnapshotRouter, secretSnapshot as eeSecretSnapshotRouter,
users as eeUsersRouter, users as eeUsersRouter,
workspace as eeWorkspaceRouter, workspace as eeWorkspaceRouter,
secretScanning as v1SecretScanningRouter, secretScanning as v1SecretScanningRouter
} from "./ee/routes/v1"; } from "./ee/routes/v1";
import { import {
auth as v1AuthRouter, auth as v1AuthRouter,
@@ -58,7 +58,7 @@ import {
signup as v2SignupRouter, signup as v2SignupRouter,
tags as v2TagsRouter, tags as v2TagsRouter,
users as v2UsersRouter, users as v2UsersRouter,
workspace as v2WorkspaceRouter, workspace as v2WorkspaceRouter
} from "./routes/v2"; } from "./routes/v2";
import { import {
auth as v3AuthRouter, auth as v3AuthRouter,
@@ -70,14 +70,21 @@ import { healthCheck } from "./routes/status";
import { getLogger } from "./utils/logger"; import { getLogger } from "./utils/logger";
import { RouteNotFoundError } from "./utils/errors"; import { RouteNotFoundError } from "./utils/errors";
import { requestErrorHandler } from "./middleware/requestErrorHandler"; import { requestErrorHandler } from "./middleware/requestErrorHandler";
import { getNodeEnv, getPort, 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 { syncSecretsToThirdPartyServices } from "./queues/integrations/syncSecretsToThirdPartyServices";
import { githubPushEventSecretScan } from "./queues/secret-scanning/githubScanPushEvent"; 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 () => {
await setup(); await setup();
await EELicenseService.initGlobalFeatureSet(); 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({ const probot = new Probot({
appId: await getSecretScanningGitAppId(), appId: await getSecretScanningGitAppId(),
privateKey: await getSecretScanningPrivateKey(), privateKey: await getSecretScanningPrivateKey(),
secret: await getSecretScanningWebhookSecret(), secret: await getSecretScanningWebhookSecret()
}); });
if ((await getNodeEnv()) != "production") { if ((await getNodeEnv()) != "production") {
@@ -106,12 +117,14 @@ const main = async () => {
source: await getSecretScanningWebhookProxy(), source: await getSecretScanningWebhookProxy(),
target: "http://backend:4000/ss-webhook", target: "http://backend:4000/ss-webhook",
logger: console 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") { if ((await getNodeEnv()) === "production") {
@@ -207,8 +220,8 @@ const main = async () => {
server.on("close", async () => { server.on("close", async () => {
await DatabaseService.closeDatabase(); await DatabaseService.closeDatabase();
syncSecretsToThirdPartyServices.close() syncSecretsToThirdPartyServices.close();
githubPushEventSecretScan.close() githubPushEventSecretScan.close();
}); });
return server; return server;
+99
View File
@@ -18,6 +18,10 @@ import {
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM, INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
INTEGRATION_FLYIO, INTEGRATION_FLYIO,
INTEGRATION_FLYIO_API_URL, 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_GITHUB,
INTEGRATION_GITLAB, INTEGRATION_GITLAB,
INTEGRATION_GITLAB_API_URL, INTEGRATION_GITLAB_API_URL,
@@ -79,6 +83,11 @@ const getApps = async ({
}) => { }) => {
let apps: App[] = []; let apps: App[] = [];
switch (integrationAuth.integration) { switch (integrationAuth.integration) {
case INTEGRATION_GCP_SECRET_MANAGER:
apps = await getAppsGCPSecretManager({
accessToken,
});
break;
case INTEGRATION_AZURE_KEY_VAULT: case INTEGRATION_AZURE_KEY_VAULT:
apps = []; apps = [];
break; break;
@@ -210,6 +219,96 @@ const getApps = async ({
return apps; 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 * Return list of apps for Heroku integration
* @param {Object} obj * @param {Object} obj
+44 -1
View File
@@ -4,6 +4,8 @@ import {
INTEGRATION_AZURE_TOKEN_URL, INTEGRATION_AZURE_TOKEN_URL,
INTEGRATION_BITBUCKET, INTEGRATION_BITBUCKET,
INTEGRATION_BITBUCKET_TOKEN_URL, INTEGRATION_BITBUCKET_TOKEN_URL,
INTEGRATION_GCP_SECRET_MANAGER,
INTEGRATION_GCP_TOKEN_URL,
INTEGRATION_GITHUB, INTEGRATION_GITHUB,
INTEGRATION_GITHUB_TOKEN_URL, INTEGRATION_GITHUB_TOKEN_URL,
INTEGRATION_GITLAB, INTEGRATION_GITLAB,
@@ -13,17 +15,19 @@ import {
INTEGRATION_NETLIFY, INTEGRATION_NETLIFY,
INTEGRATION_NETLIFY_TOKEN_URL, INTEGRATION_NETLIFY_TOKEN_URL,
INTEGRATION_VERCEL, INTEGRATION_VERCEL,
INTEGRATION_VERCEL_TOKEN_URL, INTEGRATION_VERCEL_TOKEN_URL
} from "../variables"; } from "../variables";
import { import {
getClientIdAzure, getClientIdAzure,
getClientIdBitBucket, getClientIdBitBucket,
getClientIdGCPSecretManager,
getClientIdGitHub, getClientIdGitHub,
getClientIdGitLab, getClientIdGitLab,
getClientIdNetlify, getClientIdNetlify,
getClientIdVercel, getClientIdVercel,
getClientSecretAzure, getClientSecretAzure,
getClientSecretBitBucket, getClientSecretBitBucket,
getClientSecretGCPSecretManager,
getClientSecretGitHub, getClientSecretGitHub,
getClientSecretGitLab, getClientSecretGitLab,
getClientSecretHeroku, getClientSecretHeroku,
@@ -113,6 +117,11 @@ const exchangeCode = async ({
let obj = {} as any; let obj = {} as any;
switch (integration) { switch (integration) {
case INTEGRATION_GCP_SECRET_MANAGER:
obj = await exchangeCodeGCP({
code,
});
break;
case INTEGRATION_AZURE_KEY_VAULT: case INTEGRATION_AZURE_KEY_VAULT:
obj = await exchangeCodeAzure({ obj = await exchangeCodeAzure({
code, code,
@@ -153,6 +162,40 @@ const exchangeCode = async ({
return obj; 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 * Return [accessToken] for Azure OAuth2 code-token exchange
* @param param0 * @param param0
+239 -5
View File
@@ -26,6 +26,8 @@ import {
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM, INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
INTEGRATION_FLYIO, INTEGRATION_FLYIO,
INTEGRATION_FLYIO_API_URL, INTEGRATION_FLYIO_API_URL,
INTEGRATION_GCP_SECRET_MANAGER,
INTEGRATION_GCP_SECRET_MANAGER_URL,
INTEGRATION_GITHUB, INTEGRATION_GITHUB,
INTEGRATION_GITLAB, INTEGRATION_GITLAB,
INTEGRATION_GITLAB_API_URL, INTEGRATION_GITLAB_API_URL,
@@ -92,6 +94,13 @@ const syncSecrets = async ({
accessToken: string; accessToken: string;
}) => { }) => {
switch (integration.integration) { switch (integration.integration) {
case INTEGRATION_GCP_SECRET_MANAGER:
await syncSecretsGCPSecretManager({
integration,
secrets,
accessToken
});
break;
case INTEGRATION_AZURE_KEY_VAULT: case INTEGRATION_AZURE_KEY_VAULT:
await syncSecretsAzureKeyVault({ await syncSecretsAzureKeyVault({
integration, 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] * Sync/push [secrets] to Azure Key Vault with vault URI [integration.app]
* @param {Object} obj * @param {Object} obj
@@ -1838,7 +2006,7 @@ const syncSecretsCheckly = async ({
secrets: Record<string, { value: string; comment?: string }>; secrets: Record<string, { value: string; comment?: string }>;
accessToken: string; accessToken: string;
}) => { }) => {
// get secrets from travis-ci
const getSecretsRes = ( const getSecretsRes = (
await standardRequest.get(`${INTEGRATION_CHECKLY_API_URL}/v1/variables`, { await standardRequest.get(`${INTEGRATION_CHECKLY_API_URL}/v1/variables`, {
headers: { headers: {
@@ -1860,7 +2028,6 @@ const syncSecretsCheckly = async ({
if (!(key in getSecretsRes)) { if (!(key in getSecretsRes)) {
// case: secret does not exist in checkly // case: secret does not exist in checkly
// -> add secret // -> add secret
await standardRequest.post( await standardRequest.post(
`${INTEGRATION_CHECKLY_API_URL}/v1/variables`, `${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 {Object} obj
* @param {IIntegration} obj.integration - integration details * @param {IIntegration} obj.integration - integration details
* @param {Object} obj.secrets - secrets to push to integration * @param {Object} obj.secrets - secrets to push to integration
@@ -2041,7 +2208,73 @@ const syncSecretsTeamCity = async ({
value: string; value: string;
} }
// get secrets from Teamcity 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",
},
}
))
.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].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/buildTypes/${integration.targetEnvironmentId}/parameters/env.${key}`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json"
}
}
);
}
}
} else {
// case: sync to TeamCity project
const res = ( const res = (
await standardRequest.get( await standardRequest.get(
`${integrationAuth.url}/app/rest/projects/id:${integration.appId}/parameters`, `${integrationAuth.url}/app/rest/projects/id:${integration.appId}/parameters`,
@@ -2068,7 +2301,7 @@ const syncSecretsTeamCity = async ({
`${integrationAuth.url}/app/rest/projects/id:${integration.appId}/parameters`, `${integrationAuth.url}/app/rest/projects/id:${integration.appId}/parameters`,
{ {
name: `env.${key}`, name: `env.${key}`,
value: secrets[key] value: secrets[key].value
}, },
{ {
headers: { headers: {
@@ -2094,6 +2327,7 @@ const syncSecretsTeamCity = async ({
); );
} }
} }
}
}; };
/** /**
@@ -25,6 +25,7 @@ export interface CreateSecretParams {
export interface GetSecretsParams { export interface GetSecretsParams {
workspaceId: Types.ObjectId; workspaceId: Types.ObjectId;
environment: string; environment: string;
folderId?: string;
secretPath: string; secretPath: string;
authData: AuthData; authData: AuthData;
} }
+1 -3
View File
@@ -36,6 +36,4 @@ const apiKeyDataSchema = new Schema<IAPIKeyData>(
} }
); );
const APIKeyData = model<IAPIKeyData>("APIKeyData", apiKeyDataSchema); export const APIKeyData = model<IAPIKeyData>("APIKeyData", apiKeyDataSchema);
export default APIKeyData;
+1 -3
View File
@@ -68,9 +68,7 @@ const backupPrivateKeySchema = new Schema<IBackupPrivateKey>(
} }
); );
const BackupPrivateKey = model<IBackupPrivateKey>( export const BackupPrivateKey = model<IBackupPrivateKey>(
"BackupPrivateKey", "BackupPrivateKey",
backupPrivateKeySchema backupPrivateKeySchema
); );
export default BackupPrivateKey;
+1 -3
View File
@@ -74,6 +74,4 @@ const botSchema = new Schema<IBot>(
} }
); );
const Bot = model<IBot>("Bot", botSchema); export const Bot = model<IBot>("Bot", botSchema);
export default Bot;
+1 -3
View File
@@ -40,6 +40,4 @@ const botKeySchema = new Schema<IBotKey>(
} }
); );
const BotKey = model<IBotKey>("BotKey", botKeySchema); export const BotKey = model<IBotKey>("BotKey", botKeySchema);
export default BotKey;
+1 -3
View File
@@ -93,6 +93,4 @@ const botOrgSchema = new Schema<IBotOrg>(
} }
); );
const BotOrg = model<IBotOrg>("BotOrg", botOrgSchema); export const BotOrg = model<IBotOrg>("BotOrg", botOrgSchema);
export default BotOrg;
+1 -3
View File
@@ -51,6 +51,4 @@ const folderRootSchema = new Schema<TFolderRootSchema>(
} }
); );
const Folder = model<TFolderRootSchema>("Folder", folderRootSchema); export const Folder = model<TFolderRootSchema>("Folder", folderRootSchema);
export default Folder;
+1 -3
View File
@@ -23,9 +23,7 @@ const incidentContactOrgSchema = new Schema<IIncidentContactOrg>(
} }
); );
const IncidentContactOrg = model<IIncidentContactOrg>( export const IncidentContactOrg = model<IIncidentContactOrg>(
"IncidentContactOrg", "IncidentContactOrg",
incidentContactOrgSchema incidentContactOrgSchema
); );
export default IncidentContactOrg;
+30 -89
View File
@@ -1,89 +1,30 @@
import BackupPrivateKey, { IBackupPrivateKey } from "./backupPrivateKey"; export * from "./backupPrivateKey";
import Bot, { IBot } from "./bot"; export * from "./bot";
import BotOrg, { IBotOrg } from "./botOrg"; export * from "./botOrg";
import BotKey, { IBotKey } from "./botKey"; export * from "./botKey";
import IncidentContactOrg, { IIncidentContactOrg } from "./incidentContactOrg"; export * from "./incidentContactOrg";
import Integration, { IIntegration } from "./integration"; export * from "./integration/integration";
import IntegrationAuth, { IIntegrationAuth } from "./integrationAuth"; export * from "./integrationAuth";
import Key, { IKey } from "./key"; export * from "./key";
import Membership, { IMembership } from "./membership"; export * from "./membership";
import MembershipOrg, { IMembershipOrg } from "./membershipOrg"; export * from "./membershipOrg";
import Organization, { IOrganization } from "./organization"; export * from "./organization";
import Secret, { ISecret } from "./secret"; export * from "./secret";
import Folder, { TFolderRootSchema, TFolderSchema } from "./folder"; export * from "./tag";
import SecretImport, { ISecretImports } from "./secretImports"; export * from "./folder";
import SecretBlindIndexData, { ISecretBlindIndexData } from "./secretBlindIndexData"; export * from "./secretImports";
import ServiceToken, { IServiceToken } from "./serviceToken"; export * from "./secretBlindIndexData";
import ServiceAccount, { IServiceAccount } from "./serviceAccount"; // new export * from "./serviceToken";
import ServiceAccountKey, { IServiceAccountKey } from "./serviceAccountKey"; // new export * from "./serviceAccount";
import ServiceAccountOrganizationPermission, { IServiceAccountOrganizationPermission } from "./serviceAccountOrganizationPermission"; // new export * from "./serviceAccountKey";
import ServiceAccountWorkspacePermission, { IServiceAccountWorkspacePermission } from "./serviceAccountWorkspacePermission"; // new export * from "./serviceAccountOrganizationPermission";
import TokenData, { ITokenData } from "./tokenData"; export * from "./serviceAccountWorkspacePermission";
import User, { AuthMethod, IUser } from "./user"; export * from "./tokenData";
import UserAction, { IUserAction } from "./userAction"; export * from "./user";
import Workspace, { IWorkspace } from "./workspace"; export * from "./userAction";
import ServiceTokenData, { IServiceTokenData } from "./serviceTokenData"; export * from "./workspace";
import APIKeyData, { IAPIKeyData } from "./apiKeyData"; export * from "./serviceTokenData";
import LoginSRPDetail, { ILoginSRPDetail } from "./loginSRPDetail"; export * from "./apiKeyData";
import TokenVersion, { ITokenVersion } from "./tokenVersion"; export * from "./loginSRPDetail";
export * from "./tokenVersion";
export { export * from "./webhooks";
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
};
+1
View File
@@ -0,0 +1 @@
export * from "./integration";
@@ -10,6 +10,7 @@ import {
INTEGRATION_CODEFRESH, INTEGRATION_CODEFRESH,
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM, INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
INTEGRATION_FLYIO, INTEGRATION_FLYIO,
INTEGRATION_GCP_SECRET_MANAGER,
INTEGRATION_GITHUB, INTEGRATION_GITHUB,
INTEGRATION_GITLAB, INTEGRATION_GITLAB,
INTEGRATION_HASHICORP_VAULT, INTEGRATION_HASHICORP_VAULT,
@@ -25,8 +26,9 @@ import {
INTEGRATION_TRAVISCI, INTEGRATION_TRAVISCI,
INTEGRATION_VERCEL, INTEGRATION_VERCEL,
INTEGRATION_WINDMILL INTEGRATION_WINDMILL
} from "../variables"; } from "../../variables";
import { Schema, Types, model } from "mongoose"; import { Schema, Types, model } from "mongoose";
import { Metadata } from "./types";
export interface IIntegration { export interface IIntegration {
_id: Types.ObjectId; _id: Types.ObjectId;
@@ -70,8 +72,10 @@ export interface IIntegration {
| "digital-ocean-app-platform" | "digital-ocean-app-platform"
| "cloud-66" | "cloud-66"
| "northflank" | "northflank"
| "windmill"; | "windmill"
| "gcp-secret-manager";
integrationAuth: Types.ObjectId; integrationAuth: Types.ObjectId;
metadata: Metadata;
} }
const integrationSchema = new Schema<IIntegration>( const integrationSchema = new Schema<IIntegration>(
@@ -167,7 +171,8 @@ const integrationSchema = new Schema<IIntegration>(
INTEGRATION_BITBUCKET, INTEGRATION_BITBUCKET,
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM, INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
INTEGRATION_CLOUD_66, INTEGRATION_CLOUD_66,
INTEGRATION_NORTHFLANK INTEGRATION_NORTHFLANK,
INTEGRATION_GCP_SECRET_MANAGER
], ],
required: true, required: true,
}, },
@@ -180,6 +185,9 @@ const integrationSchema = new Schema<IIntegration>(
type: String, type: String,
required: true, required: true,
default: "/", default: "/",
},
metadata: {
type: Schema.Types.Mixed
} }
}, },
{ {
@@ -187,6 +195,4 @@ const integrationSchema = new Schema<IIntegration>(
} }
); );
const Integration = model<IIntegration>("Integration", integrationSchema); export const Integration = model<IIntegration>("Integration", integrationSchema);
export default Integration;
+3
View File
@@ -0,0 +1,3 @@
export type Metadata = {
secretSuffix?: string;
}
+6 -5
View File
@@ -12,6 +12,7 @@ import {
INTEGRATION_CODEFRESH, INTEGRATION_CODEFRESH,
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM, INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
INTEGRATION_FLYIO, INTEGRATION_FLYIO,
INTEGRATION_GCP_SECRET_MANAGER,
INTEGRATION_GITHUB, INTEGRATION_GITHUB,
INTEGRATION_GITLAB, INTEGRATION_GITLAB,
INTEGRATION_HASHICORP_VAULT, INTEGRATION_HASHICORP_VAULT,
@@ -58,7 +59,8 @@ export interface IIntegrationAuth extends Document {
| "terraform-cloud" | "terraform-cloud"
| "teamcity" | "teamcity"
| "northflank" | "northflank"
| "windmill"; | "windmill"
| "gcp-secret-manager";
teamId: string; teamId: string;
accountId: string; accountId: string;
url: string; url: string;
@@ -111,7 +113,8 @@ const integrationAuthSchema = new Schema<IIntegrationAuth>(
INTEGRATION_BITBUCKET, INTEGRATION_BITBUCKET,
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM, INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
INTEGRATION_CLOUD_66, INTEGRATION_CLOUD_66,
INTEGRATION_NORTHFLANK INTEGRATION_NORTHFLANK,
INTEGRATION_GCP_SECRET_MANAGER
], ],
required: true, required: true,
}, },
@@ -190,9 +193,7 @@ const integrationAuthSchema = new Schema<IIntegrationAuth>(
} }
); );
const IntegrationAuth = model<IIntegrationAuth>( export const IntegrationAuth = model<IIntegrationAuth>(
"IntegrationAuth", "IntegrationAuth",
integrationAuthSchema integrationAuthSchema
); );
export default IntegrationAuth;
+1 -3
View File
@@ -40,6 +40,4 @@ const keySchema = new Schema<IKey>(
} }
); );
const Key = model<IKey>("Key", keySchema); export const Key = model<IKey>("Key", keySchema);
export default Key;
+1 -3
View File
@@ -24,6 +24,4 @@ const loginSRPDetailSchema = new Schema<ILoginSRPDetail>(
} }
); );
const LoginSRPDetail = model("LoginSRPDetail", loginSRPDetailSchema); export const LoginSRPDetail = model("LoginSRPDetail", loginSRPDetailSchema);
export default LoginSRPDetail;
+1 -3
View File
@@ -52,6 +52,4 @@ const membershipSchema = new Schema<IMembership>(
} }
); );
const Membership = model<IMembership>("Membership", membershipSchema); export const Membership = model<IMembership>("Membership", membershipSchema);
export default Membership;
+1 -3
View File
@@ -39,9 +39,7 @@ const membershipOrgSchema = new Schema(
} }
); );
const MembershipOrg = model<IMembershipOrg>( export const MembershipOrg = model<IMembershipOrg>(
"MembershipOrg", "MembershipOrg",
membershipOrgSchema membershipOrgSchema
); );
export default MembershipOrg;
+1 -3
View File
@@ -21,6 +21,4 @@ const organizationSchema = new Schema<IOrganization>(
} }
); );
const Organization = model<IOrganization>("Organization", organizationSchema); export const Organization = model<IOrganization>("Organization", organizationSchema);
export default Organization;
+7 -3
View File
@@ -31,6 +31,9 @@ export interface ISecret {
keyEncoding: "utf8" | "base64"; keyEncoding: "utf8" | "base64";
tags?: string[]; tags?: string[];
folder?: string; folder?: string;
metadata?: {
[key: string]: string;
}
} }
const secretSchema = new Schema<ISecret>( const secretSchema = new Schema<ISecret>(
@@ -131,6 +134,9 @@ const secretSchema = new Schema<ISecret>(
type: String, type: String,
default: "root", default: "root",
}, },
metadata: {
type: Schema.Types.Mixed
}
}, },
{ {
timestamps: true, timestamps: true,
@@ -139,6 +145,4 @@ const secretSchema = new Schema<ISecret>(
secretSchema.index({ tags: 1 }, { background: true }); secretSchema.index({ tags: 1 }, { background: true });
const Secret = model<ISecret>("Secret", secretSchema); export const Secret = model<ISecret>("Secret", secretSchema);
export default Secret;
+2 -4
View File
@@ -1,5 +1,5 @@
import mongoose, { Schema, model } from "mongoose"; import mongoose, { Schema, model } from "mongoose";
import Secret, { ISecret } from "./secret"; import { ISecret, Secret } from "./secret";
interface ISecretApprovalRequest { interface ISecretApprovalRequest {
secret: mongoose.Types.ObjectId; secret: mongoose.Types.ObjectId;
@@ -78,6 +78,4 @@ const secretApprovalRequestSchema = new Schema<ISecretApprovalRequest>(
} }
); );
const SecretApprovalRequest = model<ISecretApprovalRequest>("SecretApprovalRequest", secretApprovalRequestSchema); export const SecretApprovalRequest = model<ISecretApprovalRequest>("SecretApprovalRequest", secretApprovalRequestSchema);
export default SecretApprovalRequest;
+1 -3
View File
@@ -53,6 +53,4 @@ const secretBlindIndexDataSchema = new Schema<ISecretBlindIndexData>(
} }
); );
const SecretBlindIndexData = model<ISecretBlindIndexData>("SecretBlindIndexData", secretBlindIndexDataSchema); export const SecretBlindIndexData = model<ISecretBlindIndexData>("SecretBlindIndexData", secretBlindIndexDataSchema);
export default SecretBlindIndexData;
+1 -2
View File
@@ -48,5 +48,4 @@ const secretImportSchema = new Schema<ISecretImports>(
} }
); );
const SecretImport = model<ISecretImports>("SecretImports", secretImportSchema); export const SecretImport = model<ISecretImports>("SecretImports", secretImportSchema);
export default SecretImport;
+1 -3
View File
@@ -48,6 +48,4 @@ const serviceAccountSchema = new Schema<IServiceAccount>(
} }
); );
const ServiceAccount = model<IServiceAccount>("ServiceAccount", serviceAccountSchema); export const ServiceAccount = model<IServiceAccount>("ServiceAccount", serviceAccountSchema);
export default ServiceAccount;
+1 -3
View File
@@ -39,6 +39,4 @@ const serviceAccountKeySchema = new Schema<IServiceAccountKey>(
} }
); );
const ServiceAccountKey = model<IServiceAccountKey>("ServiceAccountKey", serviceAccountKeySchema); export const ServiceAccountKey = model<IServiceAccountKey>("ServiceAccountKey", serviceAccountKeySchema);
export default ServiceAccountKey;
@@ -18,6 +18,4 @@ const serviceAccountOrganizationPermissionSchema = new Schema<IServiceAccountOrg
} }
); );
const ServiceAccountOrganizationPermission = model<IServiceAccountOrganizationPermission>("ServiceAccountOrganizationPermission", serviceAccountOrganizationPermissionSchema); export const ServiceAccountOrganizationPermission = model<IServiceAccountOrganizationPermission>("ServiceAccountOrganizationPermission", serviceAccountOrganizationPermissionSchema);
export default ServiceAccountOrganizationPermission;
@@ -39,6 +39,4 @@ const serviceAccountWorkspacePermissionSchema = new Schema<IServiceAccountWorksp
} }
); );
const ServiceAccountWorkspacePermission = model<IServiceAccountWorkspacePermission>("ServiceAccountWorkspacePermission", serviceAccountWorkspacePermissionSchema); export const ServiceAccountWorkspacePermission = model<IServiceAccountWorkspacePermission>("ServiceAccountWorkspacePermission", serviceAccountWorkspacePermissionSchema);
export default ServiceAccountWorkspacePermission;
+1 -3
View File
@@ -56,6 +56,4 @@ const serviceTokenSchema = new Schema<IServiceToken>(
} }
); );
const ServiceToken = model<IServiceToken>("ServiceToken", serviceTokenSchema); export const ServiceToken = model<IServiceToken>("ServiceToken", serviceTokenSchema);
export default ServiceToken;
+1 -3
View File
@@ -89,6 +89,4 @@ const serviceTokenDataSchema = new Schema<IServiceTokenData>(
} }
); );
const ServiceTokenData = model<IServiceTokenData>("ServiceTokenData", serviceTokenDataSchema); export const ServiceTokenData = model<IServiceTokenData>("ServiceTokenData", serviceTokenDataSchema);
export default ServiceTokenData;
+7 -3
View File
@@ -3,6 +3,7 @@ import { Schema, Types, model } from "mongoose";
export interface ITag { export interface ITag {
_id: Types.ObjectId; _id: Types.ObjectId;
name: string; name: string;
tagColor: string;
slug: string; slug: string;
user: Types.ObjectId; user: Types.ObjectId;
workspace: Types.ObjectId; workspace: Types.ObjectId;
@@ -15,6 +16,11 @@ const tagSchema = new Schema<ITag>(
required: true, required: true,
trim: true, trim: true,
}, },
tagColor: {
type: String,
required: false,
trim: true,
},
slug: { slug: {
type: String, type: String,
required: true, required: true,
@@ -44,6 +50,4 @@ const tagSchema = new Schema<ITag>(
tagSchema.index({ slug: 1, workspace: 1 }, { unique: true }) tagSchema.index({ slug: 1, workspace: 1 }, { unique: true })
tagSchema.index({ workspace: 1 }) tagSchema.index({ workspace: 1 })
const Tag = model<ITag>("Tag", tagSchema); export const Tag = model<ITag>("Tag", tagSchema);
export default Tag;
+1 -3
View File
@@ -27,6 +27,4 @@ const tokenSchema = new Schema<IToken>({
tokenSchema.index({ email: 1 }); tokenSchema.index({ email: 1 });
const Token = model<IToken>("Token", tokenSchema); export const Token = model<IToken>("Token", tokenSchema);
export default Token;
+1 -3
View File
@@ -50,6 +50,4 @@ const tokenDataSchema = new Schema<ITokenData>({
timestamps: true, timestamps: true,
}); });
const TokenData = model<ITokenData>("TokenData", tokenDataSchema); export const TokenData = model<ITokenData>("TokenData", tokenDataSchema);
export default TokenData;
+1 -3
View File
@@ -42,6 +42,4 @@ const tokenVersionSchema = new Schema<ITokenVersion>(
} }
); );
const TokenVersion = model<ITokenVersion>("TokenVersion", tokenVersionSchema); export const TokenVersion = model<ITokenVersion>("TokenVersion", tokenVersionSchema);
export default TokenVersion;
+1 -3
View File
@@ -121,6 +121,4 @@ const userSchema = new Schema<IUser>(
} }
); );
const User = model<IUser>("User", userSchema); export const User = model<IUser>("User", userSchema);
export default User;
+1 -3
View File
@@ -23,6 +23,4 @@ const userActionSchema = new Schema<IUserAction>(
} }
); );
const UserAction = model<IUserAction>("UserAction", userActionSchema); export const UserAction = model<IUserAction>("UserAction", userActionSchema);
export default UserAction;
+1 -3
View File
@@ -80,6 +80,4 @@ const WebhookSchema = new Schema<IWebhook>(
} }
); );
const Webhook = model<IWebhook>("Webhook", WebhookSchema); export const Webhook = model<IWebhook>("Webhook", WebhookSchema);
export default Webhook;
+1 -3
View File
@@ -49,6 +49,4 @@ const workspaceSchema = new Schema<IWorkspace>({
}, },
}); });
const Workspace = model<IWorkspace>("Workspace", workspaceSchema); export const Workspace = model<IWorkspace>("Workspace", workspaceSchema);
export default Workspace;
@@ -1,6 +1,5 @@
import Queue, { Job } from "bull"; import Queue, { Job } from "bull";
import Integration from "../../models/integration"; import { Integration, IntegrationAuth } from "../../models";
import IntegrationAuth from "../../models/integrationAuth";
import { BotService } from "../../services"; import { BotService } from "../../services";
import { getIntegrationAuthAccessHelper } from "../../helpers"; import { getIntegrationAuthAccessHelper } from "../../helpers";
import { syncSecrets } from "../../integrations/sync" import { syncSecrets } from "../../integrations/sync"
@@ -36,6 +35,14 @@ syncSecretsToThirdPartyServices.process(async (job: Job) => {
secretPath: integration.secretPath 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); const integrationAuth = await IntegrationAuth.findById(integration.integrationAuth);
if (!integrationAuth) throw new Error("Failed to find integration auth"); if (!integrationAuth) throw new Error("Failed to find integration auth");
@@ -49,7 +56,7 @@ syncSecretsToThirdPartyServices.process(async (job: Job) => {
await syncSecrets({ await syncSecrets({
integration, integration,
integrationAuth, integrationAuth,
secrets, secrets: Object.keys(suffixedSecrets).length !== 0 ? suffixedSecrets : secrets,
accessId: access.accessId === undefined ? null : access.accessId, accessId: access.accessId === undefined ? null : access.accessId,
accessToken: access.accessToken accessToken: access.accessToken
}); });
@@ -1,16 +1,16 @@
import Queue, { Job } from "bull"; import Queue, { Job } from "bull";
import { ProbotOctokit } from "probot" import { ProbotOctokit } from "probot"
import { Commit, Committer, Repository } from "@octokit/webhooks-types"; import { Commit } from "@octokit/webhooks-types";
import TelemetryService from "../../services/TelemetryService"; import TelemetryService from "../../services/TelemetryService";
import { sendMail } from "../../helpers"; import { sendMail } from "../../helpers";
import GitRisks from "../../ee/models/gitRisks"; import GitRisks from "../../ee/models/gitRisks";
import { MembershipOrg, User } from "../../models"; import { MembershipOrg, User } from "../../models";
import { OWNER, ADMIN } from "../../variables"; import { ADMIN, OWNER } from "../../variables";
import { convertKeysToLowercase, scanContentAndGetFindings } from "../../ee/services/GithubSecretScanning/helper"; import { convertKeysToLowercase, scanContentAndGetFindings } from "../../ee/services/GithubSecretScanning/helper";
import { getSecretScanningGitAppId, getSecretScanningPrivateKey } from "../../config"; import { getSecretScanningGitAppId, getSecretScanningPrivateKey } from "../../config";
import { SecretMatch } from "../../ee/services/GithubSecretScanning/types"; 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 = { type TScanPushEventQueueDetails = {
organizationId: string, organizationId: string,
+9 -12
View File
@@ -8,7 +8,8 @@ import { AuthMode } from "../../variables";
router.post("/token", validateRequest, authController.getNewToken); 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", "/login1",
authLimiter, authLimiter,
body("email").exists().trim().notEmpty().toLowerCase(), body("email").exists().trim().notEmpty().toLowerCase(),
@@ -17,7 +18,8 @@ router.post( // TODO endpoint: deprecate (moved to api/v3/auth/login1)
authController.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", "/login2",
authLimiter, authLimiter,
body("email").exists().trim().notEmpty().toLowerCase(), body("email").exists().trim().notEmpty().toLowerCase(),
@@ -30,7 +32,7 @@ router.post(
"/logout", "/logout",
authLimiter, authLimiter,
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
authController.logout authController.logout
); );
@@ -38,22 +40,17 @@ router.post(
router.post( router.post(
"/checkAuth", "/checkAuth",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
authController.checkAuth authController.checkAuth
); );
router.get( router.delete(
"/common-passwords", // TODO endpoint: deprecate (moved to DELETE v2/users/me/sessions)
authLimiter,
authController.getCommonPasswords
);
router.delete( // TODO endpoint: deprecate (moved to DELETE v2/users/me/sessions)
"/sessions", "/sessions",
authLimiter, authLimiter,
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
authController.revokeAllSessions authController.revokeAllSessions
); );
+2
View File
@@ -37,6 +37,8 @@ router.post(
body("owner").trim(), body("owner").trim(),
body("path").trim(), body("path").trim(),
body("region").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, validateRequest,
integrationController.createIntegration integrationController.createIntegration
); );
+14
View File
@@ -168,6 +168,20 @@ router.get(
integrationAuthController.getIntegrationAuthNorthflankSecretGroups 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( router.delete(
"/:integrationAuthId", "/:integrationAuthId",
requireAuth({ requireAuth({
+5 -5
View File
@@ -16,7 +16,7 @@ import {
router.post( router.post(
"/:workspaceId/environments", "/:workspaceId/environments",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY],
}), }),
requireWorkspaceAuth({ requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER], acceptedRoles: [ADMIN, MEMBER],
@@ -32,7 +32,7 @@ router.post(
router.put( router.put(
"/:workspaceId/environments", "/:workspaceId/environments",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY],
}), }),
requireWorkspaceAuth({ requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER], acceptedRoles: [ADMIN, MEMBER],
@@ -49,7 +49,7 @@ router.put(
router.patch( router.patch(
"/:workspaceId/environments", "/:workspaceId/environments",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY],
}), }),
requireWorkspaceAuth({ requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER], acceptedRoles: [ADMIN, MEMBER],
@@ -67,7 +67,7 @@ router.patch(
router.delete( router.delete(
"/:workspaceId/environments", "/:workspaceId/environments",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY],
}), }),
requireWorkspaceAuth({ requireWorkspaceAuth({
acceptedRoles: [ADMIN], acceptedRoles: [ADMIN],
@@ -82,7 +82,7 @@ router.delete(
router.get( router.get(
"/:workspaceId/environments", "/:workspaceId/environments",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY],
}), }),
requireWorkspaceAuth({ requireWorkspaceAuth({
acceptedRoles: [MEMBER, ADMIN], acceptedRoles: [MEMBER, ADMIN],
+1
View File
@@ -48,6 +48,7 @@ router.post(
}), }),
param("workspaceId").exists().trim(), param("workspaceId").exists().trim(),
body("name").exists().trim(), body("name").exists().trim(),
body("tagColor").exists().trim(),
body("slug").exists().trim(), body("slug").exists().trim(),
validateRequest, validateRequest,
tagController.createWorkspaceTag tagController.createWorkspaceTag
+3 -1
View File
@@ -1,4 +1,4 @@
import express, { Request, Response } from "express"; import express 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";
@@ -17,6 +17,7 @@ router.get(
"/raw", "/raw",
query("workspaceId").optional().isString().trim(), query("workspaceId").optional().isString().trim(),
query("environment").optional().isString().trim(), query("environment").optional().isString().trim(),
query("folderId").optional().isString().trim(),
query("secretPath").default("/").isString().trim(), query("secretPath").default("/").isString().trim(),
query("include_imports").optional().isBoolean().default(false), query("include_imports").optional().isBoolean().default(false),
validateRequest, validateRequest,
@@ -144,6 +145,7 @@ router.get(
"/", "/",
query("workspaceId").exists().isString().trim(), query("workspaceId").exists().isString().trim(),
query("environment").exists().isString().trim(), query("environment").exists().isString().trim(),
query("folderId").optional().isString().trim(),
query("secretPath").default("/").isString().trim(), query("secretPath").default("/").isString().trim(),
validateRequest, validateRequest,
requireAuth({ requireAuth({
+1 -1
View File
@@ -1,6 +1,6 @@
import { nanoid } from "nanoid"; import { nanoid } from "nanoid";
import { Types } from "mongoose"; import { Types } from "mongoose";
import Folder, { TFolderSchema } from "../models/folder"; import { Folder, TFolderSchema } from "../models";
import path from "path"; import path from "path";
type TAppendFolderDTO = { type TAppendFolderDTO = {
+14 -3
View File
@@ -1,7 +1,10 @@
import { Types } from "mongoose"; import { Types } from "mongoose";
import Folder from "../models/folder"; import {
import Secret, { ISecret } from "../models/secret"; Folder,
import SecretImport from "../models/secretImports"; ISecret,
Secret,
SecretImport
} from "../models";
import { getFolderByPath } from "./FolderService"; import { getFolderByPath } from "./FolderService";
type TSecretImportFid = { environment: string; folderId: string; secretPath: string }; type TSecretImportFid = { environment: string; folderId: string; secretPath: string };
@@ -54,6 +57,14 @@ export const getAllImportedSecrets = async (
type: "shared" 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: { $group: {
_id: { _id: {
+1 -1
View File
@@ -3,7 +3,7 @@ import crypto from "crypto";
import { Types } from "mongoose"; import { Types } from "mongoose";
import picomatch from "picomatch"; import picomatch from "picomatch";
import { client, getRootEncryptionKey } from "../config"; import { client, getRootEncryptionKey } from "../config";
import Webhook, { IWebhook } from "../models/webhooks"; import { IWebhook, Webhook } from "../models";
export const triggerWebhookRequest = async ( export const triggerWebhookRequest = async (
{ url, encryptedSecretKey, iv, tag }: IWebhook, { url, encryptedSecretKey, iv, tag }: IWebhook,
+34 -27
View File
@@ -540,20 +540,26 @@ export const backfillIntegration = async () => {
}; };
export const backfillServiceTokenMultiScope = async () => { export const backfillServiceTokenMultiScope = async () => {
await ServiceTokenData.updateMany( 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 = [
{ {
scopes: { environment: environment,
$exists: false secretPath: secretPath
} }
}, ];
[
{ await ServiceTokenData.updateOne({ _id: doc._id }, { $set: { scopes: updatedScopes } });
$set: {
scopes: [{ environment: "$environment", secretPath: "$secretPath" }]
} }
} }
]
);
console.log("Migration: Service token migration v2 complete"); console.log("Migration: Service token migration v2 complete");
}; };
@@ -649,24 +655,25 @@ export const backfillUserAuthMethods = async () => {
} }
); );
await User.updateMany(
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 },
{ {
authProvider: { $set: { authMethods: authMethods },
$exists: true $unset: { authProvider: 1, authId: 1 }
},
authMethods: {
$exists: false
} }
}, );
[
{
$set: {
authMethods: ["$authProvider"]
} }
},
{
$unset: ["authProvider", "authId"]
}
]
);
} }
+2
View File
@@ -3,3 +3,5 @@ export enum AuthMode {
SERVICE_TOKEN = "serviceToken", SERVICE_TOKEN = "serviceToken",
API_KEY = "apiKey" API_KEY = "apiKey"
} }
export const K8_USER_AGENT_NAME = "k8-operator"
+15 -6
View File
@@ -1,17 +1,19 @@
import { import {
getClientIdAzure, getClientIdAzure,
getClientIdBitBucket, getClientIdBitBucket,
getClientIdGCPSecretManager,
getClientIdGitHub, getClientIdGitHub,
getClientIdGitLab, getClientIdGitLab,
getClientIdHeroku, getClientIdHeroku,
getClientIdNetlify, getClientIdNetlify,
getClientSlugVercel, getClientSlugVercel
} from "../config"; } from "../config";
// integrations // integrations
export const INTEGRATION_AZURE_KEY_VAULT = "azure-key-vault"; export const INTEGRATION_AZURE_KEY_VAULT = "azure-key-vault";
export const INTEGRATION_AWS_PARAMETER_STORE = "aws-parameter-store"; export const INTEGRATION_AWS_PARAMETER_STORE = "aws-parameter-store";
export const INTEGRATION_AWS_SECRET_MANAGER = "aws-secret-manager"; 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_HEROKU = "heroku";
export const INTEGRATION_VERCEL = "vercel"; export const INTEGRATION_VERCEL = "vercel";
export const INTEGRATION_NETLIFY = "netlify"; export const INTEGRATION_NETLIFY = "netlify";
@@ -36,6 +38,7 @@ export const INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM = "digital-ocean-app-platfor
export const INTEGRATION_CLOUD_66 = "cloud-66"; export const INTEGRATION_CLOUD_66 = "cloud-66";
export const INTEGRATION_NORTHFLANK = "northflank"; export const INTEGRATION_NORTHFLANK = "northflank";
export const INTEGRATION_SET = new Set([ export const INTEGRATION_SET = new Set([
INTEGRATION_GCP_SECRET_MANAGER,
INTEGRATION_AZURE_KEY_VAULT, INTEGRATION_AZURE_KEY_VAULT,
INTEGRATION_HEROKU, INTEGRATION_HEROKU,
INTEGRATION_VERCEL, INTEGRATION_VERCEL,
@@ -65,6 +68,7 @@ export const INTEGRATION_SET = new Set([
export const INTEGRATION_OAUTH2 = "oauth2"; export const INTEGRATION_OAUTH2 = "oauth2";
// integration oauth endpoints // 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_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_HEROKU_TOKEN_URL = "https://id.heroku.com/oauth/token";
export const INTEGRATION_VERCEL_TOKEN_URL = 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" export const INTEGRATION_BITBUCKET_TOKEN_URL = "https://bitbucket.org/site/oauth2/access_token"
// integration apps endpoints // 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_HEROKU_API_URL = "https://api.heroku.com";
export const INTEGRATION_GITLAB_API_URL = "https://gitlab.com/api"; export const INTEGRATION_GITLAB_API_URL = "https://gitlab.com/api";
export const INTEGRATION_VERCEL_API_URL = "https://api.vercel.com"; 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_CLOUD_66_API_URL = "https://app.cloud66.com/api";
export const INTEGRATION_NORTHFLANK_API_URL = "https://api.northflank.com"; 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 () => { export const getIntegrationOptions = async () => {
const INTEGRATION_OPTIONS = [ const INTEGRATION_OPTIONS = [
{ {
@@ -272,12 +281,12 @@ export const getIntegrationOptions = async () => {
docsLink: "", docsLink: "",
}, },
{ {
name: "Google Cloud Platform", name: "GCP Secret Manager",
slug: "gcp", slug: "gcp-secret-manager",
image: "Google Cloud Platform.png", image: "Google Cloud Platform.png",
isAvailable: false, isAvailable: true,
type: "", type: "oauth",
clientId: "", clientId: await getClientIdGCPSecretManager(),
docsLink: "" docsLink: ""
}, },
{ {
@@ -22,7 +22,7 @@ Resources:
DocumentDBCluster: DocumentDBCluster:
Type: "AWS::DocDB::DBCluster" Type: "AWS::DocDB::DBCluster"
Properties: Properties:
EngineVersion: 4.0.0 EngineVersion: 5.0.0
StorageEncrypted: true StorageEncrypted: true
MasterUsername: !Ref DocumentDBUsername MasterUsername: !Ref DocumentDBUsername
MasterUserPassword: !Ref DocumentDBPassword MasterUserPassword: !Ref DocumentDBPassword
@@ -38,7 +38,7 @@ Resources:
Type: "AWS::DocDB::DBClusterParameterGroup" Type: "AWS::DocDB::DBClusterParameterGroup"
Properties: Properties:
Description: "description" Description: "description"
Family: "docdb4.0" Family: "docdb5.0"
Parameters: Parameters:
tls: "disabled" tls: "disabled"
ttl_monitor: "disabled" ttl_monitor: "disabled"
@@ -97,6 +97,7 @@ Resources:
echo "JWT_SERVICE_SECRET=${!JWT_SERVICE_SECRET}" >> .env echo "JWT_SERVICE_SECRET=${!JWT_SERVICE_SECRET}" >> .env
echo "MONGO_URL=${!DOCUMENT_DB_CONNECTION_URL}" >> .env echo "MONGO_URL=${!DOCUMENT_DB_CONNECTION_URL}" >> .env
echo "HTTPS_ENABLED=false" >> .env echo "HTTPS_ENABLED=false" >> .env
echo "REDIS_URL=redis://redis:6379" >> .env
docker-compose up -d docker-compose up -d
@@ -0,0 +1,4 @@
---
title: "Create"
openapi: "POST /api/v2/workspace/{workspaceId}/environments"
---
@@ -0,0 +1,4 @@
---
title: "Delete"
openapi: "DELETE /api/v2/workspace/{workspaceId}/environments"
---
@@ -0,0 +1,4 @@
---
title: "List"
openapi: "GET /api/v2/workspace/{workspaceId}/environments"
---
@@ -0,0 +1,4 @@
---
title: "Update"
openapi: "PUT /api/v2/workspace/{workspaceId}/environments"
---
@@ -1,6 +1,6 @@
--- ---
title: "Retrieve" title: "List"
openapi: "GET /api/v3/secrets/{secretName}" openapi: "GET /api/v3/secrets/"
--- ---
<Tip> <Tip>
@@ -1,6 +1,6 @@
--- ---
title: "Retrieve All" title: "Retrieve"
openapi: "GET /api/v3/secrets/" openapi: "GET /api/v3/secrets/{secretName}"
--- ---
<Tip> <Tip>
+15 -13
View File
@@ -9,6 +9,8 @@ The changelog below reflects new product developments and updates on a monthly b
- Release Audit Logs V2. - Release Audit Logs V2.
- Add support for GitHub SSO. - Add support for GitHub SSO.
- Enable users to opt in for multiple authentication methods. - 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 ## July 2023
@@ -16,17 +18,17 @@ The changelog below reflects new product developments and updates on a monthly b
- Redesigned the project/organization experience. - Redesigned the project/organization experience.
- Updated the secrets overview page; users are now able to edit secrets directly from it. - 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 [Laravel Forge integration](https://infisical.com/docs/integrations/cloud/laravel-forge).
- Added native [Codefresh integration](https://infisical.com/docs/integrations/cicd/codefresh) - Added native [Codefresh integration](https://infisical.com/docs/integrations/cicd/codefresh).
- Added native [Bitbucket integration](https://infisical.com/docs/integrations/cicd/bitbucket) - 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 [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 [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 [Terraform Cloud integration](https://infisical.com/docs/integrations/cloud/terraform-cloud).
- Added native [Northflank integration](https://infisical.com/docs/integrations/cloud/northflank) - Added native [Northflank integration](https://infisical.com/docs/integrations/cloud/northflank).
- Added native [Windmill integration](https://infisical.com/docs/integrations/cloud/windmill) - Added native [Windmill integration](https://infisical.com/docs/integrations/cloud/windmill).
- Added support for Google SSO. - 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. - 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 [folders / path-based secret storage](https://infisical.com/docs/documentation/platform/folder).
- Released [webhooks](https://infisical.com/docs/documentation/platform/webhooks) - Released [webhooks](https://infisical.com/docs/documentation/platform/webhooks).
## June 2023 ## June 2023
@@ -68,7 +70,7 @@ The changelog below reflects new product developments and updates on a monthly b
## Feb 2023 ## Feb 2023
- Upgraded private key encryption/decryption mechanism to use Argon2id and 256-bit protected keys. - 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 suspicious login alerting if user logs in via new device or IP address.
- Added documentation for PM2 integration. - 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. - 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 native GitHub Actions integration.
- Added custom environment names. - Added custom environment names.
- Added auto-redeployment capability to the Kubernetes operator. - Added auto-redeployment capability to the Kubernetes operator.
- (Service Token 2.0) Shortened the length of service tokens - (Service Token 2.0) Shortened the length of service tokens.
- Added a public-facing API - Added a public-facing API.
- Added preliminary access control capability for users to be provisioned read/write access to environments - Added preliminary access control capability for users to be provisioned read/write access to environments.
- Performed various web UI optimizations. - Performed various web UI optimizations.
## Nov 2022 ## 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

Before

Width:  |  Height:  |  Size: 189 KiB

After

Width:  |  Height:  |  Size: 189 KiB

Before

Width:  |  Height:  |  Size: 352 KiB

After

Width:  |  Height:  |  Size: 352 KiB

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

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