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

This commit is contained in:
Daniel Inge
2023-09-01 22:43:20 +01:00
207 changed files with 3148 additions and 3019 deletions
+85 -96
View File
@@ -1,32 +1,20 @@
import { Request, Response } from "express";
import fs from "fs";
import path from "path";
import jwt from "jsonwebtoken";
import * as bigintConversion from "bigint-conversion";
// eslint-disable-next-line @typescript-eslint/no-var-requires
const jsrp = require("jsrp");
import {
LoginSRPDetail,
TokenVersion,
User,
} from "../../models";
import { LoginSRPDetail, TokenVersion, User } from "../../models";
import { clearTokens, createToken, issueAuthTokens } from "../../helpers/auth";
import { checkUserDevice } from "../../helpers/user";
import {
ACTION_LOGIN,
ACTION_LOGOUT,
} from "../../variables";
import {
BadRequestError,
UnauthorizedRequestError,
} from "../../utils/errors";
import { ACTION_LOGIN, ACTION_LOGOUT } from "../../variables";
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
import { EELogService } from "../../ee/services";
import { getUserAgentType } from "../../utils/posthog";
import {
getHttpsEnabled,
getJwtAuthLifetime,
getJwtAuthSecret,
getJwtRefreshSecret,
getJwtRefreshSecret
} from "../../config";
import { ActorType } from "../../ee/models";
@@ -44,13 +32,10 @@ declare module "jsonwebtoken" {
* @returns
*/
export const login1 = async (req: Request, res: Response) => {
const {
email,
clientPublicKey,
}: { email: string; clientPublicKey: string } = req.body;
const { email, clientPublicKey }: { email: string; clientPublicKey: string } = req.body;
const user = await User.findOne({
email,
email
}).select("+salt +verifier");
if (!user) throw new Error("Failed to find user");
@@ -59,21 +44,25 @@ export const login1 = async (req: Request, res: Response) => {
server.init(
{
salt: user.salt,
verifier: user.verifier,
verifier: user.verifier
},
async () => {
// generate server-side public key
const serverPublicKey = server.getPublicKey();
await LoginSRPDetail.findOneAndReplace({ email: email }, {
email: email,
clientPublicKey: clientPublicKey,
serverBInt: bigintConversion.bigintToBuf(server.bInt),
}, { upsert: true, returnNewDocument: false })
await LoginSRPDetail.findOneAndReplace(
{ email: email },
{
email: email,
clientPublicKey: clientPublicKey,
serverBInt: bigintConversion.bigintToBuf(server.bInt)
},
{ upsert: true, returnNewDocument: false }
);
return res.status(200).send({
serverPublicKey,
salt: user.salt,
salt: user.salt
});
}
);
@@ -89,15 +78,19 @@ export const login1 = async (req: Request, res: Response) => {
export const login2 = async (req: Request, res: Response) => {
const { email, clientProof } = req.body;
const user = await User.findOne({
email,
email
}).select("+salt +verifier +publicKey +encryptedPrivateKey +iv +tag");
if (!user) throw new Error("Failed to find user");
const loginSRPDetailFromDB = await LoginSRPDetail.findOneAndDelete({ email: email })
const loginSRPDetailFromDB = await LoginSRPDetail.findOneAndDelete({ email: email });
if (!loginSRPDetailFromDB) {
return BadRequestError(Error("It looks like some details from the first login are not found. Please try login one again"))
return BadRequestError(
Error(
"It looks like some details from the first login are not found. Please try login one again"
)
);
}
const server = new jsrp.server();
@@ -105,7 +98,7 @@ export const login2 = async (req: Request, res: Response) => {
{
salt: user.salt,
verifier: user.verifier,
b: loginSRPDetailFromDB.serverBInt,
b: loginSRPDetailFromDB.serverBInt
},
async () => {
server.setClientPublicKey(loginSRPDetailFromDB.clientPublicKey);
@@ -117,13 +110,13 @@ export const login2 = async (req: Request, res: Response) => {
await checkUserDevice({
user,
ip: req.realIP,
userAgent: req.headers["user-agent"] ?? "",
userAgent: req.headers["user-agent"] ?? ""
});
const tokens = await issueAuthTokens({
const tokens = await issueAuthTokens({
userId: user._id,
ip: req.realIP,
userAgent: req.headers["user-agent"] ?? "",
userAgent: req.headers["user-agent"] ?? ""
});
// store (refresh) token in httpOnly cookie
@@ -131,20 +124,21 @@ export const login2 = async (req: Request, res: Response) => {
httpOnly: true,
path: "/",
sameSite: "strict",
secure: await getHttpsEnabled(),
secure: await getHttpsEnabled()
});
const loginAction = await EELogService.createAction({
name: ACTION_LOGIN,
userId: user._id,
userId: user._id
});
loginAction && await EELogService.createLog({
userId: user._id,
actions: [loginAction],
channel: getUserAgentType(req.headers["user-agent"]),
ipAddress: req.realIP,
});
loginAction &&
(await EELogService.createLog({
userId: user._id,
actions: [loginAction],
channel: getUserAgentType(req.headers["user-agent"]),
ipAddress: req.realIP
}));
// return (access) token in response
return res.status(200).send({
@@ -152,12 +146,12 @@ export const login2 = async (req: Request, res: Response) => {
publicKey: user.publicKey,
encryptedPrivateKey: user.encryptedPrivateKey,
iv: user.iv,
tag: user.tag,
tag: user.tag
});
}
return res.status(400).send({
message: "Failed to authenticate. Try again?",
message: "Failed to authenticate. Try again?"
});
}
);
@@ -171,7 +165,7 @@ export const login2 = async (req: Request, res: Response) => {
*/
export const logout = async (req: Request, res: Response) => {
if (req.authData.actor.type === ActorType.USER && req.authData.tokenVersionId) {
await clearTokens(req.authData.tokenVersionId)
await clearTokens(req.authData.tokenVersionId);
}
// clear httpOnly cookie
@@ -179,49 +173,44 @@ export const logout = async (req: Request, res: Response) => {
httpOnly: true,
path: "/",
sameSite: "strict",
secure: (await getHttpsEnabled()) as boolean,
secure: (await getHttpsEnabled()) as boolean
});
const logoutAction = await EELogService.createAction({
name: ACTION_LOGOUT,
userId: req.user._id,
userId: req.user._id
});
logoutAction && await EELogService.createLog({
userId: req.user._id,
actions: [logoutAction],
channel: getUserAgentType(req.headers["user-agent"]),
ipAddress: req.realIP,
});
logoutAction &&
(await EELogService.createLog({
userId: req.user._id,
actions: [logoutAction],
channel: getUserAgentType(req.headers["user-agent"]),
ipAddress: req.realIP
}));
return res.status(200).send({
message: "Successfully logged out.",
message: "Successfully logged out."
});
};
export const getCommonPasswords = async (req: Request, res: Response) => {
const commonPasswords = fs.readFileSync(
path.resolve(__dirname, "../../data/" + "common_passwords.txt"),
"utf8"
).split("\n");
return res.status(200).send(commonPasswords);
}
export const revokeAllSessions = async (req: Request, res: Response) => {
await TokenVersion.updateMany({
user: req.user._id,
}, {
$inc: {
refreshVersion: 1,
accessVersion: 1,
await TokenVersion.updateMany(
{
user: req.user._id
},
});
{
$inc: {
refreshVersion: 1,
accessVersion: 1
}
}
);
return res.status(200).send({
message: "Successfully revoked all sessions.",
});
}
message: "Successfully revoked all sessions."
});
};
/**
* Return user is authenticated
@@ -231,9 +220,9 @@ export const revokeAllSessions = async (req: Request, res: Response) => {
*/
export const checkAuth = async (req: Request, res: Response) => {
return res.status(200).send({
message: "Authenticated",
message: "Authenticated"
});
}
};
/**
* Return new JWT access token by first validating the refresh token
@@ -244,47 +233,47 @@ export const checkAuth = async (req: Request, res: Response) => {
export const getNewToken = async (req: Request, res: Response) => {
const refreshToken = req.cookies.jid;
if (!refreshToken) throw BadRequestError({
message: "Failed to find refresh token in request cookies"
});
if (!refreshToken)
throw BadRequestError({
message: "Failed to find refresh token in request cookies"
});
const decodedToken = <jwt.UserIDJwtPayload>(
jwt.verify(refreshToken, await getJwtRefreshSecret())
);
const decodedToken = <jwt.UserIDJwtPayload>jwt.verify(refreshToken, await getJwtRefreshSecret());
const user = await User.findOne({
_id: decodedToken.userId,
_id: decodedToken.userId
}).select("+publicKey +refreshVersion +accessVersion");
if (!user) throw new Error("Failed to authenticate unfound user");
if (!user?.publicKey)
throw new Error("Failed to authenticate not fully set up account");
if (!user?.publicKey) throw new Error("Failed to authenticate not fully set up account");
const tokenVersion = await TokenVersion.findById(decodedToken.tokenVersionId);
if (!tokenVersion) throw UnauthorizedRequestError({
message: "Failed to validate refresh token",
});
if (!tokenVersion)
throw UnauthorizedRequestError({
message: "Failed to validate refresh token"
});
if (decodedToken.refreshVersion !== tokenVersion.refreshVersion) throw BadRequestError({
message: "Failed to validate refresh token",
});
if (decodedToken.refreshVersion !== tokenVersion.refreshVersion)
throw BadRequestError({
message: "Failed to validate refresh token"
});
const token = createToken({
payload: {
userId: decodedToken.userId,
tokenVersionId: tokenVersion._id.toString(),
accessVersion: tokenVersion.refreshVersion,
accessVersion: tokenVersion.refreshVersion
},
expiresIn: await getJwtAuthLifetime(),
secret: await getJwtAuthSecret(),
secret: await getJwtAuthSecret()
});
return res.status(200).send({
token,
token
});
};
export const handleAuthProviderCallback = (req: Request, res: Response) => {
res.redirect(`/login/provider/success?token=${encodeURIComponent(req.providerAuthToken)}`);
}
};
@@ -547,6 +547,57 @@ export const getIntegrationAuthNorthflankSecretGroups = async (req: Request, res
});
}
/**
* Return list of build configs for TeamCity project with id [appId]
* @param req
* @param res
* @returns
*/
export const getIntegrationAuthTeamCityBuildConfigs = async (req: Request, res: Response) => {
const appId = req.query.appId as string;
interface TeamCityBuildConfig {
id: string;
name: string;
projectName: string;
projectId: string;
href: string;
webUrl: string;
}
interface GetTeamCityBuildConfigsRes {
count: number;
href: string;
buildType: TeamCityBuildConfig[];
}
if (appId && appId !== "") {
const { data: { buildType } } = (
await standardRequest.get<GetTeamCityBuildConfigsRes>(`${req.integrationAuth.url}/app/rest/buildTypes`, {
params: {
locator: `project:${appId}`
},
headers: {
Authorization: `Bearer ${req.accessToken}`,
Accept: "application/json",
},
})
);
return res.status(200).send({
buildConfigs: buildType.map((buildConfig) => ({
name: buildConfig.name,
buildConfigId: buildConfig.id
}))
});
}
return res.status(200).send({
buildConfigs: []
});
}
/**
* Delete integration authorization with id [integrationAuthId]
* @param req
@@ -1,9 +1,8 @@
import { Request, Response } from "express";
import { Types } from "mongoose";
import { Integration } from "../../models";
import { Folder, Integration } from "../../models";
import { EventService } from "../../services";
import { eventStartIntegration } from "../../events";
import Folder from "../../models/folder";
import { getFolderByPath } from "../../services/FolderService";
import { BadRequestError } from "../../utils/errors";
import { EEAuditLogService } from "../../ee/services";
@@ -30,7 +29,8 @@ export const createIntegration = async (req: Request, res: Response) => {
owner,
path,
region,
secretPath
secretPath,
metadata
} = req.body;
const folders = await Folder.findOne({
@@ -65,7 +65,8 @@ export const createIntegration = async (req: Request, res: Response) => {
region,
secretPath,
integration: req.integrationAuth.integration,
integrationAuth: new Types.ObjectId(integrationAuthId)
integrationAuth: new Types.ObjectId(integrationAuthId),
metadata
}).save();
if (integration) {
@@ -1,8 +1,6 @@
import { Request, Response } from "express";
import { isValidScope, validateMembership } from "../../helpers";
import { ServiceTokenData } from "../../models";
import Folder from "../../models/folder";
import SecretImport from "../../models/secretImports";
import { Folder, SecretImport, ServiceTokenData } from "../../models";
import { getAllImportedSecrets } from "../../services/SecretImportService";
import { getFolderWithPathFromId } from "../../services/FolderService";
import { BadRequestError, ResourceNotFoundError,UnauthorizedRequestError } from "../../utils/errors";
@@ -4,8 +4,7 @@ import { EventType, FolderVersion } from "../../ee/models";
import { EEAuditLogService, EESecretService } from "../../ee/services";
import { validateMembership } from "../../helpers/membership";
import { isValidScope } from "../../helpers/secrets";
import { Secret, ServiceTokenData } from "../../models";
import Folder from "../../models/folder";
import { Folder, Secret, ServiceTokenData } from "../../models";
import {
appendFolder,
deleteFolderById,
@@ -2,7 +2,7 @@ import { Request, Response } from "express";
import { Types } from "mongoose";
import { client, getRootEncryptionKey } from "../../config";
import { validateMembership } from "../../helpers";
import Webhook from "../../models/webhooks";
import { Webhook } from "../../models";
import { getWebhookPayload, triggerWebhookRequest } from "../../services/WebhookService";
import { BadRequestError, ResourceNotFoundError } from "../../utils/errors";
import { EEAuditLogService } from "../../ee/services";
@@ -1,6 +1,5 @@
import { Request, Response } from "express";
import mongoose, { Types } from "mongoose";
import Secret, { ISecret } from "../../models/secret";
import {
CreateSecretRequestBody,
ModifySecretRequestBody,
@@ -20,7 +19,7 @@ import {
SECRET_SHARED
} from "../../variables";
import { TelemetryService } from "../../services";
import { User } from "../../models";
import { ISecret, Secret, User } from "../../models";
import { AccountNotFoundError } from "../../utils/errors";
/**
+53 -36
View File
@@ -1,6 +1,6 @@
import { Types } from "mongoose";
import { Request, Response } from "express";
import { ISecret, Secret, ServiceTokenData } from "../../models";
import { Folder, ISecret, Secret, ServiceTokenData, Tag } from "../../models";
import { AuditLog, EventType, IAction, SecretVersion } from "../../ee/models";
import {
ACTION_ADD_SECRETS,
@@ -9,6 +9,7 @@ import {
ACTION_UPDATE_SECRETS,
ALGORITHM_AES_256_GCM,
ENCODING_SCHEME_UTF8,
K8_USER_AGENT_NAME,
SECRET_PERSONAL
} from "../../variables";
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
@@ -23,10 +24,8 @@ import {
userHasWorkspaceAccess,
userHasWriteOnlyAbility
} from "../../ee/helpers/checkMembershipPermissions";
import Tag from "../../models/tag";
import _ from "lodash";
import { BatchSecret, BatchSecretRequest } from "../../types/secret";
import Folder from "../../models/folder";
import {
getFolderByPath,
getFolderIdFromServiceToken,
@@ -59,7 +58,7 @@ export const batchSecrets = async (req: Request, res: Response) => {
let secretPath = req.body.secretPath as string;
let folderId = req.body.folderId as string;
const createSecrets: BatchSecret[] = [];
const updateSecrets: BatchSecret[] = [];
const deleteSecrets: { _id: Types.ObjectId, secretName: string; }[] = [];
@@ -154,7 +153,7 @@ export const batchSecrets = async (req: Request, res: Response) => {
};
})
});
const auditLogs = await Promise.all(
createdSecrets.map((secret, index) => {
return EEAuditLogService.createAuditLog(
@@ -178,7 +177,7 @@ export const batchSecrets = async (req: Request, res: Response) => {
);
await AuditLog.insertMany(auditLogs);
const addAction = (await EELogService.createAction({
name: ACTION_ADD_SECRETS,
userId: req.user?._id,
@@ -234,6 +233,9 @@ export const batchSecrets = async (req: Request, res: Response) => {
$inc: {
version: 1
},
$unset: {
"metadata.source": true as const
},
...u,
_id: new Types.ObjectId(u._id)
}
@@ -277,7 +279,7 @@ export const batchSecrets = async (req: Request, res: Response) => {
$in: updateSecrets.map((u) => new Types.ObjectId(u._id))
}
});
const auditLogs = await Promise.all(
updateSecrets.map((secret) => {
return EEAuditLogService.createAuditLog(
@@ -329,26 +331,26 @@ export const batchSecrets = async (req: Request, res: Response) => {
// handle delete secrets
if (deleteSecrets.length > 0) {
const deleteSecretIds: Types.ObjectId[] = deleteSecrets.map((s) => s._id);
const deletedSecretsObj = (await Secret.find({
_id: {
$in: deleteSecretIds
}
}))
.reduce(
(obj: any, secret: ISecret) => ({
...obj,
[secret._id.toString()]: secret
}),
{}
);
.reduce(
(obj: any, secret: ISecret) => ({
...obj,
[secret._id.toString()]: secret
}),
{}
);
await Secret.deleteMany({
_id: {
$in: deleteSecretIds
}
});
await EESecretService.markDeletedSecretVersions({
secretIds: deleteSecretIds
});
@@ -949,7 +951,7 @@ export const getSecrets = async (req: Request, res: Response) => {
channel,
ipAddress: req.realIP
}));
await EEAuditLogService.createAuditLog(
req.authData,
{
@@ -966,21 +968,36 @@ export const getSecrets = async (req: Request, res: Response) => {
);
const postHogClient = await TelemetryService.getPostHogClient();
// reduce the number of events captured
let shouldRecordK8Event = false
if (req.authData.userAgent == K8_USER_AGENT_NAME) {
const randomNumber = Math.random();
if (randomNumber > 0.9) {
shouldRecordK8Event = true
}
}
if (postHogClient) {
postHogClient.capture({
event: "secrets pulled",
distinctId: await TelemetryService.getDistinctId({
authData: req.authData
}),
properties: {
numberOfSecrets: secrets.length,
environment,
workspaceId,
channel,
folderId,
userAgent: req.headers?.["user-agent"]
}
});
const shouldCapture = req.authData.userAgent !== K8_USER_AGENT_NAME || shouldRecordK8Event;
const approximateForNoneCapturedEvents = secrets.length * 10
if (shouldCapture) {
postHogClient.capture({
event: "secrets pulled",
distinctId: await TelemetryService.getDistinctId({
authData: req.authData
}),
properties: {
numberOfSecrets: shouldRecordK8Event ? approximateForNoneCapturedEvents : secrets.length,
environment,
workspaceId,
folderId,
channel: req.authData.userAgentType,
userAgent: req.authData.userAgent
}
});
}
}
return res.status(200).send({
@@ -1087,10 +1104,10 @@ export const updateSecrets = async (req: Request, res: Response) => {
tags,
...(secretCommentCiphertext !== undefined && secretCommentIV && secretCommentTag
? {
secretCommentCiphertext,
secretCommentIV,
secretCommentTag
}
secretCommentCiphertext,
secretCommentIV,
secretCommentTag
}
: {})
}
}
+3 -3
View File
@@ -1,15 +1,15 @@
import { Request, Response } from "express";
import { Types } from "mongoose";
import { Membership, Secret } from "../../models";
import Tag from "../../models/tag";
import { Membership, Secret, Tag } from "../../models";
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
export const createWorkspaceTag = async (req: Request, res: Response) => {
const { workspaceId } = req.params;
const { name, slug } = req.body;
const { name, slug, tagColor } = req.body;
const tagToCreate = {
name,
tagColor,
workspace: new Types.ObjectId(workspaceId),
slug,
user: new Types.ObjectId(req.user._id),
@@ -6,10 +6,9 @@ import { BotService } from "../../services";
import { containsGlobPatterns, repackageSecretToRaw } from "../../helpers/secrets";
import { encryptSymmetric128BitHexKeyUTF8 } from "../../utils/crypto";
import { getAllImportedSecrets } from "../../services/SecretImportService";
import Folder from "../../models/folder";
import { Folder, IServiceTokenData } from "../../models";
import { getFolderByPath } from "../../services/FolderService";
import { BadRequestError } from "../../utils/errors";
import { IServiceTokenData } from "../../models";
import { requireWorkspaceAuth } from "../../middleware";
import { ADMIN, MEMBER, PERMISSION_READ_SECRETS } from "../../variables";
@@ -23,6 +22,7 @@ export const getSecretsRaw = async (req: Request, res: Response) => {
let workspaceId = req.query.workspaceId as string;
let environment = req.query.environment as string;
let secretPath = req.query.secretPath as string;
const folderId = req.query.folderId as string | undefined;
const includeImports = req.query.include_imports as string;
// if the service token has single scope, it will get all secrets for that scope by default
@@ -47,6 +47,7 @@ export const getSecretsRaw = async (req: Request, res: Response) => {
const secrets = await SecretService.getSecrets({
workspaceId: new Types.ObjectId(workspaceId),
environment,
folderId,
secretPath,
authData: req.authData
});
@@ -284,11 +285,13 @@ export const getSecrets = async (req: Request, res: Response) => {
const workspaceId = req.query.workspaceId as string;
const environment = req.query.environment as string;
const secretPath = req.query.secretPath as string;
const folderId = req.query.folderId as string | undefined;
const includeImports = req.query.include_imports as string;
const secrets = await SecretService.getSecrets({
workspaceId: new Types.ObjectId(workspaceId),
environment,
folderId,
secretPath,
authData: req.authData
});