feat(secret-ref): implemented backend changes for multi env and folder in service token

This commit is contained in:
akhilmhdh
2023-07-05 23:00:17 +05:30
parent 088d8097a9
commit 0cee453202
10 changed files with 856 additions and 710 deletions
+223 -32
View File
@@ -71,6 +71,7 @@
"@types/node": "^18.11.3", "@types/node": "^18.11.3",
"@types/nodemailer": "^6.4.6", "@types/nodemailer": "^6.4.6",
"@types/passport": "^1.0.12", "@types/passport": "^1.0.12",
"@types/picomatch": "^2.3.0",
"@types/supertest": "^2.0.12", "@types/supertest": "^2.0.12",
"@types/swagger-jsdoc": "^6.0.1", "@types/swagger-jsdoc": "^6.0.1",
"@types/swagger-ui-express": "^4.1.3", "@types/swagger-ui-express": "^4.1.3",
@@ -2200,6 +2201,26 @@
} }
} }
}, },
"node_modules/@jest/reporters/node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"dev": true,
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@jest/schemas": { "node_modules/@jest/schemas": {
"version": "29.4.3", "version": "29.4.3",
"resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.3.tgz", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.3.tgz",
@@ -3241,6 +3262,12 @@
"@types/express": "*" "@types/express": "*"
} }
}, },
"node_modules/@types/picomatch": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@types/picomatch/-/picomatch-2.3.0.tgz",
"integrity": "sha512-O397rnSS9iQI4OirieAtsDqvCj4+3eY1J+EPdNTKuHuRWIfUoGyzX294o8C4KJYaLqgSrd2o60c5EqCU8Zv02g==",
"dev": true
},
"node_modules/@types/prettier": { "node_modules/@types/prettier": {
"version": "2.7.2", "version": "2.7.2",
"resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz", "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz",
@@ -5680,25 +5707,6 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/glob-parent": { "node_modules/glob-parent": {
"version": "6.0.2", "version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
@@ -6481,6 +6489,26 @@
} }
} }
}, },
"node_modules/jest-config/node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"dev": true,
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/jest-diff": { "node_modules/jest-diff": {
"version": "29.5.0", "version": "29.5.0",
"resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.5.0.tgz", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.5.0.tgz",
@@ -6776,6 +6804,26 @@
"node": "^14.15.0 || ^16.10.0 || >=18.0.0" "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
} }
}, },
"node_modules/jest-runtime/node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"dev": true,
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/jest-snapshot": { "node_modules/jest-snapshot": {
"version": "29.5.0", "version": "29.5.0",
"resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.5.0.tgz", "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.5.0.tgz",
@@ -11071,6 +11119,25 @@
"url": "https://github.com/sponsors/isaacs" "url": "https://github.com/sponsors/isaacs"
} }
}, },
"node_modules/rimraf/node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/ripemd160": { "node_modules/ripemd160": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz",
@@ -11669,6 +11736,25 @@
"node": ">=0.4.0" "node": ">=0.4.0"
} }
}, },
"node_modules/swagger-autogen/node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/swagger-ui-dist": { "node_modules/swagger-ui-dist": {
"version": "4.19.0", "version": "4.19.0",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-4.19.0.tgz", "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-4.19.0.tgz",
@@ -11723,6 +11809,26 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/test-exclude/node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"dev": true,
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/text-hex": { "node_modules/text-hex": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz",
@@ -14154,6 +14260,22 @@
"string-length": "^4.0.1", "string-length": "^4.0.1",
"strip-ansi": "^6.0.0", "strip-ansi": "^6.0.0",
"v8-to-istanbul": "^9.0.1" "v8-to-istanbul": "^9.0.1"
},
"dependencies": {
"glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
}
} }
}, },
"@jest/schemas": { "@jest/schemas": {
@@ -14988,6 +15110,12 @@
"@types/express": "*" "@types/express": "*"
} }
}, },
"@types/picomatch": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@types/picomatch/-/picomatch-2.3.0.tgz",
"integrity": "sha512-O397rnSS9iQI4OirieAtsDqvCj4+3eY1J+EPdNTKuHuRWIfUoGyzX294o8C4KJYaLqgSrd2o60c5EqCU8Zv02g==",
"dev": true
},
"@types/prettier": { "@types/prettier": {
"version": "2.7.2", "version": "2.7.2",
"resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz", "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz",
@@ -16808,19 +16936,6 @@
"integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
"dev": true "dev": true
}, },
"glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
},
"glob-parent": { "glob-parent": {
"version": "6.0.2", "version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
@@ -17371,6 +17486,22 @@
"pretty-format": "^29.5.0", "pretty-format": "^29.5.0",
"slash": "^3.0.0", "slash": "^3.0.0",
"strip-json-comments": "^3.1.1" "strip-json-comments": "^3.1.1"
},
"dependencies": {
"glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
}
} }
}, },
"jest-diff": { "jest-diff": {
@@ -17606,6 +17737,22 @@
"jest-util": "^29.5.0", "jest-util": "^29.5.0",
"slash": "^3.0.0", "slash": "^3.0.0",
"strip-bom": "^4.0.0" "strip-bom": "^4.0.0"
},
"dependencies": {
"glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
}
} }
}, },
"jest-snapshot": { "jest-snapshot": {
@@ -20674,6 +20821,21 @@
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"requires": { "requires": {
"glob": "^7.1.3" "glob": "^7.1.3"
},
"dependencies": {
"glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
}
} }
}, },
"ripemd160": { "ripemd160": {
@@ -21129,6 +21291,19 @@
"version": "7.4.1", "version": "7.4.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
"integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==" "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A=="
},
"glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
} }
} }
}, },
@@ -21174,6 +21349,22 @@
"@istanbuljs/schema": "^0.1.2", "@istanbuljs/schema": "^0.1.2",
"glob": "^7.1.4", "glob": "^7.1.4",
"minimatch": "^3.0.4" "minimatch": "^3.0.4"
},
"dependencies": {
"glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
}
} }
}, },
"text-hex": { "text-hex": {
+1
View File
@@ -89,6 +89,7 @@
"@types/node": "^18.11.3", "@types/node": "^18.11.3",
"@types/nodemailer": "^6.4.6", "@types/nodemailer": "^6.4.6",
"@types/passport": "^1.0.12", "@types/passport": "^1.0.12",
"@types/picomatch": "^2.3.0",
"@types/supertest": "^2.0.12", "@types/supertest": "^2.0.12",
"@types/swagger-jsdoc": "^6.0.1", "@types/swagger-jsdoc": "^6.0.1",
"@types/swagger-ui-express": "^4.1.3", "@types/swagger-ui-express": "^4.1.3",
+178 -196
View File
@@ -1,5 +1,6 @@
import { Types } from "mongoose"; import { Types } from "mongoose";
import { Request, Response } from "express"; import { Request, Response } from "express";
import picomatch from "picomatch";
import { ISecret, Secret, ServiceTokenData } from "../../models"; import { ISecret, Secret, ServiceTokenData } from "../../models";
import { IAction, SecretVersion } from "../../ee/models"; import { IAction, SecretVersion } from "../../ee/models";
import { import {
@@ -9,7 +10,7 @@ import {
ACTION_UPDATE_SECRETS, ACTION_UPDATE_SECRETS,
ALGORITHM_AES_256_GCM, ALGORITHM_AES_256_GCM,
ENCODING_SCHEME_UTF8, ENCODING_SCHEME_UTF8,
SECRET_PERSONAL, SECRET_PERSONAL
} from "../../variables"; } from "../../variables";
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors"; import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
import { EventService } from "../../services"; import { EventService } from "../../services";
@@ -21,7 +22,7 @@ import { PERMISSION_WRITE_SECRETS } from "../../variables";
import { import {
userHasNoAbility, userHasNoAbility,
userHasWorkspaceAccess, userHasWorkspaceAccess,
userHasWriteOnlyAbility, userHasWriteOnlyAbility
} from "../../ee/helpers/checkMembershipPermissions"; } from "../../ee/helpers/checkMembershipPermissions";
import Tag from "../../models/tag"; import Tag from "../../models/tag";
import _ from "lodash"; import _ from "lodash";
@@ -30,7 +31,7 @@ import Folder from "../../models/folder";
import { import {
getFolderByPath, getFolderByPath,
getFolderIdFromServiceToken, getFolderIdFromServiceToken,
searchByFolderId, searchByFolderId
} from "../../services/FolderService"; } from "../../services/FolderService";
/** /**
@@ -47,7 +48,7 @@ export const batchSecrets = async (req: Request, res: Response) => {
workspaceId, workspaceId,
environment, environment,
requests, requests,
secretPath, secretPath
}: { }: {
workspaceId: string; workspaceId: string;
environment: string; environment: string;
@@ -63,7 +64,7 @@ export const batchSecrets = async (req: Request, res: Response) => {
// get secret blind index salt // get secret blind index salt
const salt = await SecretService.getSecretBlindIndexSalt({ const salt = await SecretService.getSecretBlindIndexSalt({
workspaceId: new Types.ObjectId(workspaceId), workspaceId: new Types.ObjectId(workspaceId)
}); });
const folders = await Folder.findOne({ workspace: workspaceId, environment }); const folders = await Folder.findOne({ workspace: workspaceId, environment });
@@ -73,22 +74,22 @@ export const batchSecrets = async (req: Request, res: Response) => {
} }
if (req.authData.authPayload instanceof ServiceTokenData) { if (req.authData.authPayload instanceof ServiceTokenData) {
const { secretPath: serviceTkScopedSecretPath } = req.authData.authPayload; const { scopes: tkScopes } = req.authData.authPayload;
const validScope = tkScopes.find(
(scope) =>
picomatch.isMatch(secretPath, scope.secretPath, { strictSlashes: false }) &&
scope.environment === environment
);
// in service token when not giving secretpath folderid must be root // in service token when not giving secretpath folderid must be root
// this is to avoid giving folderid when service tokens are used // this is to avoid giving folderid when service tokens are used
if ( if ((!secretPath && folderId !== "root") || (secretPath && !validScope)) {
(!secretPath && folderId !== "root") ||
(secretPath && secretPath !== serviceTkScopedSecretPath)
) {
throw UnauthorizedRequestError({ message: "Folder Permission Denied" }); throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
} }
} }
if (secretPath) { if (secretPath) {
folderId = await getFolderIdFromServiceToken( folderId = await getFolderIdFromServiceToken(workspaceId, environment, secretPath);
workspaceId,
environment,
secretPath
);
} }
for await (const request of requests) { for await (const request of requests) {
@@ -97,12 +98,10 @@ export const batchSecrets = async (req: Request, res: Response) => {
let secretBlindIndex = ""; let secretBlindIndex = "";
switch (request.method) { switch (request.method) {
case "POST": case "POST":
secretBlindIndex = await SecretService.generateSecretBlindIndexWithSalt( secretBlindIndex = await SecretService.generateSecretBlindIndexWithSalt({
{ secretName: request.secret.secretName,
secretName: request.secret.secretName, salt
salt, });
}
);
createSecrets.push({ createSecrets.push({
...request.secret, ...request.secret,
@@ -113,16 +112,14 @@ export const batchSecrets = async (req: Request, res: Response) => {
folder: folderId, folder: folderId,
secretBlindIndex, secretBlindIndex,
algorithm: ALGORITHM_AES_256_GCM, algorithm: ALGORITHM_AES_256_GCM,
keyEncoding: ENCODING_SCHEME_UTF8, keyEncoding: ENCODING_SCHEME_UTF8
}); });
break; break;
case "PATCH": case "PATCH":
secretBlindIndex = await SecretService.generateSecretBlindIndexWithSalt( secretBlindIndex = await SecretService.generateSecretBlindIndexWithSalt({
{ secretName: request.secret.secretName,
secretName: request.secret.secretName, salt
salt, });
}
);
updateSecrets.push({ updateSecrets.push({
...request.secret, ...request.secret,
@@ -130,7 +127,7 @@ export const batchSecrets = async (req: Request, res: Response) => {
secretBlindIndex, secretBlindIndex,
folder: folderId, folder: folderId,
algorithm: ALGORITHM_AES_256_GCM, algorithm: ALGORITHM_AES_256_GCM,
keyEncoding: ENCODING_SCHEME_UTF8, keyEncoding: ENCODING_SCHEME_UTF8
}); });
break; break;
case "DELETE": case "DELETE":
@@ -150,9 +147,9 @@ export const batchSecrets = async (req: Request, res: Response) => {
...n._doc, ...n._doc,
_id: new Types.ObjectId(), _id: new Types.ObjectId(),
secret: n._id, secret: n._id,
isDeleted: false, isDeleted: false
}; };
}), })
}); });
const addAction = (await EELogService.createAction({ const addAction = (await EELogService.createAction({
@@ -161,7 +158,7 @@ export const batchSecrets = async (req: Request, res: Response) => {
serviceAccountId: req.serviceAccount?._id, serviceAccountId: req.serviceAccount?._id,
serviceTokenDataId: req.serviceTokenData?._id, serviceTokenDataId: req.serviceTokenData?._id,
workspaceId: new Types.ObjectId(workspaceId), workspaceId: new Types.ObjectId(workspaceId),
secretIds: createdSecrets.map((n) => n._id), secretIds: createdSecrets.map((n) => n._id)
})) as IAction; })) as IAction;
actions.push(addAction); actions.push(addAction);
@@ -175,8 +172,8 @@ export const batchSecrets = async (req: Request, res: Response) => {
workspaceId, workspaceId,
folderId, folderId,
channel, channel,
userAgent: req.headers?.["user-agent"], userAgent: req.headers?.["user-agent"]
}, }
}); });
} }
} }
@@ -195,7 +192,7 @@ export const batchSecrets = async (req: Request, res: Response) => {
listedSecretsObj = req.secrets.reduce( listedSecretsObj = req.secrets.reduce(
(obj: any, secret: ISecret) => ({ (obj: any, secret: ISecret) => ({
...obj, ...obj,
[secret._id.toString()]: secret, [secret._id.toString()]: secret
}), }),
{} {}
); );
@@ -204,16 +201,16 @@ export const batchSecrets = async (req: Request, res: Response) => {
updateOne: { updateOne: {
filter: { filter: {
_id: new Types.ObjectId(u._id), _id: new Types.ObjectId(u._id),
workspace: new Types.ObjectId(workspaceId), workspace: new Types.ObjectId(workspaceId)
}, },
update: { update: {
$inc: { $inc: {
version: 1, version: 1
}, },
...u, ...u,
_id: new Types.ObjectId(u._id), _id: new Types.ObjectId(u._id)
}, }
}, }
})); }));
await Secret.bulkWrite(updateOperations); await Secret.bulkWrite(updateOperations);
@@ -240,25 +237,25 @@ export const batchSecrets = async (req: Request, res: Response) => {
algorithm: ALGORITHM_AES_256_GCM, algorithm: ALGORITHM_AES_256_GCM,
keyEncoding: ENCODING_SCHEME_UTF8, keyEncoding: ENCODING_SCHEME_UTF8,
tags: u.tags, tags: u.tags,
folder: u.folder, folder: u.folder
}) })
); );
await EESecretService.addSecretVersions({ await EESecretService.addSecretVersions({
secretVersions, secretVersions
}); });
updatedSecrets = await Secret.find({ updatedSecrets = await Secret.find({
_id: { _id: {
$in: updateSecrets.map((u) => new Types.ObjectId(u._id)), $in: updateSecrets.map((u) => new Types.ObjectId(u._id))
}, }
}); });
const updateAction = (await EELogService.createAction({ const updateAction = (await EELogService.createAction({
name: ACTION_UPDATE_SECRETS, name: ACTION_UPDATE_SECRETS,
userId: req.user._id, userId: req.user._id,
workspaceId: new Types.ObjectId(workspaceId), workspaceId: new Types.ObjectId(workspaceId),
secretIds: updatedSecrets.map((u) => u._id), secretIds: updatedSecrets.map((u) => u._id)
})) as IAction; })) as IAction;
actions.push(updateAction); actions.push(updateAction);
@@ -272,8 +269,8 @@ export const batchSecrets = async (req: Request, res: Response) => {
workspaceId, workspaceId,
folderId, folderId,
channel, channel,
userAgent: req.headers?.["user-agent"], userAgent: req.headers?.["user-agent"]
}, }
}); });
} }
} }
@@ -282,19 +279,19 @@ export const batchSecrets = async (req: Request, res: Response) => {
if (deleteSecrets.length > 0) { if (deleteSecrets.length > 0) {
await Secret.deleteMany({ await Secret.deleteMany({
_id: { _id: {
$in: deleteSecrets, $in: deleteSecrets
}, }
}); });
await EESecretService.markDeletedSecretVersions({ await EESecretService.markDeletedSecretVersions({
secretIds: deleteSecrets, secretIds: deleteSecrets
}); });
const deleteAction = (await EELogService.createAction({ const deleteAction = (await EELogService.createAction({
name: ACTION_DELETE_SECRETS, name: ACTION_DELETE_SECRETS,
userId: req.user._id, userId: req.user._id,
workspaceId: new Types.ObjectId(workspaceId), workspaceId: new Types.ObjectId(workspaceId),
secretIds: deleteSecrets, secretIds: deleteSecrets
})) as IAction; })) as IAction;
actions.push(deleteAction); actions.push(deleteAction);
@@ -307,8 +304,8 @@ export const batchSecrets = async (req: Request, res: Response) => {
environment, environment,
workspaceId, workspaceId,
channel: channel, channel: channel,
userAgent: req.headers?.["user-agent"], userAgent: req.headers?.["user-agent"]
}, }
}); });
} }
} }
@@ -320,22 +317,22 @@ export const batchSecrets = async (req: Request, res: Response) => {
workspaceId: new Types.ObjectId(workspaceId), workspaceId: new Types.ObjectId(workspaceId),
actions, actions,
channel, channel,
ipAddress: req.realIP, ipAddress: req.realIP
}); });
} }
// // trigger event - push secrets // // trigger event - push secrets
await EventService.handleEvent({ await EventService.handleEvent({
event: eventPushSecrets({ event: eventPushSecrets({
workspaceId: new Types.ObjectId(workspaceId), workspaceId: new Types.ObjectId(workspaceId)
}), })
}); });
// (EE) take a secret snapshot // (EE) take a secret snapshot
await EESecretService.takeSecretSnapshot({ await EESecretService.takeSecretSnapshot({
workspaceId: new Types.ObjectId(workspaceId), workspaceId: new Types.ObjectId(workspaceId),
environment, environment,
folderId, folderId
}); });
const resObj: { [key: string]: ISecret[] | string[] } = {}; const resObj: { [key: string]: ISecret[] | string[] } = {};
@@ -418,7 +415,7 @@ export const createSecrets = async (req: Request, res: Response) => {
const { const {
workspaceId, workspaceId,
environment, environment,
secretPath, secretPath
}: { }: {
workspaceId: string; workspaceId: string;
environment: string; environment: string;
@@ -435,8 +432,7 @@ export const createSecrets = async (req: Request, res: Response) => {
); );
if (!hasAccess) { if (!hasAccess) {
throw UnauthorizedRequestError({ throw UnauthorizedRequestError({
message: message: "You do not have the necessary permission(s) perform this action"
"You do not have the necessary permission(s) perform this action",
}); });
} }
} }
@@ -449,28 +445,28 @@ export const createSecrets = async (req: Request, res: Response) => {
// case: create 1 secret // case: create 1 secret
listOfSecretsToCreate = [req.body.secrets]; listOfSecretsToCreate = [req.body.secrets];
} }
if (req.authData.authPayload instanceof ServiceTokenData) { if (req.authData.authPayload instanceof ServiceTokenData) {
const { secretPath: serviceTkScopedSecretPath } = req.authData.authPayload; const { scopes: tkScopes } = req.authData.authPayload;
const validScope = tkScopes.find(
(scope) =>
picomatch.isMatch(secretPath || "/", scope.secretPath, { strictSlashes: false }) &&
scope.environment === environment
);
// in service token when not giving secretpath folderid must be root // in service token when not giving secretpath folderid must be root
// this is to avoid giving folderid when service tokens are used // this is to avoid giving folderid when service tokens are used
if ( if ((!secretPath && folderId !== "root") || (secretPath && !validScope)) {
(!secretPath && folderId !== "root") ||
(secretPath && secretPath !== serviceTkScopedSecretPath)
) {
throw UnauthorizedRequestError({ message: "Folder Permission Denied" }); throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
} }
} }
if (secretPath) { if (secretPath) {
folderId = await getFolderIdFromServiceToken( folderId = await getFolderIdFromServiceToken(workspaceId, environment, secretPath);
workspaceId,
environment,
secretPath
);
} }
// get secret blind index salt // get secret blind index salt
const salt = await SecretService.getSecretBlindIndexSalt({ const salt = await SecretService.getSecretBlindIndexSalt({
workspaceId: new Types.ObjectId(workspaceId), workspaceId: new Types.ObjectId(workspaceId)
}); });
type secretsToCreateType = { type secretsToCreateType = {
@@ -502,15 +498,14 @@ export const createSecrets = async (req: Request, res: Response) => {
secretCommentCiphertext, secretCommentCiphertext,
secretCommentIV, secretCommentIV,
secretCommentTag, secretCommentTag,
tags, tags
}: secretsToCreateType) => { }: secretsToCreateType) => {
let secretBlindIndex; let secretBlindIndex;
if (secretName) { if (secretName) {
secretBlindIndex = secretBlindIndex = await SecretService.generateSecretBlindIndexWithSalt({
await SecretService.generateSecretBlindIndexWithSalt({ secretName,
secretName, salt
salt, });
});
} }
return { return {
@@ -532,22 +527,22 @@ export const createSecrets = async (req: Request, res: Response) => {
secretCommentTag, secretCommentTag,
algorithm: ALGORITHM_AES_256_GCM, algorithm: ALGORITHM_AES_256_GCM,
keyEncoding: ENCODING_SCHEME_UTF8, keyEncoding: ENCODING_SCHEME_UTF8,
tags, tags
}; };
} }
) )
); );
const newlyCreatedSecrets: ISecret[] = ( const newlyCreatedSecrets: ISecret[] = (await Secret.insertMany(secretsToInsert)).map(
await Secret.insertMany(secretsToInsert) (insertedSecret) => insertedSecret.toObject()
).map((insertedSecret) => insertedSecret.toObject()); );
setTimeout(async () => { setTimeout(async () => {
// trigger event - push secrets // trigger event - push secrets
await EventService.handleEvent({ await EventService.handleEvent({
event: eventPushSecrets({ event: eventPushSecrets({
workspaceId: new Types.ObjectId(workspaceId), workspaceId: new Types.ObjectId(workspaceId)
}), })
}); });
}, 5000); }, 5000);
@@ -567,7 +562,7 @@ export const createSecrets = async (req: Request, res: Response) => {
secretKeyTag, secretKeyTag,
secretValueCiphertext, secretValueCiphertext,
secretValueIV, secretValueIV,
secretValueTag, secretValueTag
}) => }) =>
new SecretVersion({ new SecretVersion({
secret: _id, secret: _id,
@@ -586,9 +581,9 @@ export const createSecrets = async (req: Request, res: Response) => {
secretValueTag, secretValueTag,
folder: folderId, folder: folderId,
algorithm: ALGORITHM_AES_256_GCM, algorithm: ALGORITHM_AES_256_GCM,
keyEncoding: ENCODING_SCHEME_UTF8, keyEncoding: ENCODING_SCHEME_UTF8
}) })
), )
}); });
const addAction = await EELogService.createAction({ const addAction = await EELogService.createAction({
@@ -597,7 +592,7 @@ export const createSecrets = async (req: Request, res: Response) => {
serviceAccountId: req.serviceAccount?._id, serviceAccountId: req.serviceAccount?._id,
serviceTokenDataId: req.serviceTokenData?._id, serviceTokenDataId: req.serviceTokenData?._id,
workspaceId: new Types.ObjectId(workspaceId), workspaceId: new Types.ObjectId(workspaceId),
secretIds: newlyCreatedSecrets.map((n) => n._id), secretIds: newlyCreatedSecrets.map((n) => n._id)
}); });
// (EE) create (audit) log // (EE) create (audit) log
@@ -609,14 +604,14 @@ export const createSecrets = async (req: Request, res: Response) => {
workspaceId: new Types.ObjectId(workspaceId), workspaceId: new Types.ObjectId(workspaceId),
actions: [addAction], actions: [addAction],
channel, channel,
ipAddress: req.realIP, ipAddress: req.realIP
})); }));
// (EE) take a secret snapshot // (EE) take a secret snapshot
await EESecretService.takeSecretSnapshot({ await EESecretService.takeSecretSnapshot({
workspaceId: new Types.ObjectId(workspaceId), workspaceId: new Types.ObjectId(workspaceId),
environment, environment,
folderId, folderId
}); });
const postHogClient = await TelemetryService.getPostHogClient(); const postHogClient = await TelemetryService.getPostHogClient();
@@ -624,7 +619,7 @@ export const createSecrets = async (req: Request, res: Response) => {
postHogClient.capture({ postHogClient.capture({
event: "secrets added", event: "secrets added",
distinctId: await TelemetryService.getDistinctId({ distinctId: await TelemetryService.getDistinctId({
authData: req.authData, authData: req.authData
}), }),
properties: { properties: {
numberOfSecrets: listOfSecretsToCreate.length, numberOfSecrets: listOfSecretsToCreate.length,
@@ -632,13 +627,13 @@ export const createSecrets = async (req: Request, res: Response) => {
workspaceId, workspaceId,
channel: channel, channel: channel,
folderId, folderId,
userAgent: req.headers?.["user-agent"], userAgent: req.headers?.["user-agent"]
}, }
}); });
} }
return res.status(200).send({ return res.status(200).send({
secrets: newlyCreatedSecrets, secrets: newlyCreatedSecrets
}); });
}; };
@@ -696,10 +691,7 @@ export const getSecrets = async (req: Request, res: Response) => {
const environment = req.query.environment as string; const environment = req.query.environment as string;
const folders = await Folder.findOne({ workspace: workspaceId, environment }); const folders = await Folder.findOne({ workspace: workspaceId, environment });
if ( if ((!folders && folderId && folderId !== "root") || (!folders && secretPath)) {
(!folders && folderId && folderId !== "root") ||
(!folders && secretPath)
) {
res.send({ secrets: [] }); res.send({ secrets: [] });
return; return;
} }
@@ -712,13 +704,17 @@ export const getSecrets = async (req: Request, res: Response) => {
} }
if (req.authData.authPayload instanceof ServiceTokenData) { if (req.authData.authPayload instanceof ServiceTokenData) {
const { secretPath: serviceTkScopedSecretPath } = req.authData.authPayload; const { scopes: tkScopes } = req.authData.authPayload;
const validScope = tkScopes.find(
(scope) =>
picomatch.isMatch((secretPath as string) || "/", scope.secretPath, {
strictSlashes: false
}) && scope.environment === environment
);
// in service token when not giving secretpath folderid must be root // in service token when not giving secretpath folderid must be root
// this is to avoid giving folderid when service tokens are used // this is to avoid giving folderid when service tokens are used
if ( if ((!secretPath && folderId !== "root") || (secretPath && !validScope)) {
(!secretPath && folderId !== "root") ||
(secretPath && secretPath !== serviceTkScopedSecretPath)
) {
throw UnauthorizedRequestError({ message: "Folder Permission Denied" }); throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
} }
} }
@@ -738,8 +734,7 @@ export const getSecrets = async (req: Request, res: Response) => {
// query tags table to get all tags ids for the tag names for the given workspace // query tags table to get all tags ids for the tag names for the given workspace
let tagIds = []; let tagIds = [];
const tagNamesList = const tagNamesList = typeof tagSlugs === "string" && tagSlugs !== "" ? tagSlugs.split(",") : [];
typeof tagSlugs === "string" && tagSlugs !== "" ? tagSlugs.split(",") : [];
if (tagNamesList != undefined && tagNamesList.length != 0) { if (tagNamesList != undefined && tagNamesList.length != 0) {
const workspaceFromDB = await Tag.find({ workspace: workspaceId }); const workspaceFromDB = await Tag.find({ workspace: workspaceId });
tagIds = _.map(tagNamesList, (tagName: string) => { tagIds = _.map(tagNamesList, (tagName: string) => {
@@ -762,8 +757,7 @@ export const getSecrets = async (req: Request, res: Response) => {
); );
if (hasNoAccess) { if (hasNoAccess) {
throw UnauthorizedRequestError({ throw UnauthorizedRequestError({
message: message: "You do not have the necessary permission(s) perform this action"
"You do not have the necessary permission(s) perform this action",
}); });
} }
@@ -773,8 +767,8 @@ export const getSecrets = async (req: Request, res: Response) => {
folder: folderId, folder: folderId,
$or: [ $or: [
{ user: req.user._id }, // personal secrets for this user { user: req.user._id }, // personal secrets for this user
{ user: { $exists: false } }, // shared secrets from workspace { user: { $exists: false } } // shared secrets from workspace
], ]
}; };
if (tagIds.length > 0) { if (tagIds.length > 0) {
@@ -801,8 +795,8 @@ export const getSecrets = async (req: Request, res: Response) => {
environment, environment,
$or: [ $or: [
{ user: userId }, // personal secrets for this user { user: userId }, // personal secrets for this user
{ user: { $exists: false } }, // shared secrets from workspace { user: { $exists: false } } // shared secrets from workspace
], ]
}; };
if (tagIds.length > 0) { if (tagIds.length > 0) {
@@ -820,7 +814,7 @@ export const getSecrets = async (req: Request, res: Response) => {
workspace: workspaceId, workspace: workspaceId,
environment, environment,
folder: folderId, folder: folderId,
user: { $exists: false }, // shared secrets only from workspace user: { $exists: false } // shared secrets only from workspace
}; };
if (tagIds.length > 0) { if (tagIds.length > 0) {
@@ -838,7 +832,7 @@ export const getSecrets = async (req: Request, res: Response) => {
serviceAccountId: req.serviceAccount?._id, serviceAccountId: req.serviceAccount?._id,
serviceTokenDataId: req.serviceTokenData?._id, serviceTokenDataId: req.serviceTokenData?._id,
workspaceId: new Types.ObjectId(workspaceId as string), workspaceId: new Types.ObjectId(workspaceId as string),
secretIds: secrets.map((n: any) => n._id), secretIds: secrets.map((n: any) => n._id)
}); });
readAction && readAction &&
@@ -849,7 +843,7 @@ export const getSecrets = async (req: Request, res: Response) => {
workspaceId: new Types.ObjectId(workspaceId as string), workspaceId: new Types.ObjectId(workspaceId as string),
actions: [readAction], actions: [readAction],
channel, channel,
ipAddress: req.realIP, ipAddress: req.realIP
})); }));
const postHogClient = await TelemetryService.getPostHogClient(); const postHogClient = await TelemetryService.getPostHogClient();
@@ -857,7 +851,7 @@ export const getSecrets = async (req: Request, res: Response) => {
postHogClient.capture({ postHogClient.capture({
event: "secrets pulled", event: "secrets pulled",
distinctId: await TelemetryService.getDistinctId({ distinctId: await TelemetryService.getDistinctId({
authData: req.authData, authData: req.authData
}), }),
properties: { properties: {
numberOfSecrets: secrets.length, numberOfSecrets: secrets.length,
@@ -865,13 +859,13 @@ export const getSecrets = async (req: Request, res: Response) => {
workspaceId, workspaceId,
channel, channel,
folderId, folderId,
userAgent: req.headers?.["user-agent"], userAgent: req.headers?.["user-agent"]
}, }
}); });
} }
return res.status(200).send({ return res.status(200).send({
secrets, secrets
}); });
}; };
@@ -925,9 +919,7 @@ export const updateSecrets = async (req: Request, res: Response) => {
} }
} }
*/ */
const channel = req.headers?.["user-agent"]?.toLowerCase().includes("mozilla") const channel = req.headers?.["user-agent"]?.toLowerCase().includes("mozilla") ? "web" : "cli";
? "web"
: "cli";
interface PatchSecret { interface PatchSecret {
id: string; id: string;
@@ -943,51 +935,47 @@ export const updateSecrets = async (req: Request, res: Response) => {
tags: string[]; tags: string[];
} }
const updateOperationsToPerform = req.body.secrets.map( const updateOperationsToPerform = req.body.secrets.map((secret: PatchSecret) => {
(secret: PatchSecret) => { const {
const { secretKeyCiphertext,
secretKeyCiphertext, secretKeyIV,
secretKeyIV, secretKeyTag,
secretKeyTag, secretValueCiphertext,
secretValueCiphertext, secretValueIV,
secretValueIV, secretValueTag,
secretValueTag, secretCommentCiphertext,
secretCommentCiphertext, secretCommentIV,
secretCommentIV, secretCommentTag,
secretCommentTag, tags
tags, } = secret;
} = secret;
return { return {
updateOne: { updateOne: {
filter: { _id: new Types.ObjectId(secret.id) }, filter: { _id: new Types.ObjectId(secret.id) },
update: { update: {
$inc: { $inc: {
version: 1, version: 1
},
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretValueCiphertext,
secretValueIV,
secretValueTag,
algorithm: ALGORITHM_AES_256_GCM,
keyEncoding: ENCODING_SCHEME_UTF8,
tags,
...(secretCommentCiphertext !== undefined &&
secretCommentIV &&
secretCommentTag
? {
secretCommentCiphertext,
secretCommentIV,
secretCommentTag,
}
: {}),
}, },
}, secretKeyCiphertext,
}; secretKeyIV,
} secretKeyTag,
); secretValueCiphertext,
secretValueIV,
secretValueTag,
algorithm: ALGORITHM_AES_256_GCM,
keyEncoding: ENCODING_SCHEME_UTF8,
tags,
...(secretCommentCiphertext !== undefined && secretCommentIV && secretCommentTag
? {
secretCommentCiphertext,
secretCommentIV,
secretCommentTag
}
: {})
}
}
};
});
await Secret.bulkWrite(updateOperationsToPerform); await Secret.bulkWrite(updateOperationsToPerform);
@@ -1009,7 +997,7 @@ export const updateSecrets = async (req: Request, res: Response) => {
secretCommentCiphertext, secretCommentCiphertext,
secretCommentIV, secretCommentIV,
secretCommentTag, secretCommentTag,
tags, tags
} = secretModificationsBySecretId[secret._id.toString()]; } = secretModificationsBySecretId[secret._id.toString()];
return { return {
@@ -1018,9 +1006,7 @@ export const updateSecrets = async (req: Request, res: Response) => {
workspace: secret.workspace, workspace: secret.workspace,
type: secret.type, type: secret.type,
environment: secret.environment, environment: secret.environment,
secretKeyCiphertext: secretKeyCiphertext secretKeyCiphertext: secretKeyCiphertext ? secretKeyCiphertext : secret.secretKeyCiphertext,
? secretKeyCiphertext
: secret.secretKeyCiphertext,
secretKeyIV: secretKeyIV ? secretKeyIV : secret.secretKeyIV, secretKeyIV: secretKeyIV ? secretKeyIV : secret.secretKeyIV,
secretKeyTag: secretKeyTag ? secretKeyTag : secret.secretKeyTag, secretKeyTag: secretKeyTag ? secretKeyTag : secret.secretKeyTag,
secretValueCiphertext: secretValueCiphertext secretValueCiphertext: secretValueCiphertext
@@ -1031,17 +1017,13 @@ export const updateSecrets = async (req: Request, res: Response) => {
secretCommentCiphertext: secretCommentCiphertext secretCommentCiphertext: secretCommentCiphertext
? secretCommentCiphertext ? secretCommentCiphertext
: secret.secretCommentCiphertext, : secret.secretCommentCiphertext,
secretCommentIV: secretCommentIV secretCommentIV: secretCommentIV ? secretCommentIV : secret.secretCommentIV,
? secretCommentIV secretCommentTag: secretCommentTag ? secretCommentTag : secret.secretCommentTag,
: secret.secretCommentIV,
secretCommentTag: secretCommentTag
? secretCommentTag
: secret.secretCommentTag,
tags: tags ? tags : secret.tags, tags: tags ? tags : secret.tags,
algorithm: ALGORITHM_AES_256_GCM, algorithm: ALGORITHM_AES_256_GCM,
keyEncoding: ENCODING_SCHEME_UTF8, keyEncoding: ENCODING_SCHEME_UTF8
}; };
}), })
}; };
await EESecretService.addSecretVersions(secretVersions); await EESecretService.addSecretVersions(secretVersions);
@@ -1062,8 +1044,8 @@ export const updateSecrets = async (req: Request, res: Response) => {
setTimeout(async () => { setTimeout(async () => {
await EventService.handleEvent({ await EventService.handleEvent({
event: eventPushSecrets({ event: eventPushSecrets({
workspaceId: new Types.ObjectId(key), workspaceId: new Types.ObjectId(key)
}), })
}); });
}, 10000); }, 10000);
@@ -1073,7 +1055,7 @@ export const updateSecrets = async (req: Request, res: Response) => {
serviceAccountId: req.serviceAccount?._id, serviceAccountId: req.serviceAccount?._id,
serviceTokenDataId: req.serviceTokenData?._id, serviceTokenDataId: req.serviceTokenData?._id,
workspaceId: new Types.ObjectId(key), workspaceId: new Types.ObjectId(key),
secretIds: workspaceSecretObj[key].map((secret: ISecret) => secret._id), secretIds: workspaceSecretObj[key].map((secret: ISecret) => secret._id)
}); });
// (EE) create (audit) log // (EE) create (audit) log
@@ -1085,7 +1067,7 @@ export const updateSecrets = async (req: Request, res: Response) => {
workspaceId: new Types.ObjectId(key), workspaceId: new Types.ObjectId(key),
actions: [updateAction], actions: [updateAction],
channel, channel,
ipAddress: req.realIP, ipAddress: req.realIP
})); }));
// (EE) take a secret snapshot // (EE) take a secret snapshot
@@ -1101,15 +1083,15 @@ export const updateSecrets = async (req: Request, res: Response) => {
postHogClient.capture({ postHogClient.capture({
event: "secrets modified", event: "secrets modified",
distinctId: await TelemetryService.getDistinctId({ distinctId: await TelemetryService.getDistinctId({
authData: req.authData, authData: req.authData
}), }),
properties: { properties: {
numberOfSecrets: workspaceSecretObj[key].length, numberOfSecrets: workspaceSecretObj[key].length,
environment: workspaceSecretObj[key][0].environment, environment: workspaceSecretObj[key][0].environment,
workspaceId: key, workspaceId: key,
channel: channel, channel: channel,
userAgent: req.headers?.["user-agent"], userAgent: req.headers?.["user-agent"]
}, }
}); });
} }
}); });
@@ -1117,9 +1099,9 @@ export const updateSecrets = async (req: Request, res: Response) => {
return res.status(200).send({ return res.status(200).send({
secrets: await Secret.find({ secrets: await Secret.find({
_id: { _id: {
$in: req.secrets.map((secret: ISecret) => secret._id), $in: req.secrets.map((secret: ISecret) => secret._id)
}, }
}), })
}); });
}; };
@@ -1179,12 +1161,12 @@ export const deleteSecrets = async (req: Request, res: Response) => {
await Secret.deleteMany({ await Secret.deleteMany({
_id: { _id: {
$in: toDelete, $in: toDelete
}, }
}); });
await EESecretService.markDeletedSecretVersions({ await EESecretService.markDeletedSecretVersions({
secretIds: toDelete, secretIds: toDelete
}); });
// group secrets into workspaces so deleted secrets can // group secrets into workspaces so deleted secrets can
@@ -1202,8 +1184,8 @@ export const deleteSecrets = async (req: Request, res: Response) => {
// trigger event - push secrets // trigger event - push secrets
await EventService.handleEvent({ await EventService.handleEvent({
event: eventPushSecrets({ event: eventPushSecrets({
workspaceId: new Types.ObjectId(key), workspaceId: new Types.ObjectId(key)
}), })
}); });
const deleteAction = await EELogService.createAction({ const deleteAction = await EELogService.createAction({
name: ACTION_DELETE_SECRETS, name: ACTION_DELETE_SECRETS,
@@ -1211,7 +1193,7 @@ export const deleteSecrets = async (req: Request, res: Response) => {
serviceAccountId: req.serviceAccount?._id, serviceAccountId: req.serviceAccount?._id,
serviceTokenDataId: req.serviceTokenData?._id, serviceTokenDataId: req.serviceTokenData?._id,
workspaceId: new Types.ObjectId(key), workspaceId: new Types.ObjectId(key),
secretIds: workspaceSecretObj[key].map((secret: ISecret) => secret._id), secretIds: workspaceSecretObj[key].map((secret: ISecret) => secret._id)
}); });
// (EE) create (audit) log // (EE) create (audit) log
@@ -1223,7 +1205,7 @@ export const deleteSecrets = async (req: Request, res: Response) => {
workspaceId: new Types.ObjectId(key), workspaceId: new Types.ObjectId(key),
actions: [deleteAction], actions: [deleteAction],
channel, channel,
ipAddress: req.realIP, ipAddress: req.realIP
})); }));
// (EE) take a secret snapshot // (EE) take a secret snapshot
@@ -1237,20 +1219,20 @@ export const deleteSecrets = async (req: Request, res: Response) => {
postHogClient.capture({ postHogClient.capture({
event: "secrets deleted", event: "secrets deleted",
distinctId: await TelemetryService.getDistinctId({ distinctId: await TelemetryService.getDistinctId({
authData: req.authData, authData: req.authData
}), }),
properties: { properties: {
numberOfSecrets: workspaceSecretObj[key].length, numberOfSecrets: workspaceSecretObj[key].length,
environment: workspaceSecretObj[key][0].environment, environment: workspaceSecretObj[key][0].environment,
workspaceId: key, workspaceId: key,
channel: channel, channel: channel,
userAgent: req.headers?.["user-agent"], userAgent: req.headers?.["user-agent"]
}, }
}); });
} }
}); });
return res.status(200).send({ return res.status(200).send({
secrets: req.secrets, secrets: req.secrets
}); });
}; };
@@ -2,10 +2,7 @@ import { Request, Response } from "express";
import crypto from "crypto"; import crypto from "crypto";
import bcrypt from "bcrypt"; import bcrypt from "bcrypt";
import { ServiceAccount, ServiceTokenData, User } from "../../models"; import { ServiceAccount, ServiceTokenData, User } from "../../models";
import { import { AUTH_MODE_JWT, AUTH_MODE_SERVICE_ACCOUNT } from "../../variables";
AUTH_MODE_JWT,
AUTH_MODE_SERVICE_ACCOUNT,
} from "../../variables";
import { getSaltRounds } from "../../config"; import { getSaltRounds } from "../../config";
import { BadRequestError } from "../../utils/errors"; import { BadRequestError } from "../../utils/errors";
import Folder from "../../models/folder"; import Folder from "../../models/folder";
@@ -46,14 +43,13 @@ export const getServiceTokenData = async (req: Request, res: Response) => {
if (!(req.authData.authPayload instanceof ServiceTokenData)) if (!(req.authData.authPayload instanceof ServiceTokenData))
throw BadRequestError({ throw BadRequestError({
message: "Failed accepted client validation for service token data", message: "Failed accepted client validation for service token data"
}); });
const serviceTokenData = await ServiceTokenData.findById( const serviceTokenData = await ServiceTokenData.findById(req.authData.authPayload._id)
req.authData.authPayload._id
)
.select("+encryptedKey +iv +tag") .select("+encryptedKey +iv +tag")
.populate("user").lean(); .populate("user")
.lean();
return res.status(200).json(serviceTokenData); return res.status(200).json(serviceTokenData);
}; };
@@ -68,29 +64,7 @@ export const getServiceTokenData = async (req: Request, res: Response) => {
export const createServiceTokenData = async (req: Request, res: Response) => { export const createServiceTokenData = async (req: Request, res: Response) => {
let serviceTokenData; let serviceTokenData;
const { const { name, workspaceId, encryptedKey, iv, tag, expiresIn, permissions, scopes } = req.body;
name,
workspaceId,
environment,
encryptedKey,
iv,
tag,
expiresIn,
secretPath,
permissions,
} = req.body;
const folders = await Folder.findOne({
workspace: workspaceId,
environment,
});
if (folders) {
const folder = getFolderByPath(folders.nodes, secretPath);
if (folder == undefined) {
throw BadRequestError({ message: "Path for service token does not exist" })
}
}
const secret = crypto.randomBytes(16).toString("hex"); const secret = crypto.randomBytes(16).toString("hex");
const secretHash = await bcrypt.hash(secret, await getSaltRounds()); const secretHash = await bcrypt.hash(secret, await getSaltRounds());
@@ -103,10 +77,7 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
let user, serviceAccount; let user, serviceAccount;
if ( if (req.authData.authMode === AUTH_MODE_JWT && req.authData.authPayload instanceof User) {
req.authData.authMode === AUTH_MODE_JWT &&
req.authData.authPayload instanceof User
) {
user = req.authData.authPayload._id; user = req.authData.authPayload._id;
} }
@@ -120,17 +91,16 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
serviceTokenData = await new ServiceTokenData({ serviceTokenData = await new ServiceTokenData({
name, name,
workspace: workspaceId, workspace: workspaceId,
environment,
user, user,
serviceAccount, serviceAccount,
scopes,
lastUsed: new Date(), lastUsed: new Date(),
expiresAt, expiresAt,
secretHash, secretHash,
encryptedKey, encryptedKey,
iv, iv,
tag, tag,
secretPath, permissions
permissions,
}).save(); }).save();
// return service token data without sensitive data // return service token data without sensitive data
@@ -142,7 +112,7 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
return res.status(200).send({ return res.status(200).send({
serviceToken, serviceToken,
serviceTokenData, serviceTokenData
}); });
}; };
@@ -155,11 +125,9 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
export const deleteServiceTokenData = async (req: Request, res: Response) => { export const deleteServiceTokenData = async (req: Request, res: Response) => {
const { serviceTokenDataId } = req.params; const { serviceTokenDataId } = req.params;
const serviceTokenData = await ServiceTokenData.findByIdAndDelete( const serviceTokenData = await ServiceTokenData.findByIdAndDelete(serviceTokenDataId);
serviceTokenDataId
);
return res.status(200).send({ return res.status(200).send({
serviceTokenData, serviceTokenData
}); });
}; };
+161 -175
View File
@@ -4,21 +4,16 @@ import {
DeleteSecretParams, DeleteSecretParams,
GetSecretParams, GetSecretParams,
GetSecretsParams, GetSecretsParams,
UpdateSecretParams, UpdateSecretParams
} from "../interfaces/services/SecretService"; } from "../interfaces/services/SecretService";
import { import { ISecret, Secret, SecretBlindIndexData, ServiceTokenData } from "../models";
ISecret,
Secret,
SecretBlindIndexData,
ServiceTokenData,
} from "../models";
import { SecretVersion } from "../ee/models"; import { SecretVersion } from "../ee/models";
import { import {
BadRequestError, BadRequestError,
InternalServerError, InternalServerError,
SecretBlindIndexDataNotFoundError, SecretBlindIndexDataNotFoundError,
SecretNotFoundError, SecretNotFoundError,
UnauthorizedRequestError, UnauthorizedRequestError
} from "../utils/errors"; } from "../utils/errors";
import { import {
ACTION_ADD_SECRETS, ACTION_ADD_SECRETS,
@@ -29,51 +24,42 @@ import {
ENCODING_SCHEME_BASE64, ENCODING_SCHEME_BASE64,
ENCODING_SCHEME_UTF8, ENCODING_SCHEME_UTF8,
SECRET_PERSONAL, SECRET_PERSONAL,
SECRET_SHARED, SECRET_SHARED
} from "../variables"; } from "../variables";
import crypto from "crypto"; import crypto from "crypto";
import * as argon2 from "argon2"; import * as argon2 from "argon2";
import { import {
decryptSymmetric128BitHexKeyUTF8, decryptSymmetric128BitHexKeyUTF8,
encryptSymmetric128BitHexKeyUTF8, encryptSymmetric128BitHexKeyUTF8
} from "../utils/crypto"; } from "../utils/crypto";
import { TelemetryService } from "../services"; import { TelemetryService } from "../services";
import { client, getEncryptionKey, getRootEncryptionKey } from "../config"; import { client, getEncryptionKey, getRootEncryptionKey } from "../config";
import { EELogService, EESecretService } from "../ee/services"; import { EELogService, EESecretService } from "../ee/services";
import { import { getAuthDataPayloadIdObj, getAuthDataPayloadUserObj } from "../utils/auth";
getAuthDataPayloadIdObj,
getAuthDataPayloadUserObj,
} from "../utils/auth";
import { getFolderIdFromServiceToken } from "../services/FolderService"; import { getFolderIdFromServiceToken } from "../services/FolderService";
import picomatch from "picomatch";
/** /**
* Returns an object containing secret [secret] but with its value, key, comment decrypted. * Returns an object containing secret [secret] but with its value, key, comment decrypted.
* *
* Precondition: the workspace for secret [secret] must have E2EE disabled * Precondition: the workspace for secret [secret] must have E2EE disabled
* @param {ISecret} secret - secret to repackage to raw * @param {ISecret} secret - secret to repackage to raw
* @param {String} key - symmetric key to use to decrypt secret * @param {String} key - symmetric key to use to decrypt secret
* @returns * @returns
*/ */
export const repackageSecretToRaw = ({ export const repackageSecretToRaw = ({ secret, key }: { secret: ISecret; key: string }) => {
secret,
key,
}: {
secret: ISecret;
key: string;
}) => {
const secretKey = decryptSymmetric128BitHexKeyUTF8({ const secretKey = decryptSymmetric128BitHexKeyUTF8({
ciphertext: secret.secretKeyCiphertext, ciphertext: secret.secretKeyCiphertext,
iv: secret.secretKeyIV, iv: secret.secretKeyIV,
tag: secret.secretKeyTag, tag: secret.secretKeyTag,
key, key
}); });
const secretValue = decryptSymmetric128BitHexKeyUTF8({ const secretValue = decryptSymmetric128BitHexKeyUTF8({
ciphertext: secret.secretValueCiphertext, ciphertext: secret.secretValueCiphertext,
iv: secret.secretValueIV, iv: secret.secretValueIV,
tag: secret.secretValueTag, tag: secret.secretValueTag,
key, key
}); });
let secretComment = ""; let secretComment = "";
@@ -83,11 +69,11 @@ export const repackageSecretToRaw = ({
ciphertext: secret.secretCommentCiphertext, ciphertext: secret.secretCommentCiphertext,
iv: secret.secretCommentIV, iv: secret.secretCommentIV,
tag: secret.secretCommentTag, tag: secret.secretCommentTag,
key, key
}); });
} }
return ({ return {
_id: secret._id, _id: secret._id,
version: secret.version, version: secret.version,
workspace: secret.workspace, workspace: secret.workspace,
@@ -96,9 +82,9 @@ export const repackageSecretToRaw = ({
user: secret.user, user: secret.user,
secretKey, secretKey,
secretValue, secretValue,
secretComment, secretComment
}); };
} };
/** /**
* Create secret blind index data containing encrypted blind index [salt] * Create secret blind index data containing encrypted blind index [salt]
@@ -107,7 +93,7 @@ export const repackageSecretToRaw = ({
* @param {Types.ObjectId} obj.workspaceId * @param {Types.ObjectId} obj.workspaceId
*/ */
export const createSecretBlindIndexDataHelper = async ({ export const createSecretBlindIndexDataHelper = async ({
workspaceId, workspaceId
}: { }: {
workspaceId: Types.ObjectId; workspaceId: Types.ObjectId;
}) => { }) => {
@@ -121,7 +107,7 @@ export const createSecretBlindIndexDataHelper = async ({
const { const {
ciphertext: encryptedSaltCiphertext, ciphertext: encryptedSaltCiphertext,
iv: saltIV, iv: saltIV,
tag: saltTag, tag: saltTag
} = client.encryptSymmetric(salt, rootEncryptionKey); } = client.encryptSymmetric(salt, rootEncryptionKey);
return await new SecretBlindIndexData({ return await new SecretBlindIndexData({
@@ -130,16 +116,16 @@ export const createSecretBlindIndexDataHelper = async ({
saltIV, saltIV,
saltTag, saltTag,
algorithm: ALGORITHM_AES_256_GCM, algorithm: ALGORITHM_AES_256_GCM,
keyEncoding: ENCODING_SCHEME_BASE64, keyEncoding: ENCODING_SCHEME_BASE64
}).save(); }).save();
} else { } else {
const { const {
ciphertext: encryptedSaltCiphertext, ciphertext: encryptedSaltCiphertext,
iv: saltIV, iv: saltIV,
tag: saltTag, tag: saltTag
} = encryptSymmetric128BitHexKeyUTF8({ } = encryptSymmetric128BitHexKeyUTF8({
plaintext: salt, plaintext: salt,
key: encryptionKey, key: encryptionKey
}); });
return await new SecretBlindIndexData({ return await new SecretBlindIndexData({
@@ -148,7 +134,7 @@ export const createSecretBlindIndexDataHelper = async ({
saltIV, saltIV,
saltTag, saltTag,
algorithm: ALGORITHM_AES_256_GCM, algorithm: ALGORITHM_AES_256_GCM,
keyEncoding: ENCODING_SCHEME_UTF8, keyEncoding: ENCODING_SCHEME_UTF8
}).save(); }).save();
} }
}; };
@@ -160,7 +146,7 @@ export const createSecretBlindIndexDataHelper = async ({
* @returns * @returns
*/ */
export const getSecretBlindIndexSaltHelper = async ({ export const getSecretBlindIndexSaltHelper = async ({
workspaceId, workspaceId
}: { }: {
workspaceId: Types.ObjectId; workspaceId: Types.ObjectId;
}) => { }) => {
@@ -168,36 +154,30 @@ export const getSecretBlindIndexSaltHelper = async ({
const rootEncryptionKey = await getRootEncryptionKey(); const rootEncryptionKey = await getRootEncryptionKey();
const secretBlindIndexData = await SecretBlindIndexData.findOne({ const secretBlindIndexData = await SecretBlindIndexData.findOne({
workspace: workspaceId, workspace: workspaceId
}).select("+algorithm +keyEncoding"); }).select("+algorithm +keyEncoding");
if (!secretBlindIndexData) throw SecretBlindIndexDataNotFoundError(); if (!secretBlindIndexData) throw SecretBlindIndexDataNotFoundError();
if ( if (rootEncryptionKey && secretBlindIndexData.keyEncoding === ENCODING_SCHEME_BASE64) {
rootEncryptionKey &&
secretBlindIndexData.keyEncoding === ENCODING_SCHEME_BASE64
) {
return client.decryptSymmetric( return client.decryptSymmetric(
secretBlindIndexData.encryptedSaltCiphertext, secretBlindIndexData.encryptedSaltCiphertext,
rootEncryptionKey, rootEncryptionKey,
secretBlindIndexData.saltIV, secretBlindIndexData.saltIV,
secretBlindIndexData.saltTag secretBlindIndexData.saltTag
); );
} else if ( } else if (encryptionKey && secretBlindIndexData.keyEncoding === ENCODING_SCHEME_UTF8) {
encryptionKey &&
secretBlindIndexData.keyEncoding === ENCODING_SCHEME_UTF8
) {
// decrypt workspace salt // decrypt workspace salt
return decryptSymmetric128BitHexKeyUTF8({ return decryptSymmetric128BitHexKeyUTF8({
ciphertext: secretBlindIndexData.encryptedSaltCiphertext, ciphertext: secretBlindIndexData.encryptedSaltCiphertext,
iv: secretBlindIndexData.saltIV, iv: secretBlindIndexData.saltIV,
tag: secretBlindIndexData.saltTag, tag: secretBlindIndexData.saltTag,
key: encryptionKey, key: encryptionKey
}); });
} }
throw InternalServerError({ throw InternalServerError({
message: "Failed to obtain workspace salt needed for secret blind indexing", message: "Failed to obtain workspace salt needed for secret blind indexing"
}); });
}; };
@@ -210,7 +190,7 @@ export const getSecretBlindIndexSaltHelper = async ({
*/ */
export const generateSecretBlindIndexWithSaltHelper = async ({ export const generateSecretBlindIndexWithSaltHelper = async ({
secretName, secretName,
salt, salt
}: { }: {
secretName: string; secretName: string;
salt: string; salt: string;
@@ -224,7 +204,7 @@ export const generateSecretBlindIndexWithSaltHelper = async ({
memoryCost: 65536, // default pool of 64 MiB per thread. memoryCost: 65536, // default pool of 64 MiB per thread.
hashLength: 32, hashLength: 32,
parallelism: 1, parallelism: 1,
raw: true, raw: true
}) })
).toString("base64"); ).toString("base64");
@@ -240,7 +220,7 @@ export const generateSecretBlindIndexWithSaltHelper = async ({
*/ */
export const generateSecretBlindIndexHelper = async ({ export const generateSecretBlindIndexHelper = async ({
secretName, secretName,
workspaceId, workspaceId
}: { }: {
secretName: string; secretName: string;
workspaceId: Types.ObjectId; workspaceId: Types.ObjectId;
@@ -250,16 +230,13 @@ export const generateSecretBlindIndexHelper = async ({
const rootEncryptionKey = await getRootEncryptionKey(); const rootEncryptionKey = await getRootEncryptionKey();
const secretBlindIndexData = await SecretBlindIndexData.findOne({ const secretBlindIndexData = await SecretBlindIndexData.findOne({
workspace: workspaceId, workspace: workspaceId
}).select("+algorithm +keyEncoding"); }).select("+algorithm +keyEncoding");
if (!secretBlindIndexData) throw SecretBlindIndexDataNotFoundError(); if (!secretBlindIndexData) throw SecretBlindIndexDataNotFoundError();
let salt; let salt;
if ( if (rootEncryptionKey && secretBlindIndexData.keyEncoding === ENCODING_SCHEME_BASE64) {
rootEncryptionKey &&
secretBlindIndexData.keyEncoding === ENCODING_SCHEME_BASE64
) {
salt = client.decryptSymmetric( salt = client.decryptSymmetric(
secretBlindIndexData.encryptedSaltCiphertext, secretBlindIndexData.encryptedSaltCiphertext,
rootEncryptionKey, rootEncryptionKey,
@@ -269,32 +246,29 @@ export const generateSecretBlindIndexHelper = async ({
const secretBlindIndex = await generateSecretBlindIndexWithSaltHelper({ const secretBlindIndex = await generateSecretBlindIndexWithSaltHelper({
secretName, secretName,
salt, salt
}); });
return secretBlindIndex; return secretBlindIndex;
} else if ( } else if (encryptionKey && secretBlindIndexData.keyEncoding === ENCODING_SCHEME_UTF8) {
encryptionKey &&
secretBlindIndexData.keyEncoding === ENCODING_SCHEME_UTF8
) {
// decrypt workspace salt // decrypt workspace salt
salt = decryptSymmetric128BitHexKeyUTF8({ salt = decryptSymmetric128BitHexKeyUTF8({
ciphertext: secretBlindIndexData.encryptedSaltCiphertext, ciphertext: secretBlindIndexData.encryptedSaltCiphertext,
iv: secretBlindIndexData.saltIV, iv: secretBlindIndexData.saltIV,
tag: secretBlindIndexData.saltTag, tag: secretBlindIndexData.saltTag,
key: encryptionKey, key: encryptionKey
}); });
const secretBlindIndex = await generateSecretBlindIndexWithSaltHelper({ const secretBlindIndex = await generateSecretBlindIndexWithSaltHelper({
secretName, secretName,
salt, salt
}); });
return secretBlindIndex; return secretBlindIndex;
} }
throw InternalServerError({ throw InternalServerError({
message: "Failed to generate secret blind index", message: "Failed to generate secret blind index"
}); });
}; };
@@ -323,38 +297,39 @@ export const createSecretHelper = async ({
secretCommentCiphertext, secretCommentCiphertext,
secretCommentIV, secretCommentIV,
secretCommentTag, secretCommentTag,
secretPath = "/", secretPath = "/"
}: CreateSecretParams) => { }: CreateSecretParams) => {
const secretBlindIndex = await generateSecretBlindIndexHelper({ const secretBlindIndex = await generateSecretBlindIndexHelper({
secretName, secretName,
workspaceId: new Types.ObjectId(workspaceId), workspaceId: new Types.ObjectId(workspaceId)
}); });
// if using service token filter towards the folderId by secretpath // if using service token filter towards the folderId by secretpath
if (authData.authPayload instanceof ServiceTokenData) { if (authData.authPayload instanceof ServiceTokenData) {
const { secretPath: serviceTkScopedSecretPath } = authData.authPayload; const { scopes: tkScopes } = authData.authPayload;
if (secretPath !== serviceTkScopedSecretPath) { const validScope = tkScopes.find(
(scope) =>
picomatch.isMatch(secretPath, scope.secretPath, { strictSlashes: false }) &&
scope.environment === environment
);
if (!validScope) {
throw UnauthorizedRequestError({ message: "Folder Permission Denied" }); throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
} }
} }
const folderId = await getFolderIdFromServiceToken( const folderId = await getFolderIdFromServiceToken(workspaceId, environment, secretPath);
workspaceId,
environment,
secretPath
);
const exists = await Secret.exists({ const exists = await Secret.exists({
secretBlindIndex, secretBlindIndex,
workspace: new Types.ObjectId(workspaceId), workspace: new Types.ObjectId(workspaceId),
folder: folderId, folder: folderId,
type, type,
...(type === SECRET_PERSONAL ? getAuthDataPayloadUserObj(authData) : {}), ...(type === SECRET_PERSONAL ? getAuthDataPayloadUserObj(authData) : {})
}); });
if (exists) if (exists)
throw BadRequestError({ throw BadRequestError({
message: "Failed to create secret that already exists", message: "Failed to create secret that already exists"
}); });
if (type === SECRET_PERSONAL) { if (type === SECRET_PERSONAL) {
@@ -365,13 +340,12 @@ export const createSecretHelper = async ({
secretBlindIndex, secretBlindIndex,
folder: folderId, folder: folderId,
workspace: new Types.ObjectId(workspaceId), workspace: new Types.ObjectId(workspaceId),
type: SECRET_SHARED, type: SECRET_SHARED
}); });
if (!exists) if (!exists)
throw BadRequestError({ throw BadRequestError({
message: message: "Failed to create personal secret override for no corresponding shared secret"
"Failed to create personal secret override for no corresponding shared secret",
}); });
} }
@@ -394,7 +368,7 @@ 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
}).save(); }).save();
const secretVersion = new SecretVersion({ const secretVersion = new SecretVersion({
@@ -414,12 +388,12 @@ export const createSecretHelper = async ({
secretValueIV, secretValueIV,
secretValueTag, secretValueTag,
algorithm: ALGORITHM_AES_256_GCM, algorithm: ALGORITHM_AES_256_GCM,
keyEncoding: ENCODING_SCHEME_UTF8, keyEncoding: ENCODING_SCHEME_UTF8
}); });
// (EE) add version for new secret // (EE) add version for new secret
await EESecretService.addSecretVersions({ await EESecretService.addSecretVersions({
secretVersions: [secretVersion], secretVersions: [secretVersion]
}); });
// (EE) create (audit) log // (EE) create (audit) log
@@ -427,7 +401,7 @@ export const createSecretHelper = async ({
name: ACTION_ADD_SECRETS, name: ACTION_ADD_SECRETS,
...getAuthDataPayloadIdObj(authData), ...getAuthDataPayloadIdObj(authData),
workspaceId, workspaceId,
secretIds: [secret._id], secretIds: [secret._id]
}); });
action && action &&
@@ -436,14 +410,14 @@ export const createSecretHelper = async ({
workspaceId, workspaceId,
actions: [action], actions: [action],
channel: authData.authChannel, channel: authData.authChannel,
ipAddress: authData.authIP, ipAddress: authData.authIP
})); }));
// (EE) take a secret snapshot // (EE) take a secret snapshot
await EESecretService.takeSecretSnapshot({ await EESecretService.takeSecretSnapshot({
workspaceId, workspaceId,
environment, environment,
folderId, folderId
}); });
const postHogClient = await TelemetryService.getPostHogClient(); const postHogClient = await TelemetryService.getPostHogClient();
@@ -452,7 +426,7 @@ export const createSecretHelper = async ({
postHogClient.capture({ postHogClient.capture({
event: "secrets added", event: "secrets added",
distinctId: await TelemetryService.getDistinctId({ distinctId: await TelemetryService.getDistinctId({
authData, authData
}), }),
properties: { properties: {
numberOfSecrets: 1, numberOfSecrets: 1,
@@ -460,8 +434,8 @@ export const createSecretHelper = async ({
workspaceId, workspaceId,
folderId, folderId,
channel: authData.authChannel, channel: authData.authChannel,
userAgent: authData.authUserAgent, userAgent: authData.authUserAgent
}, }
}); });
} }
@@ -480,21 +454,23 @@ export const getSecretsHelper = async ({
workspaceId, workspaceId,
environment, environment,
authData, authData,
secretPath = "/", secretPath = "/"
}: GetSecretsParams) => { }: GetSecretsParams) => {
let secrets: ISecret[] = []; let secrets: ISecret[] = [];
// if using service token filter towards the folderId by secretpath // if using service token filter towards the folderId by secretpath
if (authData.authPayload instanceof ServiceTokenData) { if (authData.authPayload instanceof ServiceTokenData) {
const { secretPath: serviceTkScopedSecretPath } = authData.authPayload; const { scopes: tkScopes } = authData.authPayload;
if (secretPath !== serviceTkScopedSecretPath) { const validScope = tkScopes.find(
(scope) =>
picomatch.isMatch(secretPath, scope.secretPath, { strictSlashes: false }) &&
scope.environment === environment
);
if (!validScope) {
throw UnauthorizedRequestError({ message: "Folder Permission Denied" }); throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
} }
} }
const folderId = await getFolderIdFromServiceToken( const folderId = await getFolderIdFromServiceToken(workspaceId, environment, secretPath);
workspaceId,
environment,
secretPath
);
// get personal secrets first // get personal secrets first
secrets = await Secret.find({ secrets = await Secret.find({
@@ -502,8 +478,10 @@ export const getSecretsHelper = async ({
environment, environment,
folder: folderId, folder: folderId,
type: SECRET_PERSONAL, type: SECRET_PERSONAL,
...getAuthDataPayloadUserObj(authData), ...getAuthDataPayloadUserObj(authData)
}).populate("tags").lean(); })
.populate("tags")
.lean();
// concat with shared secrets // concat with shared secrets
secrets = secrets.concat( secrets = secrets.concat(
@@ -513,9 +491,11 @@ export const getSecretsHelper = async ({
folder: folderId, folder: folderId,
type: SECRET_SHARED, type: SECRET_SHARED,
secretBlindIndex: { secretBlindIndex: {
$nin: secrets.map((secret) => secret.secretBlindIndex), $nin: secrets.map((secret) => secret.secretBlindIndex)
}, }
}).populate("tags").lean() })
.populate("tags")
.lean()
); );
// (EE) create (audit) log // (EE) create (audit) log
@@ -523,7 +503,7 @@ export const getSecretsHelper = async ({
name: ACTION_READ_SECRETS, name: ACTION_READ_SECRETS,
...getAuthDataPayloadIdObj(authData), ...getAuthDataPayloadIdObj(authData),
workspaceId, workspaceId,
secretIds: secrets.map((secret) => secret._id), secretIds: secrets.map((secret) => secret._id)
}); });
action && action &&
@@ -532,7 +512,7 @@ export const getSecretsHelper = async ({
workspaceId, workspaceId,
actions: [action], actions: [action],
channel: authData.authChannel, channel: authData.authChannel,
ipAddress: authData.authIP, ipAddress: authData.authIP
})); }));
const postHogClient = await TelemetryService.getPostHogClient(); const postHogClient = await TelemetryService.getPostHogClient();
@@ -541,7 +521,7 @@ export const getSecretsHelper = async ({
postHogClient.capture({ postHogClient.capture({
event: "secrets pulled", event: "secrets pulled",
distinctId: await TelemetryService.getDistinctId({ distinctId: await TelemetryService.getDistinctId({
authData, authData
}), }),
properties: { properties: {
numberOfSecrets: secrets.length, numberOfSecrets: secrets.length,
@@ -549,8 +529,8 @@ export const getSecretsHelper = async ({
workspaceId, workspaceId,
folderId, folderId,
channel: authData.authChannel, channel: authData.authChannel,
userAgent: authData.authUserAgent, userAgent: authData.authUserAgent
}, }
}); });
} }
@@ -573,25 +553,27 @@ export const getSecretHelper = async ({
environment, environment,
type, type,
authData, authData,
secretPath = "/", secretPath = "/"
}: GetSecretParams) => { }: GetSecretParams) => {
const secretBlindIndex = await generateSecretBlindIndexHelper({ const secretBlindIndex = await generateSecretBlindIndexHelper({
secretName, secretName,
workspaceId: new Types.ObjectId(workspaceId), workspaceId: new Types.ObjectId(workspaceId)
}); });
let secret: ISecret | null = null; let secret: ISecret | null = null;
// if using service token filter towards the folderId by secretpath // if using service token filter towards the folderId by secretpath
if (authData.authPayload instanceof ServiceTokenData) { if (authData.authPayload instanceof ServiceTokenData) {
const { secretPath: serviceTkScopedSecretPath } = authData.authPayload; const { scopes: tkScopes } = authData.authPayload;
if (secretPath !== serviceTkScopedSecretPath) { const validScope = tkScopes.find(
(scope) =>
picomatch.isMatch(secretPath, scope.secretPath, { strictSlashes: false }) &&
scope.environment === environment
);
if (!validScope) {
throw UnauthorizedRequestError({ message: "Folder Permission Denied" }); throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
} }
} }
const folderId = await getFolderIdFromServiceToken( const folderId = await getFolderIdFromServiceToken(workspaceId, environment, secretPath);
workspaceId,
environment,
secretPath
);
// try getting personal secret first (if exists) // try getting personal secret first (if exists)
secret = await Secret.findOne({ secret = await Secret.findOne({
@@ -600,7 +582,7 @@ export const getSecretHelper = async ({
environment, environment,
folder: folderId, folder: folderId,
type: type ?? SECRET_PERSONAL, type: type ?? SECRET_PERSONAL,
...(type === SECRET_PERSONAL ? getAuthDataPayloadUserObj(authData) : {}), ...(type === SECRET_PERSONAL ? getAuthDataPayloadUserObj(authData) : {})
}).lean(); }).lean();
if (!secret) { if (!secret) {
@@ -611,7 +593,7 @@ export const getSecretHelper = async ({
workspace: new Types.ObjectId(workspaceId), workspace: new Types.ObjectId(workspaceId),
environment, environment,
folder: folderId, folder: folderId,
type: SECRET_SHARED, type: SECRET_SHARED
}).lean(); }).lean();
} }
@@ -622,7 +604,7 @@ export const getSecretHelper = async ({
name: ACTION_READ_SECRETS, name: ACTION_READ_SECRETS,
...getAuthDataPayloadIdObj(authData), ...getAuthDataPayloadIdObj(authData),
workspaceId, workspaceId,
secretIds: [secret._id], secretIds: [secret._id]
}); });
action && action &&
@@ -631,7 +613,7 @@ export const getSecretHelper = async ({
workspaceId, workspaceId,
actions: [action], actions: [action],
channel: authData.authChannel, channel: authData.authChannel,
ipAddress: authData.authIP, ipAddress: authData.authIP
})); }));
const postHogClient = await TelemetryService.getPostHogClient(); const postHogClient = await TelemetryService.getPostHogClient();
@@ -640,7 +622,7 @@ export const getSecretHelper = async ({
postHogClient.capture({ postHogClient.capture({
event: "secrets pull", event: "secrets pull",
distinctId: await TelemetryService.getDistinctId({ distinctId: await TelemetryService.getDistinctId({
authData, authData
}), }),
properties: { properties: {
numberOfSecrets: 1, numberOfSecrets: 1,
@@ -648,8 +630,8 @@ export const getSecretHelper = async ({
workspaceId, workspaceId,
folderId, folderId,
channel: authData.authChannel, channel: authData.authChannel,
userAgent: authData.authUserAgent, userAgent: authData.authUserAgent
}, }
}); });
} }
@@ -679,26 +661,28 @@ export const updateSecretHelper = async ({
secretValueCiphertext, secretValueCiphertext,
secretValueIV, secretValueIV,
secretValueTag, secretValueTag,
secretPath, secretPath
}: UpdateSecretParams) => { }: UpdateSecretParams) => {
const secretBlindIndex = await generateSecretBlindIndexHelper({ const secretBlindIndex = await generateSecretBlindIndexHelper({
secretName, secretName,
workspaceId: new Types.ObjectId(workspaceId), workspaceId: new Types.ObjectId(workspaceId)
}); });
let secret: ISecret | null = null; let secret: ISecret | null = null;
// if using service token filter towards the folderId by secretpath // if using service token filter towards the folderId by secretpath
if (authData.authPayload instanceof ServiceTokenData) { if (authData.authPayload instanceof ServiceTokenData) {
const { secretPath: serviceTkScopedSecretPath } = authData.authPayload; const { scopes: tkScopes } = authData.authPayload;
if (secretPath !== serviceTkScopedSecretPath) { const validScope = tkScopes.find(
(scope) =>
picomatch.isMatch(secretPath, scope.secretPath, { strictSlashes: false }) &&
scope.environment === environment
);
if (!validScope) {
throw UnauthorizedRequestError({ message: "Folder Permission Denied" }); throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
} }
} }
const folderId = await getFolderIdFromServiceToken( const folderId = await getFolderIdFromServiceToken(workspaceId, environment, secretPath);
workspaceId,
environment,
secretPath
);
if (type === SECRET_SHARED) { if (type === SECRET_SHARED) {
// case: update shared secret // case: update shared secret
@@ -708,16 +692,16 @@ export const updateSecretHelper = async ({
workspace: new Types.ObjectId(workspaceId), workspace: new Types.ObjectId(workspaceId),
environment, environment,
folder: folderId, folder: folderId,
type, type
}, },
{ {
secretValueCiphertext, secretValueCiphertext,
secretValueIV, secretValueIV,
secretValueTag, secretValueTag,
$inc: { version: 1 }, $inc: { version: 1 }
}, },
{ {
new: true, new: true
} }
); );
} else { } else {
@@ -730,16 +714,16 @@ export const updateSecretHelper = async ({
environment, environment,
type, type,
folder: folderId, folder: folderId,
...getAuthDataPayloadUserObj(authData), ...getAuthDataPayloadUserObj(authData)
}, },
{ {
secretValueCiphertext, secretValueCiphertext,
secretValueIV, secretValueIV,
secretValueTag, secretValueTag,
$inc: { version: 1 }, $inc: { version: 1 }
}, },
{ {
new: true, new: true
} }
); );
} }
@@ -763,12 +747,12 @@ export const updateSecretHelper = async ({
secretValueIV, secretValueIV,
secretValueTag, secretValueTag,
algorithm: ALGORITHM_AES_256_GCM, algorithm: ALGORITHM_AES_256_GCM,
keyEncoding: ENCODING_SCHEME_UTF8, keyEncoding: ENCODING_SCHEME_UTF8
}); });
// (EE) add version for new secret // (EE) add version for new secret
await EESecretService.addSecretVersions({ await EESecretService.addSecretVersions({
secretVersions: [secretVersion], secretVersions: [secretVersion]
}); });
// (EE) create (audit) log // (EE) create (audit) log
@@ -776,7 +760,7 @@ export const updateSecretHelper = async ({
name: ACTION_UPDATE_SECRETS, name: ACTION_UPDATE_SECRETS,
...getAuthDataPayloadIdObj(authData), ...getAuthDataPayloadIdObj(authData),
workspaceId, workspaceId,
secretIds: [secret._id], secretIds: [secret._id]
}); });
action && action &&
@@ -785,14 +769,14 @@ export const updateSecretHelper = async ({
workspaceId, workspaceId,
actions: [action], actions: [action],
channel: authData.authChannel, channel: authData.authChannel,
ipAddress: authData.authIP, ipAddress: authData.authIP
})); }));
// (EE) take a secret snapshot // (EE) take a secret snapshot
await EESecretService.takeSecretSnapshot({ await EESecretService.takeSecretSnapshot({
workspaceId, workspaceId,
environment, environment,
folderId: secret?.folder, folderId: secret?.folder
}); });
const postHogClient = await TelemetryService.getPostHogClient(); const postHogClient = await TelemetryService.getPostHogClient();
@@ -801,7 +785,7 @@ export const updateSecretHelper = async ({
postHogClient.capture({ postHogClient.capture({
event: "secrets modified", event: "secrets modified",
distinctId: await TelemetryService.getDistinctId({ distinctId: await TelemetryService.getDistinctId({
authData, authData
}), }),
properties: { properties: {
numberOfSecrets: 1, numberOfSecrets: 1,
@@ -809,8 +793,8 @@ export const updateSecretHelper = async ({
workspaceId, workspaceId,
folderId, folderId,
channel: authData.authChannel, channel: authData.authChannel,
userAgent: authData.authUserAgent, userAgent: authData.authUserAgent
}, }
}); });
} }
@@ -833,26 +817,27 @@ export const deleteSecretHelper = async ({
environment, environment,
type, type,
authData, authData,
secretPath = "/", secretPath = "/"
}: DeleteSecretParams) => { }: DeleteSecretParams) => {
const secretBlindIndex = await generateSecretBlindIndexHelper({ const secretBlindIndex = await generateSecretBlindIndexHelper({
secretName, secretName,
workspaceId: new Types.ObjectId(workspaceId), workspaceId: new Types.ObjectId(workspaceId)
}); });
// if using service token filter towards the folderId by secretpath // if using service token filter towards the folderId by secretpath
if (authData.authPayload instanceof ServiceTokenData) { if (authData.authPayload instanceof ServiceTokenData) {
const { secretPath: serviceTkScopedSecretPath } = authData.authPayload; const { scopes: tkScopes } = authData.authPayload;
const validScope = tkScopes.find(
(scope) =>
picomatch.isMatch(secretPath, scope.secretPath, { strictSlashes: false }) &&
scope.environment === environment
);
if (secretPath !== serviceTkScopedSecretPath) { if (!validScope) {
throw UnauthorizedRequestError({ message: "Folder Permission Denied" }); throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
} }
} }
const folderId = await getFolderIdFromServiceToken( const folderId = await getFolderIdFromServiceToken(workspaceId, environment, secretPath);
workspaceId,
environment,
secretPath
);
let secrets: ISecret[] = []; let secrets: ISecret[] = [];
let secret: ISecret | null = null; let secret: ISecret | null = null;
@@ -862,7 +847,7 @@ export const deleteSecretHelper = async ({
secretBlindIndex, secretBlindIndex,
workspaceId: new Types.ObjectId(workspaceId), workspaceId: new Types.ObjectId(workspaceId),
environment, environment,
folder: folderId, folder: folderId
}).lean(); }).lean();
secret = await Secret.findOneAndDelete({ secret = await Secret.findOneAndDelete({
@@ -870,14 +855,14 @@ export const deleteSecretHelper = async ({
workspaceId: new Types.ObjectId(workspaceId), workspaceId: new Types.ObjectId(workspaceId),
environment, environment,
type, type,
folder: folderId, folder: folderId
}).lean(); }).lean();
await Secret.deleteMany({ await Secret.deleteMany({
secretBlindIndex, secretBlindIndex,
workspaceId: new Types.ObjectId(workspaceId), workspaceId: new Types.ObjectId(workspaceId),
environment, environment,
folder: folderId, folder: folderId
}); });
} else { } else {
secret = await Secret.findOneAndDelete({ secret = await Secret.findOneAndDelete({
@@ -886,7 +871,7 @@ export const deleteSecretHelper = async ({
workspaceId: new Types.ObjectId(workspaceId), workspaceId: new Types.ObjectId(workspaceId),
environment, environment,
type, type,
...getAuthDataPayloadUserObj(authData), ...getAuthDataPayloadUserObj(authData)
}).lean(); }).lean();
if (secret) { if (secret) {
@@ -897,7 +882,7 @@ export const deleteSecretHelper = async ({
if (!secret) throw SecretNotFoundError(); if (!secret) throw SecretNotFoundError();
await EESecretService.markDeletedSecretVersions({ await EESecretService.markDeletedSecretVersions({
secretIds: secrets.map((secret) => secret._id), secretIds: secrets.map((secret) => secret._id)
}); });
// (EE) create (audit) log // (EE) create (audit) log
@@ -905,22 +890,23 @@ export const deleteSecretHelper = async ({
name: ACTION_DELETE_SECRETS, name: ACTION_DELETE_SECRETS,
...getAuthDataPayloadIdObj(authData), ...getAuthDataPayloadIdObj(authData),
workspaceId, workspaceId,
secretIds: secrets.map((secret) => secret._id), secretIds: secrets.map((secret) => secret._id)
}); });
action && (await EELogService.createLog({ action &&
...getAuthDataPayloadIdObj(authData), (await EELogService.createLog({
workspaceId, ...getAuthDataPayloadIdObj(authData),
actions: [action], workspaceId,
channel: authData.authChannel, actions: [action],
ipAddress: authData.authIP, channel: authData.authChannel,
})); ipAddress: authData.authIP
}));
// (EE) take a secret snapshot // (EE) take a secret snapshot
await EESecretService.takeSecretSnapshot({ await EESecretService.takeSecretSnapshot({
workspaceId, workspaceId,
environment, environment,
folderId: secret?.folder, folderId: secret?.folder
}); });
const postHogClient = await TelemetryService.getPostHogClient(); const postHogClient = await TelemetryService.getPostHogClient();
@@ -929,7 +915,7 @@ export const deleteSecretHelper = async ({
postHogClient.capture({ postHogClient.capture({
event: "secrets deleted", event: "secrets deleted",
distinctId: await TelemetryService.getDistinctId({ distinctId: await TelemetryService.getDistinctId({
authData, authData
}), }),
properties: { properties: {
numberOfSecrets: secrets.length, numberOfSecrets: secrets.length,
@@ -937,13 +923,13 @@ export const deleteSecretHelper = async ({
workspaceId, workspaceId,
folderId, folderId,
channel: authData.authChannel, channel: authData.authChannel,
userAgent: authData.authUserAgent, userAgent: authData.authUserAgent
}, }
}); });
} }
return ({ return {
secrets, secrets,
secret, secret
}); };
}; };
+33 -27
View File
@@ -4,7 +4,10 @@ export interface IServiceTokenData extends Document {
_id: Types.ObjectId; _id: Types.ObjectId;
name: string; name: string;
workspace: Types.ObjectId; workspace: Types.ObjectId;
environment: string; scopes: Array<{
environment: string;
secretPath: string;
}>;
user: Types.ObjectId; user: Types.ObjectId;
serviceAccount: Types.ObjectId; serviceAccount: Types.ObjectId;
lastUsed: Date; lastUsed: Date;
@@ -13,7 +16,6 @@ export interface IServiceTokenData extends Document {
encryptedKey: string; encryptedKey: string;
iv: string; iv: string;
tag: string; tag: string;
secretPath: string;
permissions: string[]; permissions: string[];
} }
@@ -21,68 +23,72 @@ const serviceTokenDataSchema = new Schema<IServiceTokenData>(
{ {
name: { name: {
type: String, type: String,
required: true, required: true
}, },
workspace: { workspace: {
type: Schema.Types.ObjectId, type: Schema.Types.ObjectId,
ref: "Workspace", ref: "Workspace",
required: true, required: true
}, },
environment: { scopes: {
type: String, type: [
required: true, {
environment: {
type: String,
required: true
},
secretPath: {
type: String,
default: "/",
required: true
}
}
],
required: true
}, },
user: { user: {
type: Schema.Types.ObjectId, type: Schema.Types.ObjectId,
ref: "User", ref: "User",
required: true, required: true
}, },
serviceAccount: { serviceAccount: {
type: Schema.Types.ObjectId, type: Schema.Types.ObjectId,
ref: "ServiceAccount", ref: "ServiceAccount"
}, },
lastUsed: { lastUsed: {
type: Date, type: Date
}, },
expiresAt: { expiresAt: {
type: Date, type: Date
}, },
secretHash: { secretHash: {
type: String, type: String,
required: true, required: true,
select: false, select: false
}, },
encryptedKey: { encryptedKey: {
type: String, type: String,
select: false, select: false
}, },
iv: { iv: {
type: String, type: String,
select: false, select: false
}, },
tag: { tag: {
type: String, type: String,
select: false, select: false
}, },
permissions: { permissions: {
type: [String], type: [String],
enum: ["read", "write"], enum: ["read", "write"],
default: ["read"], default: ["read"]
}, }
secretPath: {
type: String,
default: "/",
required: true,
},
}, },
{ {
timestamps: true, timestamps: true
} }
); );
const ServiceTokenData = model<IServiceTokenData>( const ServiceTokenData = model<IServiceTokenData>("ServiceTokenData", serviceTokenDataSchema);
"ServiceTokenData",
serviceTokenDataSchema
);
export default ServiceTokenData; export default ServiceTokenData;
+12 -15
View File
@@ -4,7 +4,7 @@ import {
requireAuth, requireAuth,
requireServiceTokenDataAuth, requireServiceTokenDataAuth,
requireWorkspaceAuth, requireWorkspaceAuth,
validateRequest, validateRequest
} from "../../middleware"; } from "../../middleware";
import { body, param } from "express-validator"; import { body, param } from "express-validator";
import { import {
@@ -13,14 +13,14 @@ import {
AUTH_MODE_SERVICE_ACCOUNT, AUTH_MODE_SERVICE_ACCOUNT,
AUTH_MODE_SERVICE_TOKEN, AUTH_MODE_SERVICE_TOKEN,
MEMBER, MEMBER,
PERMISSION_WRITE_SECRETS, PERMISSION_WRITE_SECRETS
} from "../../variables"; } from "../../variables";
import { serviceTokenDataController } from "../../controllers/v2"; import { serviceTokenDataController } from "../../controllers/v2";
router.get( router.get(
"/", "/",
requireAuth({ requireAuth({
acceptedAuthModes: [AUTH_MODE_SERVICE_TOKEN], acceptedAuthModes: [AUTH_MODE_SERVICE_TOKEN]
}), }),
serviceTokenDataController.getServiceTokenData serviceTokenDataController.getServiceTokenData
); );
@@ -28,33 +28,30 @@ router.get(
router.post( router.post(
"/", "/",
requireAuth({ requireAuth({
acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_SERVICE_ACCOUNT], acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_SERVICE_ACCOUNT]
}), }),
requireWorkspaceAuth({ requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER], acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "body", locationWorkspaceId: "body",
locationEnvironment: "body", locationEnvironment: "body",
requiredPermissions: [PERMISSION_WRITE_SECRETS], requiredPermissions: [PERMISSION_WRITE_SECRETS]
}), }),
body("name").exists().isString().trim(), body("name").exists().isString().trim(),
body("workspaceId").exists().isString().trim(), body("workspaceId").exists().isString().trim(),
body("environment").exists().isString().trim(), body("scopes").exists().isArray(),
body("scopes.*.environment").exists().isString().trim(),
body("scopes.*.secretPath").exists().isString().trim(),
body("encryptedKey").exists().isString().trim(), body("encryptedKey").exists().isString().trim(),
body("iv").exists().isString().trim(), body("iv").exists().isString().trim(),
body("secretPath").isString().default("/").trim(),
body("tag").exists().isString().trim(), body("tag").exists().isString().trim(),
body("expiresIn").exists().isNumeric(), // measured in ms body("expiresIn").exists().isNumeric(), // measured in ms
body("permissions") body("permissions")
.isArray({ min: 1 }) .isArray({ min: 1 })
.custom((value: string[]) => { .custom((value: string[]) => {
const allowedPermissions = ["read", "write"]; const allowedPermissions = ["read", "write"];
const invalidValues = value.filter( const invalidValues = value.filter((v) => !allowedPermissions.includes(v));
(v) => !allowedPermissions.includes(v)
);
if (invalidValues.length > 0) { if (invalidValues.length > 0) {
throw new Error( throw new Error(`permissions contains invalid values: ${invalidValues.join(", ")}`);
`permissions contains invalid values: ${invalidValues.join(", ")}`
);
} }
return true; return true;
@@ -66,10 +63,10 @@ router.post(
router.delete( router.delete(
"/:serviceTokenDataId", "/:serviceTokenDataId",
requireAuth({ requireAuth({
acceptedAuthModes: [AUTH_MODE_JWT], acceptedAuthModes: [AUTH_MODE_JWT]
}), }),
requireServiceTokenDataAuth({ requireServiceTokenDataAuth({
acceptedRoles: [ADMIN, MEMBER], acceptedRoles: [ADMIN, MEMBER]
}), }),
param("serviceTokenDataId").exists().trim(), param("serviceTokenDataId").exists().trim(),
validateRequest, validateRequest,
+105 -91
View File
@@ -13,14 +13,14 @@ import {
Secret, Secret,
SecretBlindIndexData, SecretBlindIndexData,
ServiceTokenData, ServiceTokenData,
Workspace, Workspace
} from "../../models"; } from "../../models";
import { generateKeyPair } from "../../utils/crypto"; import { generateKeyPair } from "../../utils/crypto";
import { client, getEncryptionKey, getRootEncryptionKey } from "../../config"; import { client, getEncryptionKey, getRootEncryptionKey } from "../../config";
import { import {
ALGORITHM_AES_256_GCM, ALGORITHM_AES_256_GCM,
ENCODING_SCHEME_BASE64, ENCODING_SCHEME_BASE64,
ENCODING_SCHEME_UTF8, ENCODING_SCHEME_UTF8
} from "../../variables"; } from "../../variables";
import { InternalServerError } from "../errors"; import { InternalServerError } from "../errors";
@@ -29,10 +29,7 @@ import { InternalServerError } from "../errors";
* corresponding secret versions * corresponding secret versions
*/ */
export const backfillSecretVersions = async () => { export const backfillSecretVersions = async () => {
await Secret.updateMany( await Secret.updateMany({ version: { $exists: false } }, { $set: { version: 1 } });
{ version: { $exists: false } },
{ $set: { version: 1 } }
);
const unversionedSecrets: ISecret[] = await Secret.aggregate([ const unversionedSecrets: ISecret[] = await Secret.aggregate([
{ {
@@ -40,14 +37,14 @@ export const backfillSecretVersions = async () => {
from: "secretversions", from: "secretversions",
localField: "_id", localField: "_id",
foreignField: "secret", foreignField: "secret",
as: "versions", as: "versions"
}, }
}, },
{ {
$match: { $match: {
versions: { $size: 0 }, versions: { $size: 0 }
}, }
}, }
]); ]);
if (unversionedSecrets.length > 0) { if (unversionedSecrets.length > 0) {
@@ -62,9 +59,9 @@ export const backfillSecretVersions = async () => {
workspace: s.workspace, workspace: s.workspace,
environment: s.environment, environment: s.environment,
algorithm: ALGORITHM_AES_256_GCM, algorithm: ALGORITHM_AES_256_GCM,
keyEncoding: ENCODING_SCHEME_UTF8, keyEncoding: ENCODING_SCHEME_UTF8
}) })
), )
}); });
} }
console.log("Migration: Secret version migration v1 complete"); console.log("Migration: Secret version migration v1 complete");
@@ -80,8 +77,8 @@ export const backfillBots = async () => {
const workspaceIdsWithBot = await Bot.distinct("workspace"); const workspaceIdsWithBot = await Bot.distinct("workspace");
const workspaceIdsToAddBot = await Workspace.distinct("_id", { const workspaceIdsToAddBot = await Workspace.distinct("_id", {
_id: { _id: {
$nin: workspaceIdsWithBot, $nin: workspaceIdsWithBot
}, }
}); });
if (workspaceIdsToAddBot.length === 0) return; if (workspaceIdsToAddBot.length === 0) return;
@@ -94,7 +91,7 @@ export const backfillBots = async () => {
const { const {
ciphertext: encryptedPrivateKey, ciphertext: encryptedPrivateKey,
iv, iv,
tag, tag
} = client.encryptSymmetric(privateKey, rootEncryptionKey); } = client.encryptSymmetric(privateKey, rootEncryptionKey);
return new Bot({ return new Bot({
@@ -106,16 +103,16 @@ export const backfillBots = async () => {
iv, iv,
tag, tag,
algorithm: ALGORITHM_AES_256_GCM, algorithm: ALGORITHM_AES_256_GCM,
keyEncoding: ENCODING_SCHEME_BASE64, keyEncoding: ENCODING_SCHEME_BASE64
}); });
} else if (encryptionKey) { } else if (encryptionKey) {
const { const {
ciphertext: encryptedPrivateKey, ciphertext: encryptedPrivateKey,
iv, iv,
tag, tag
} = encryptSymmetric128BitHexKeyUTF8({ } = encryptSymmetric128BitHexKeyUTF8({
plaintext: privateKey, plaintext: privateKey,
key: encryptionKey, key: encryptionKey
}); });
return new Bot({ return new Bot({
@@ -127,13 +124,12 @@ export const backfillBots = async () => {
iv, iv,
tag, tag,
algorithm: ALGORITHM_AES_256_GCM, algorithm: ALGORITHM_AES_256_GCM,
keyEncoding: ENCODING_SCHEME_UTF8, keyEncoding: ENCODING_SCHEME_UTF8
}); });
} }
throw InternalServerError({ throw InternalServerError({
message: message: "Failed to backfill workspace bots due to missing encryption key"
"Failed to backfill workspace bots due to missing encryption key",
}); });
}) })
); );
@@ -149,13 +145,11 @@ export const backfillSecretBlindIndexData = async () => {
const encryptionKey = await getEncryptionKey(); const encryptionKey = await getEncryptionKey();
const rootEncryptionKey = await getRootEncryptionKey(); const rootEncryptionKey = await getRootEncryptionKey();
const workspaceIdsBlindIndexed = await SecretBlindIndexData.distinct( const workspaceIdsBlindIndexed = await SecretBlindIndexData.distinct("workspace");
"workspace"
);
const workspaceIdsToBlindIndex = await Workspace.distinct("_id", { const workspaceIdsToBlindIndex = await Workspace.distinct("_id", {
_id: { _id: {
$nin: workspaceIdsBlindIndexed, $nin: workspaceIdsBlindIndexed
}, }
}); });
if (workspaceIdsToBlindIndex.length === 0) return; if (workspaceIdsToBlindIndex.length === 0) return;
@@ -168,7 +162,7 @@ export const backfillSecretBlindIndexData = async () => {
const { const {
ciphertext: encryptedSaltCiphertext, ciphertext: encryptedSaltCiphertext,
iv: saltIV, iv: saltIV,
tag: saltTag, tag: saltTag
} = client.encryptSymmetric(salt, rootEncryptionKey); } = client.encryptSymmetric(salt, rootEncryptionKey);
return new SecretBlindIndexData({ return new SecretBlindIndexData({
@@ -177,16 +171,16 @@ export const backfillSecretBlindIndexData = async () => {
saltIV, saltIV,
saltTag, saltTag,
algorithm: ALGORITHM_AES_256_GCM, algorithm: ALGORITHM_AES_256_GCM,
keyEncoding: ENCODING_SCHEME_BASE64, keyEncoding: ENCODING_SCHEME_BASE64
}); });
} else if (encryptionKey) { } else if (encryptionKey) {
const { const {
ciphertext: encryptedSaltCiphertext, ciphertext: encryptedSaltCiphertext,
iv: saltIV, iv: saltIV,
tag: saltTag, tag: saltTag
} = encryptSymmetric128BitHexKeyUTF8({ } = encryptSymmetric128BitHexKeyUTF8({
plaintext: salt, plaintext: salt,
key: encryptionKey, key: encryptionKey
}); });
return new SecretBlindIndexData({ return new SecretBlindIndexData({
@@ -195,13 +189,12 @@ export const backfillSecretBlindIndexData = async () => {
saltIV, saltIV,
saltTag, saltTag,
algorithm: ALGORITHM_AES_256_GCM, algorithm: ALGORITHM_AES_256_GCM,
keyEncoding: ENCODING_SCHEME_UTF8, keyEncoding: ENCODING_SCHEME_UTF8
}); });
} }
throw InternalServerError({ throw InternalServerError({
message: message: "Failed to backfill secret blind index data due to missing encryption key"
"Failed to backfill secret blind index data due to missing encryption key",
}); });
}) })
); );
@@ -219,17 +212,17 @@ export const backfillEncryptionMetadata = async () => {
await Secret.updateMany( await Secret.updateMany(
{ {
algorithm: { algorithm: {
$exists: false, $exists: false
}, },
keyEncoding: { keyEncoding: {
$exists: false, $exists: false
}, }
}, },
{ {
$set: { $set: {
algorithm: ALGORITHM_AES_256_GCM, algorithm: ALGORITHM_AES_256_GCM,
keyEncoding: ENCODING_SCHEME_UTF8, keyEncoding: ENCODING_SCHEME_UTF8
}, }
} }
); );
@@ -237,17 +230,17 @@ export const backfillEncryptionMetadata = async () => {
await SecretVersion.updateMany( await SecretVersion.updateMany(
{ {
algorithm: { algorithm: {
$exists: false, $exists: false
}, },
keyEncoding: { keyEncoding: {
$exists: false, $exists: false
}, }
}, },
{ {
$set: { $set: {
algorithm: ALGORITHM_AES_256_GCM, algorithm: ALGORITHM_AES_256_GCM,
keyEncoding: ENCODING_SCHEME_UTF8, keyEncoding: ENCODING_SCHEME_UTF8
}, }
} }
); );
@@ -255,17 +248,17 @@ export const backfillEncryptionMetadata = async () => {
await SecretBlindIndexData.updateMany( await SecretBlindIndexData.updateMany(
{ {
algorithm: { algorithm: {
$exists: false, $exists: false
}, },
keyEncoding: { keyEncoding: {
$exists: false, $exists: false
}, }
}, },
{ {
$set: { $set: {
algorithm: ALGORITHM_AES_256_GCM, algorithm: ALGORITHM_AES_256_GCM,
keyEncoding: ENCODING_SCHEME_UTF8, keyEncoding: ENCODING_SCHEME_UTF8
}, }
} }
); );
@@ -273,17 +266,17 @@ export const backfillEncryptionMetadata = async () => {
await Bot.updateMany( await Bot.updateMany(
{ {
algorithm: { algorithm: {
$exists: false, $exists: false
}, },
keyEncoding: { keyEncoding: {
$exists: false, $exists: false
}, }
}, },
{ {
$set: { $set: {
algorithm: ALGORITHM_AES_256_GCM, algorithm: ALGORITHM_AES_256_GCM,
keyEncoding: ENCODING_SCHEME_UTF8, keyEncoding: ENCODING_SCHEME_UTF8
}, }
} }
); );
@@ -291,17 +284,17 @@ export const backfillEncryptionMetadata = async () => {
await BackupPrivateKey.updateMany( await BackupPrivateKey.updateMany(
{ {
algorithm: { algorithm: {
$exists: false, $exists: false
}, },
keyEncoding: { keyEncoding: {
$exists: false, $exists: false
}, }
}, },
{ {
$set: { $set: {
algorithm: ALGORITHM_AES_256_GCM, algorithm: ALGORITHM_AES_256_GCM,
keyEncoding: ENCODING_SCHEME_UTF8, keyEncoding: ENCODING_SCHEME_UTF8
}, }
} }
); );
@@ -309,17 +302,17 @@ export const backfillEncryptionMetadata = async () => {
await IntegrationAuth.updateMany( await IntegrationAuth.updateMany(
{ {
algorithm: { algorithm: {
$exists: false, $exists: false
}, },
keyEncoding: { keyEncoding: {
$exists: false, $exists: false
}, }
}, },
{ {
$set: { $set: {
algorithm: ALGORITHM_AES_256_GCM, algorithm: ALGORITHM_AES_256_GCM,
keyEncoding: ENCODING_SCHEME_UTF8, keyEncoding: ENCODING_SCHEME_UTF8
}, }
} }
); );
}; };
@@ -328,26 +321,26 @@ export const backfillSecretFolders = async () => {
await Secret.updateMany( await Secret.updateMany(
{ {
folder: { folder: {
$exists: false, $exists: false
}, }
}, },
{ {
$set: { $set: {
folder: "root", folder: "root"
}, }
} }
); );
await SecretVersion.updateMany( await SecretVersion.updateMany(
{ {
folder: { folder: {
$exists: false, $exists: false
}, }
}, },
{ {
$set: { $set: {
folder: "root", folder: "root"
}, }
} }
); );
@@ -355,20 +348,20 @@ export const backfillSecretFolders = async () => {
await SecretVersion.updateMany( await SecretVersion.updateMany(
{ {
tags: { tags: {
$exists: false, $exists: false
}, }
}, },
{ {
$set: { $set: {
tags: [], tags: []
}, }
} }
); );
let secretSnapshots = await SecretSnapshot.find({ let secretSnapshots = await SecretSnapshot.find({
environment: { environment: {
$exists: false, $exists: false
}, }
}) })
.populate<{ secretVersions: ISecretVersion[] }>("secretVersions") .populate<{ secretVersions: ISecretVersion[] }>("secretVersions")
.limit(50); .limit(50);
@@ -377,8 +370,7 @@ export const backfillSecretFolders = async () => {
for (const secSnapshot of secretSnapshots) { for (const secSnapshot of secretSnapshots) {
const groupSnapByEnv: Record<string, Array<ISecretVersion>> = {}; const groupSnapByEnv: Record<string, Array<ISecretVersion>> = {};
secSnapshot.secretVersions.forEach((secVer) => { secSnapshot.secretVersions.forEach((secVer) => {
if (!groupSnapByEnv?.[secVer.environment]) if (!groupSnapByEnv?.[secVer.environment]) groupSnapByEnv[secVer.environment] = [];
groupSnapByEnv[secVer.environment] = [];
groupSnapByEnv[secVer.environment].push(secVer); groupSnapByEnv[secVer.environment].push(secVer);
}); });
@@ -390,7 +382,7 @@ export const backfillSecretFolders = async () => {
...secSnapshot.toObject({ virtuals: false }), ...secSnapshot.toObject({ virtuals: false }),
_id: new Types.ObjectId(), _id: new Types.ObjectId(),
environment: snapEnv, environment: snapEnv,
secretVersions: secretIdsOfEnvGroup, secretVersions: secretIdsOfEnvGroup
}; };
}); });
@@ -400,8 +392,8 @@ export const backfillSecretFolders = async () => {
secretSnapshots = await SecretSnapshot.find({ secretSnapshots = await SecretSnapshot.find({
environment: { environment: {
$exists: false, $exists: false
}, }
}) })
.populate<{ secretVersions: ISecretVersion[] }>("secretVersions") .populate<{ secretVersions: ISecretVersion[] }>("secretVersions")
.limit(50); .limit(50);
@@ -414,13 +406,13 @@ export const backfillServiceToken = async () => {
await ServiceTokenData.updateMany( await ServiceTokenData.updateMany(
{ {
secretPath: { secretPath: {
$exists: false, $exists: false
}, }
}, },
{ {
$set: { $set: {
secretPath: "/", secretPath: "/"
}, }
} }
); );
console.log("Migration: Service token migration v1 complete"); console.log("Migration: Service token migration v1 complete");
@@ -430,14 +422,36 @@ export const backfillIntegration = async () => {
await Integration.updateMany( await Integration.updateMany(
{ {
secretPath: { secretPath: {
$exists: false, $exists: false
}, }
}, },
{ {
$set: { $set: {
secretPath: "/", secretPath: "/"
}, }
} }
); );
console.log("Migration: Integration migration v1 complete"); console.log("Migration: Integration migration v1 complete");
}; };
export const backfillServiceTokenMultiScope = async () => {
await ServiceTokenData.updateMany(
{
scopes: {
$exists: false
}
},
[
{
$set: {
scopes: [{ environment: "$environment", secretPath: "$secretPath" }]
}
},
{
$unset: ["environment", "secretPath"]
}
]
);
console.log("Migration: Service token migration v2 complete");
};
+5 -6
View File
@@ -14,17 +14,15 @@ import {
backfillSecretFolders, backfillSecretFolders,
backfillSecretVersions, backfillSecretVersions,
backfillServiceToken, backfillServiceToken,
backfillServiceTokenMultiScope
} from "./backfillData"; } from "./backfillData";
import { import { reencryptBotPrivateKeys, reencryptSecretBlindIndexDataSalts } from "./reencryptData";
reencryptBotPrivateKeys,
reencryptSecretBlindIndexDataSalts,
} from "./reencryptData";
import { import {
getClientIdGoogle, getClientIdGoogle,
getClientSecretGoogle, getClientSecretGoogle,
getMongoURL, getMongoURL,
getNodeEnv, getNodeEnv,
getSentryDSN, getSentryDSN
} from "../../config"; } from "../../config";
import { initializePassport } from "../auth"; import { initializePassport } from "../auth";
@@ -79,6 +77,7 @@ export const setup = async () => {
await backfillSecretFolders(); await backfillSecretFolders();
await backfillServiceToken(); await backfillServiceToken();
await backfillIntegration(); await backfillIntegration();
await backfillServiceTokenMultiScope();
// re-encrypt any data previously encrypted under server hex 128-bit ENCRYPTION_KEY // re-encrypt any data previously encrypted under server hex 128-bit ENCRYPTION_KEY
// to base64 256-bit ROOT_ENCRYPTION_KEY // to base64 256-bit ROOT_ENCRYPTION_KEY
@@ -90,7 +89,7 @@ export const setup = async () => {
dsn: await getSentryDSN(), dsn: await getSentryDSN(),
tracesSampleRate: 1.0, tracesSampleRate: 1.0,
debug: (await getNodeEnv()) === "production" ? false : true, debug: (await getNodeEnv()) === "production" ? false : true,
environment: await getNodeEnv(), environment: await getNodeEnv()
}); });
await createTestUserForDevelopment(); await createTestUserForDevelopment();
+126 -124
View File
@@ -1,22 +1,19 @@
import { Types } from "mongoose"; import { Types } from "mongoose";
import { import {
ISecret, ISecret,
IServiceAccount, IServiceAccount,
IServiceTokenData, IServiceTokenData,
IUser, IUser,
ServiceAccount, ServiceAccount,
ServiceTokenData, ServiceTokenData,
User, User
} from "../models"; } from "../models";
import { import { ServiceTokenDataNotFoundError, UnauthorizedRequestError } from "../utils/errors";
ServiceTokenDataNotFoundError,
UnauthorizedRequestError,
} from "../utils/errors";
import { import {
AUTH_MODE_API_KEY, AUTH_MODE_API_KEY,
AUTH_MODE_JWT, AUTH_MODE_JWT,
AUTH_MODE_SERVICE_ACCOUNT, AUTH_MODE_SERVICE_ACCOUNT,
AUTH_MODE_SERVICE_TOKEN, AUTH_MODE_SERVICE_TOKEN
} from "../variables"; } from "../variables";
import { validateUserClientForWorkspace } from "./user"; import { validateUserClientForWorkspace } from "./user";
import { validateServiceAccountClientForWorkspace } from "./serviceAccount"; import { validateServiceAccountClientForWorkspace } from "./serviceAccount";
@@ -30,65 +27,71 @@ import { validateServiceAccountClientForWorkspace } from "./serviceAccount";
* @param {Array<'admin' | 'member'>} obj.acceptedRoles - accepted workspace roles * @param {Array<'admin' | 'member'>} obj.acceptedRoles - accepted workspace roles
*/ */
export const validateClientForServiceTokenData = async ({ export const validateClientForServiceTokenData = async ({
authData, authData,
serviceTokenDataId, serviceTokenDataId,
acceptedRoles, acceptedRoles
}: { }: {
authData: { authData: {
authMode: string; authMode: string;
authPayload: IUser | IServiceAccount | IServiceTokenData; authPayload: IUser | IServiceAccount | IServiceTokenData;
}; };
serviceTokenDataId: Types.ObjectId; serviceTokenDataId: Types.ObjectId;
acceptedRoles: Array<"admin" | "member">; acceptedRoles: Array<"admin" | "member">;
}) => { }) => {
const serviceTokenData = await ServiceTokenData const serviceTokenData = await ServiceTokenData.findById(serviceTokenDataId)
.findById(serviceTokenDataId) .select("+encryptedKey +iv +tag")
.select("+encryptedKey +iv +tag") .populate<{ user: IUser }>("user");
.populate<{ user: IUser }>("user");
if (!serviceTokenData) throw ServiceTokenDataNotFoundError({ if (!serviceTokenData)
message: "Failed to find service token data", throw ServiceTokenDataNotFoundError({
message: "Failed to find service token data"
}); });
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) { if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
await validateUserClientForWorkspace({ await validateUserClientForWorkspace({
user: authData.authPayload, user: authData.authPayload,
workspaceId: serviceTokenData.workspace, workspaceId: serviceTokenData.workspace,
acceptedRoles, acceptedRoles
}); });
return serviceTokenData;
}
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) { return serviceTokenData;
await validateServiceAccountClientForWorkspace({ }
serviceAccount: authData.authPayload,
workspaceId: serviceTokenData.workspace,
});
return serviceTokenData;
}
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) { if (
throw UnauthorizedRequestError({ authData.authMode === AUTH_MODE_SERVICE_ACCOUNT &&
message: "Failed service token authorization for service token data", authData.authPayload instanceof ServiceAccount
}); ) {
} await validateServiceAccountClientForWorkspace({
serviceAccount: authData.authPayload,
workspaceId: serviceTokenData.workspace
});
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) { return serviceTokenData;
await validateUserClientForWorkspace({ }
user: authData.authPayload,
workspaceId: serviceTokenData.workspace, if (
acceptedRoles, authData.authMode === AUTH_MODE_SERVICE_TOKEN &&
}); authData.authPayload instanceof ServiceTokenData
) {
return serviceTokenData;
}
throw UnauthorizedRequestError({ throw UnauthorizedRequestError({
message: "Failed client authorization for service token data", message: "Failed service token authorization for service token data"
}); });
} }
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
await validateUserClientForWorkspace({
user: authData.authPayload,
workspaceId: serviceTokenData.workspace,
acceptedRoles
});
return serviceTokenData;
}
throw UnauthorizedRequestError({
message: "Failed client authorization for service token data"
});
};
/** /**
* Validate that service token (client) can access workspace * Validate that service token (client) can access workspace
@@ -101,42 +104,42 @@ export const validateClientForServiceTokenData = async ({
* @param {String[]} requiredPermissions - required permissions as part of the endpoint * @param {String[]} requiredPermissions - required permissions as part of the endpoint
*/ */
export const validateServiceTokenDataClientForWorkspace = async ({ export const validateServiceTokenDataClientForWorkspace = async ({
serviceTokenData, serviceTokenData,
workspaceId, workspaceId,
environment, environment,
requiredPermissions, requiredPermissions
}: { }: {
serviceTokenData: IServiceTokenData; serviceTokenData: IServiceTokenData;
workspaceId: Types.ObjectId; workspaceId: Types.ObjectId;
environment?: string; environment?: string;
requiredPermissions?: string[]; requiredPermissions?: string[];
}) => { }) => {
if (!serviceTokenData.workspace.equals(workspaceId)) { if (!serviceTokenData.workspace.equals(workspaceId)) {
// case: invalid workspaceId passed // case: invalid workspaceId passed
throw UnauthorizedRequestError({
message: "Failed service token authorization for the given workspace"
});
}
if (environment) {
// case: environment is specified
if (!serviceTokenData.scopes.find(({ environment: tkEnv }) => tkEnv === environment)) {
// case: invalid environment passed
throw UnauthorizedRequestError({
message: "Failed service token authorization for the given workspace environment"
});
}
requiredPermissions?.forEach((permission) => {
if (!serviceTokenData.permissions.includes(permission)) {
throw UnauthorizedRequestError({ throw UnauthorizedRequestError({
message: "Failed service token authorization for the given workspace", message: `Failed service token authorization for the given workspace environment action: ${permission}`
}); });
} }
});
if (environment) { }
// case: environment is specified };
if (serviceTokenData.environment !== environment) {
// case: invalid environment passed
throw UnauthorizedRequestError({
message: "Failed service token authorization for the given workspace environment",
});
}
requiredPermissions?.forEach((permission) => {
if (!serviceTokenData.permissions.includes(permission)) {
throw UnauthorizedRequestError({
message: `Failed service token authorization for the given workspace environment action: ${permission}`,
});
}
});
}
}
/** /**
* Validate that service token (client) can access secrets * Validate that service token (client) can access secrets
@@ -147,36 +150,35 @@ export const validateServiceTokenDataClientForWorkspace = async ({
* @param {string[]} requiredPermissions - required permissions as part of the endpoint * @param {string[]} requiredPermissions - required permissions as part of the endpoint
*/ */
export const validateServiceTokenDataClientForSecrets = async ({ export const validateServiceTokenDataClientForSecrets = async ({
serviceTokenData, serviceTokenData,
secrets, secrets,
requiredPermissions, requiredPermissions
}: { }: {
serviceTokenData: IServiceTokenData; serviceTokenData: IServiceTokenData;
secrets: ISecret[]; secrets: ISecret[];
requiredPermissions?: string[]; requiredPermissions?: string[];
}) => { }) => {
secrets.forEach((secret: ISecret) => {
if (!serviceTokenData.workspace.equals(secret.workspace)) {
// case: invalid workspaceId passed
throw UnauthorizedRequestError({
message: "Failed service token authorization for the given workspace"
});
}
secrets.forEach((secret: ISecret) => { if (!serviceTokenData.scopes.find(({ environment: tkEnv }) => tkEnv === secret.environment)) {
if (!serviceTokenData.workspace.equals(secret.workspace)) { // case: invalid environment passed
// case: invalid workspaceId passed throw UnauthorizedRequestError({
throw UnauthorizedRequestError({ message: "Failed service token authorization for the given workspace environment"
message: "Failed service token authorization for the given workspace", });
}); }
}
requiredPermissions?.forEach((permission) => {
if (serviceTokenData.environment !== secret.environment) { if (!serviceTokenData.permissions.includes(permission)) {
// case: invalid environment passed throw UnauthorizedRequestError({
throw UnauthorizedRequestError({ message: `Failed service token authorization for the given workspace environment action: ${permission}`
message: "Failed service token authorization for the given workspace environment",
});
}
requiredPermissions?.forEach((permission) => {
if (!serviceTokenData.permissions.includes(permission)) {
throw UnauthorizedRequestError({
message: `Failed service token authorization for the given workspace environment action: ${permission}`,
});
}
}); });
}
}); });
} });
};