Skip to content

Commit

Permalink
Merge pull request #205 from Infisical/activity-logs
Browse files Browse the repository at this point in the history
Add endpoints for rollbacks and secret versions
  • Loading branch information
vmatsiiako authored Jan 9, 2023
2 parents bd5dad7 + 47ab0b4 commit 6ce12c7
Show file tree
Hide file tree
Showing 11 changed files with 431 additions and 65 deletions.
1 change: 0 additions & 1 deletion backend/src/controllers/v2/apiKeyDataController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ export const createAPIKeyData = async (req: Request, res: Response) => {
apiKey = `ak.${apiKeyData._id.toString()}.${secret}`;

} catch (err) {
console.error(err);
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
Expand Down
101 changes: 101 additions & 0 deletions backend/src/ee/controllers/v1/secretController.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Request, Response } from 'express';
import * as Sentry from '@sentry/node';
import { Secret } from '../../../models';
import { SecretVersion } from '../../models';
import { EESecretService } from '../../services';

/**
* Return secret versions for secret with id [secretId]
Expand Down Expand Up @@ -33,4 +35,103 @@ import { SecretVersion } from '../../models';
return res.status(200).send({
secretVersions
});
}

/**
* Roll back secret with id [secretId] to version [version]
* @param req
* @param res
* @returns
*/
export const rollbackSecretVersion = async (req: Request, res: Response) => {
let secret;
try {
const { secretId } = req.params;
const { version } = req.body;

// validate secret version
const oldSecretVersion = await SecretVersion.findOne({
secret: secretId,
version
});

if (!oldSecretVersion) throw new Error('Failed to find secret version');

const {
workspace,
type,
user,
environment,
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretKeyHash,
secretValueCiphertext,
secretValueIV,
secretValueTag,
secretValueHash
} = oldSecretVersion;

// update secret
secret = await Secret.findByIdAndUpdate(
secretId,
{
$inc: {
version: 1
},
workspace,
type,
user,
environment,
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretKeyHash,
secretValueCiphertext,
secretValueIV,
secretValueTag,
secretValueHash
},
{
new: true
}
);

if (!secret) throw new Error('Failed to find and update secret');

// add new secret version
await new SecretVersion({
secret: secretId,
version: secret.version,
workspace,
type,
user,
environment,
isDeleted: false,
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretKeyHash,
secretValueCiphertext,
secretValueIV,
secretValueTag,
secretValueHash
}).save();

// take secret snapshot
await EESecretService.takeSecretSnapshot({
workspaceId: secret.workspace.toString()
});

} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to roll back secret version'
});
}

return res.status(200).send({
secret
});
}
6 changes: 6 additions & 0 deletions backend/src/ee/controllers/v1/secretSnapshotController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ import { Request, Response } from 'express';
import * as Sentry from '@sentry/node';
import { SecretSnapshot } from '../../models';

/**
* Return secret snapshot with id [secretSnapshotId]
* @param req
* @param res
* @returns
*/
export const getSecretSnapshot = async (req: Request, res: Response) => {
let secretSnapshot;
try {
Expand Down
165 changes: 163 additions & 2 deletions backend/src/ee/controllers/v1/workspaceController.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import e, { Request, Response } from 'express';
import { Request, Response } from 'express';
import * as Sentry from '@sentry/node';
import { Types } from 'mongoose';
import {
Secret
} from '../../../models';
import {
SecretSnapshot,
Log
Log,
SecretVersion,
ISecretVersion
} from '../../models';
import { EESecretService } from '../../services';
import { getLatestSecretVersionIds } from '../../helpers/secretVersion';

/**
* Return secret snapshots for workspace with id [workspaceId]
Expand Down Expand Up @@ -63,6 +71,159 @@ export const getWorkspaceSecretSnapshotsCount = async (req: Request, res: Respon
});
}

/**
* Rollback secret snapshot with id [secretSnapshotId] to version [version]
* @param req
* @param res
* @returns
*/
export const rollbackWorkspaceSecretSnapshot = async (req: Request, res: Response) => {
let secrets;
try {
const { workspaceId } = req.params;
const { version } = req.body;

// validate secret snapshot
const secretSnapshot = await SecretSnapshot.findOne({
workspace: workspaceId,
version
}).populate<{ secretVersions: ISecretVersion[]}>('secretVersions');

if (!secretSnapshot) throw new Error('Failed to find secret snapshot');

// TODO: fix any
const oldSecretVersionsObj: any = secretSnapshot.secretVersions
.reduce((accumulator, s) => ({
...accumulator,
[`${s.secret.toString()}`]: s
}), {});

const latestSecretVersionIds = await getLatestSecretVersionIds({
secretIds: secretSnapshot.secretVersions.map((sv) => sv.secret)
});

// TODO: fix any
const latestSecretVersions: any = (await SecretVersion.find({
_id: {
$in: latestSecretVersionIds.map((s) => s.versionId)
}
}, 'secret version'))
.reduce((accumulator, s) => ({
...accumulator,
[`${s.secret.toString()}`]: s
}), {});

// delete existing secrets
await Secret.deleteMany({
workspace: workspaceId
});

// add secrets
secrets = await Secret.insertMany(
secretSnapshot.secretVersions.map((sv) => {
const secretId = sv.secret;
const {
workspace,
type,
user,
environment,
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretKeyHash,
secretValueCiphertext,
secretValueIV,
secretValueTag,
secretValueHash,
createdAt
} = oldSecretVersionsObj[secretId.toString()];

return ({
_id: secretId,
version: latestSecretVersions[secretId.toString()].version + 1,
workspace,
type,
user,
environment,
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretKeyHash,
secretValueCiphertext,
secretValueIV,
secretValueTag,
secretValueHash,
secretCommentCiphertext: '',
secretCommentIV: '',
secretCommentTag: '',
createdAt
});
})
);

// add secret versions
await SecretVersion.insertMany(
secrets.map(({
_id,
version,
workspace,
type,
user,
environment,
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretKeyHash,
secretValueCiphertext,
secretValueIV,
secretValueTag,
secretValueHash
}) => ({
_id: new Types.ObjectId(),
secret: _id,
version,
workspace,
type,
user,
environment,
isDeleted: false,
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretKeyHash,
secretValueCiphertext,
secretValueIV,
secretValueTag,
secretValueHash
}))
);

// update secret versions of restored secrets as not deleted
await SecretVersion.updateMany({
secret: {
$in: secretSnapshot.secretVersions.map((sv) => sv.secret)
}
}, {
isDeleted: false
});

// take secret snapshot
await EESecretService.takeSecretSnapshot({
workspaceId
});
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to roll back secret snapshot'
});
}

return res.status(200).send({
secrets
});
}

/**
* Return (audit) logs for workspace with id [workspaceId]
* @param req
Expand Down
Loading

0 comments on commit 6ce12c7

Please sign in to comment.