Merge remote-tracking branch 'origin/main' into secret-scan-whole-repo
@@ -3203,6 +3203,9 @@
|
|||||||
"name": {
|
"name": {
|
||||||
"example": "any"
|
"example": "any"
|
||||||
},
|
},
|
||||||
|
"tagColor": {
|
||||||
|
"example": "any"
|
||||||
|
},
|
||||||
"slug": {
|
"slug": {
|
||||||
"example": "any"
|
"example": "any"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 },
|
||||||
clientPublicKey: clientPublicKey,
|
{
|
||||||
serverBInt: bigintConversion.bigintToBuf(server.bInt),
|
email: email,
|
||||||
}, { upsert: true, returnNewDocument: false })
|
clientPublicKey: clientPublicKey,
|
||||||
|
serverBInt: bigintConversion.bigintToBuf(server.bInt)
|
||||||
|
},
|
||||||
|
{ 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 &&
|
||||||
userId: user._id,
|
(await EELogService.createLog({
|
||||||
actions: [loginAction],
|
userId: user._id,
|
||||||
channel: getUserAgentType(req.headers["user-agent"]),
|
actions: [loginAction],
|
||||||
ipAddress: req.realIP,
|
channel: getUserAgentType(req.headers["user-agent"]),
|
||||||
});
|
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 &&
|
||||||
userId: req.user._id,
|
(await EELogService.createLog({
|
||||||
actions: [logoutAction],
|
userId: req.user._id,
|
||||||
channel: getUserAgentType(req.headers["user-agent"]),
|
actions: [logoutAction],
|
||||||
ipAddress: req.realIP,
|
channel: getUserAgentType(req.headers["user-agent"]),
|
||||||
});
|
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: {
|
|
||||||
refreshVersion: 1,
|
|
||||||
accessVersion: 1,
|
|
||||||
},
|
},
|
||||||
});
|
{
|
||||||
|
$inc: {
|
||||||
|
refreshVersion: 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)
|
||||||
message: "Failed to find refresh token in request cookies"
|
throw BadRequestError({
|
||||||
});
|
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)
|
||||||
}
|
}
|
||||||
@@ -335,13 +337,13 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
|||||||
$in: deleteSecretIds
|
$in: deleteSecretIds
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.reduce(
|
.reduce(
|
||||||
(obj: any, secret: ISecret) => ({
|
(obj: any, secret: ISecret) => ({
|
||||||
...obj,
|
...obj,
|
||||||
[secret._id.toString()]: secret
|
[secret._id.toString()]: secret
|
||||||
}),
|
}),
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
|
|
||||||
await Secret.deleteMany({
|
await Secret.deleteMany({
|
||||||
_id: {
|
_id: {
|
||||||
@@ -966,21 +968,36 @@ 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) {
|
||||||
postHogClient.capture({
|
const shouldCapture = req.authData.userAgent !== K8_USER_AGENT_NAME || shouldRecordK8Event;
|
||||||
event: "secrets pulled",
|
const approximateForNoneCapturedEvents = secrets.length * 10
|
||||||
distinctId: await TelemetryService.getDistinctId({
|
|
||||||
authData: req.authData
|
if (shouldCapture) {
|
||||||
}),
|
postHogClient.capture({
|
||||||
properties: {
|
event: "secrets pulled",
|
||||||
numberOfSecrets: secrets.length,
|
distinctId: await TelemetryService.getDistinctId({
|
||||||
environment,
|
authData: req.authData
|
||||||
workspaceId,
|
}),
|
||||||
channel,
|
properties: {
|
||||||
folderId,
|
numberOfSecrets: shouldRecordK8Event ? approximateForNoneCapturedEvents : secrets.length,
|
||||||
userAgent: req.headers?.["user-agent"]
|
environment,
|
||||||
}
|
workspaceId,
|
||||||
});
|
folderId,
|
||||||
|
channel: req.authData.userAgentType,
|
||||||
|
userAgent: req.authData.userAgent
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
@@ -1087,10 +1104,10 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
|||||||
tags,
|
tags,
|
||||||
...(secretCommentCiphertext !== undefined && secretCommentIV && secretCommentTag
|
...(secretCommentCiphertext !== undefined && secretCommentIV && secretCommentTag
|
||||||
? {
|
? {
|
||||||
secretCommentCiphertext,
|
secretCommentCiphertext,
|
||||||
secretCommentIV,
|
secretCommentIV,
|
||||||
secretCommentTag
|
secretCommentTag
|
||||||
}
|
}
|
||||||
: {})
|
: {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,21 +574,36 @@ export const getSecretsHelper = async ({
|
|||||||
|
|
||||||
const postHogClient = await TelemetryService.getPostHogClient();
|
const postHogClient = await TelemetryService.getPostHogClient();
|
||||||
|
|
||||||
if (postHogClient) {
|
// reduce the number of events captured
|
||||||
postHogClient.capture({
|
let shouldRecordK8Event = false
|
||||||
event: "secrets pulled",
|
if (authData.userAgent == K8_USER_AGENT_NAME) {
|
||||||
distinctId: await TelemetryService.getDistinctId({
|
const randomNumber = Math.random();
|
||||||
authData
|
if (randomNumber > 0.9) {
|
||||||
}),
|
shouldRecordK8Event = true
|
||||||
properties: {
|
}
|
||||||
numberOfSecrets: secrets.length,
|
}
|
||||||
environment,
|
|
||||||
workspaceId,
|
const numberOfSignupSecrets = (secrets.filter((secret) => secret?.metadata?.source === "signup")).length;
|
||||||
folderId,
|
const atLeastOneNonSignUpSecret = (secrets.length - numberOfSignupSecrets > 0)
|
||||||
channel: authData.userAgentType,
|
|
||||||
userAgent: authData.userAgent
|
if (postHogClient && atLeastOneNonSignUpSecret) {
|
||||||
}
|
const shouldCapture = authData.userAgent !== K8_USER_AGENT_NAME || shouldRecordK8Event;
|
||||||
});
|
const approximateForNoneCapturedEvents = secrets.length * 10
|
||||||
|
|
||||||
|
if (shouldCapture) {
|
||||||
|
postHogClient.capture({
|
||||||
|
event: "secrets pulled",
|
||||||
|
distinctId: await TelemetryService.getDistinctId({ authData }),
|
||||||
|
properties: {
|
||||||
|
numberOfSecrets: shouldRecordK8Event ? approximateForNoneCapturedEvents : secrets.length,
|
||||||
|
environment,
|
||||||
|
workspaceId,
|
||||||
|
folderId,
|
||||||
|
channel: authData.userAgentType,
|
||||||
|
userAgent: authData.userAgent
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return secrets;
|
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
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,57 +2208,124 @@ const syncSecretsTeamCity = async ({
|
|||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// get secrets from Teamcity
|
interface TeamCityBuildConfigParameter {
|
||||||
const res = (
|
name: string;
|
||||||
await standardRequest.get(
|
value: string;
|
||||||
`${integrationAuth.url}/app/rest/projects/id:${integration.appId}/parameters`,
|
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: {
|
headers: {
|
||||||
Authorization: `Bearer ${accessToken}`,
|
Authorization: `Bearer ${accessToken}`,
|
||||||
Accept: "application/json"
|
Accept: "application/json",
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
)
|
))
|
||||||
).data.property.reduce((obj: any, secret: TeamCitySecret) => {
|
.data
|
||||||
const secretName = secret.name.replace(/^env\./, "");
|
.property
|
||||||
return {
|
.filter((parameter) => !parameter.inherited)
|
||||||
...obj,
|
.reduce((obj: any, secret: TeamCitySecret) => {
|
||||||
[secretName]: secret.value
|
const secretName = secret.name.replace(/^env\./, "");
|
||||||
};
|
return {
|
||||||
}, {});
|
...obj,
|
||||||
|
[secretName]: secret.value
|
||||||
|
};
|
||||||
|
}, {});
|
||||||
|
|
||||||
for await (const key of Object.keys(secrets)) {
|
for await (const key of Object.keys(secrets)) {
|
||||||
if (!(key in res) || (key in res && secrets[key] !== res[key])) {
|
if (!(key in res) || (key in res && secrets[key].value !== res[key])) {
|
||||||
// case: secret does not exist in TeamCity or secret value has changed
|
// case: secret does not exist in TeamCity or secret value has changed
|
||||||
// -> create/update secret
|
// -> create/update secret
|
||||||
await standardRequest.post(
|
await standardRequest.post(`${integrationAuth.url}/app/rest/buildTypes/${integration.targetEnvironmentId}/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: {
|
||||||
Authorization: `Bearer ${accessToken}`,
|
Authorization: `Bearer ${accessToken}`,
|
||||||
Accept: "application/json"
|
Accept: "application/json",
|
||||||
}
|
},
|
||||||
}
|
});
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
for await (const key of Object.keys(res)) {
|
for await (const key of Object.keys(res)) {
|
||||||
if (!(key in secrets)) {
|
if (!(key in secrets)) {
|
||||||
// delete secret
|
// delete secret
|
||||||
await standardRequest.delete(
|
await standardRequest.delete(
|
||||||
`${integrationAuth.url}/app/rest/projects/id:${integration.appId}/parameters/env.${key}`,
|
`${integrationAuth.url}/app/rest/buildTypes/${integration.targetEnvironmentId}/parameters/env.${key}`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
Accept: "application/json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// case: sync to TeamCity project
|
||||||
|
const res = (
|
||||||
|
await standardRequest.get(
|
||||||
|
`${integrationAuth.url}/app/rest/projects/id:${integration.appId}/parameters`,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${accessToken}`,
|
Authorization: `Bearer ${accessToken}`,
|
||||||
Accept: "application/json"
|
Accept: "application/json"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
|
).data.property.reduce((obj: any, secret: TeamCitySecret) => {
|
||||||
|
const secretName = secret.name.replace(/^env\./, "");
|
||||||
|
return {
|
||||||
|
...obj,
|
||||||
|
[secretName]: secret.value
|
||||||
|
};
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
for await (const key of Object.keys(secrets)) {
|
||||||
|
if (!(key in res) || (key in res && secrets[key] !== res[key])) {
|
||||||
|
// case: secret does not exist in TeamCity or secret value has changed
|
||||||
|
// -> create/update secret
|
||||||
|
await standardRequest.post(
|
||||||
|
`${integrationAuth.url}/app/rest/projects/id:${integration.appId}/parameters`,
|
||||||
|
{
|
||||||
|
name: `env.${key}`,
|
||||||
|
value: secrets[key].value
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
Accept: "application/json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for await (const key of Object.keys(res)) {
|
||||||
|
if (!(key in secrets)) {
|
||||||
|
// delete secret
|
||||||
|
await standardRequest.delete(
|
||||||
|
`${integrationAuth.url}/app/rest/projects/id:${integration.appId}/parameters/env.${key}`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
Accept: "application/json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
|
|||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
};
|
|
||||||
@@ -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;
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export type Metadata = {
|
||||||
|
secretSuffix?: string;
|
||||||
|
}
|
||||||
@@ -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;
|
|
||||||
|
|||||||
@@ -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;
|
|
||||||
@@ -24,6 +24,4 @@ const loginSRPDetailSchema = new Schema<ILoginSRPDetail>(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const LoginSRPDetail = model("LoginSRPDetail", loginSRPDetailSchema);
|
export const LoginSRPDetail = model("LoginSRPDetail", loginSRPDetailSchema);
|
||||||
|
|
||||||
export default LoginSRPDetail;
|
|
||||||
@@ -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;
|
|
||||||
@@ -39,9 +39,7 @@ const membershipOrgSchema = new Schema(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const MembershipOrg = model<IMembershipOrg>(
|
export const MembershipOrg = model<IMembershipOrg>(
|
||||||
"MembershipOrg",
|
"MembershipOrg",
|
||||||
membershipOrgSchema
|
membershipOrgSchema
|
||||||
);
|
);
|
||||||
|
|
||||||
export default MembershipOrg;
|
|
||||||
|
|||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -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],
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,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 = {
|
||||||
|
|||||||
@@ -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: {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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 } });
|
||||||
{
|
|
||||||
scopes: {
|
for (const doc of documentsToUpdate) {
|
||||||
$exists: false
|
// Cast doc to any to bypass TypeScript's type checks
|
||||||
}
|
const anyDoc = doc as any;
|
||||||
},
|
|
||||||
[
|
const environment = anyDoc.environment;
|
||||||
{
|
const secretPath = anyDoc.secretPath;
|
||||||
$set: {
|
|
||||||
scopes: [{ environment: "$environment", secretPath: "$secretPath" }]
|
if (environment && secretPath) {
|
||||||
|
const updatedScopes = [
|
||||||
|
{
|
||||||
|
environment: environment,
|
||||||
|
secretPath: secretPath
|
||||||
}
|
}
|
||||||
}
|
];
|
||||||
]
|
|
||||||
);
|
await ServiceTokenData.updateOne({ _id: doc._id }, { $set: { scopes: updatedScopes } });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
console.log("Migration: Service token migration v2 complete");
|
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: {
|
authProvider: { $exists: true },
|
||||||
$exists: true
|
authMethods: { $exists: false }
|
||||||
},
|
});
|
||||||
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;
|
||||||
$set: {
|
const authMethods = [authProvider];
|
||||||
authMethods: ["$authProvider"]
|
|
||||||
|
await User.updateOne(
|
||||||
|
{ _id: doc._id },
|
||||||
|
{
|
||||||
|
$set: { authMethods: authMethods },
|
||||||
|
$unset: { authProvider: 1, authId: 1 }
|
||||||
}
|
}
|
||||||
},
|
);
|
||||||
{
|
}
|
||||||
$unset: ["authProvider", "authId"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -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,35 +38,37 @@ 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,
|
||||||
INTEGRATION_NETLIFY,
|
INTEGRATION_NETLIFY,
|
||||||
INTEGRATION_GITHUB,
|
INTEGRATION_GITHUB,
|
||||||
INTEGRATION_GITLAB,
|
INTEGRATION_GITLAB,
|
||||||
INTEGRATION_RENDER,
|
INTEGRATION_RENDER,
|
||||||
INTEGRATION_FLYIO,
|
INTEGRATION_FLYIO,
|
||||||
INTEGRATION_CIRCLECI,
|
INTEGRATION_CIRCLECI,
|
||||||
INTEGRATION_LARAVELFORGE,
|
INTEGRATION_LARAVELFORGE,
|
||||||
INTEGRATION_TRAVISCI,
|
INTEGRATION_TRAVISCI,
|
||||||
INTEGRATION_TEAMCITY,
|
INTEGRATION_TEAMCITY,
|
||||||
INTEGRATION_SUPABASE,
|
INTEGRATION_SUPABASE,
|
||||||
INTEGRATION_CHECKLY,
|
INTEGRATION_CHECKLY,
|
||||||
INTEGRATION_TERRAFORM_CLOUD,
|
INTEGRATION_TERRAFORM_CLOUD,
|
||||||
INTEGRATION_HASHICORP_VAULT,
|
INTEGRATION_HASHICORP_VAULT,
|
||||||
INTEGRATION_CLOUDFLARE_PAGES,
|
INTEGRATION_CLOUDFLARE_PAGES,
|
||||||
INTEGRATION_CODEFRESH,
|
INTEGRATION_CODEFRESH,
|
||||||
INTEGRATION_WINDMILL,
|
INTEGRATION_WINDMILL,
|
||||||
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 types
|
// integration types
|
||||||
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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
|
After Width: | Height: | Size: 444 KiB |
|
After Width: | Height: | Size: 503 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
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 |
|
After Width: | Height: | Size: 179 KiB |
|
After Width: | Height: | Size: 370 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 940 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 740 KiB |
|
After Width: | Height: | Size: 856 KiB |
|
After Width: | Height: | Size: 782 KiB |