Compare commits

...

36 Commits

Author SHA1 Message Date
d8d480f2bc edge case: read write for non existing env in deniedPermissions 2023-09-15 22:32:42 -04:00
f16944024b Update build-staging-img.yml 2023-09-15 20:18:05 -04:00
29da8843a3 add prefix for role name 2023-09-15 20:11:06 -04:00
8cd6a1f564 no release lock after backfill complete 2023-09-15 18:56:41 -04:00
e8fd3c8045 update lock time for permission backfill 2023-09-15 18:14:31 -04:00
59cd8580d5 bring back tests for CI 2023-09-15 17:56:12 -04:00
859cec49d1 make redis client conditional 2023-09-15 17:55:43 -04:00
5494bc6c3c Update build-staging-img.yml 2023-09-15 17:22:54 -04:00
95385b1f45 Merge pull request #991 from akhilmhdh/feat/rbac-migratio
feat(rbac): migration script for permission from old permission to new
2023-09-15 17:09:51 -04:00
b88a319582 add lock mechanism for backfillPermission script 2023-09-15 16:39:59 -04:00
26229b07bc Merge pull request #994 from Infisical/integration-options
Finish integration options/react form refactor for GitLab and GCP SM …
2023-09-15 21:00:55 +01:00
3ab5db9b2a Finish integration options/react form refactor for GitLab and GCP SM integrations, add docs for it 2023-09-15 20:53:31 +01:00
717b831e94 Merge pull request #992 from serin0837/parse-env-file
fix hyphen env variable import bug
2023-09-15 11:45:36 -07:00
336b5897f0 update role description 2023-09-15 12:40:40 -04:00
0ce5aaf61c add role deduplication logic 2023-09-15 11:44:48 -04:00
adfa90340d remove unsetting deniedPermissions 2023-09-15 09:53:17 -04:00
444aca0070 fix hyphen env variable import bug 2023-09-15 14:52:30 +01:00
029766c534 feat(rbac): migration script for permission from old permission to new 2023-09-15 16:20:21 +05:30
9b14b64ec2 Merge pull request #983 from ragnarbull/main
Docs: Update FAQ for Alpine CDN error
2023-09-15 00:31:14 -04:00
0a72dccdcf add back defaultOpen="true" 2023-09-15 00:30:15 -04:00
7fe94d66cd Create new FAQ page under developer setup docs 2023-09-15 11:59:15 +10:00
f503f8c76d Merge pull request #985 from xphyr/main
changing CMD for Dockerfile  to address issue #984
2023-09-14 19:32:09 -04:00
7982b1d668 replace owner role for local dev user 2023-09-14 19:27:23 -04:00
7a78209613 Merge pull request #977 from akhilmhdh:feat/permission-patch-2
feat(rbac): removed owner role and changed member permissions
2023-09-14 18:57:02 -04:00
019024e4ae remove the use of owner everywhere else 2023-09-14 18:23:59 -04:00
4d6895a793 Merge pull request #933 from MohamadTahir:add_resource_probs_to_deployments
Add resource specification to frontend and backend deployment containers
2023-09-14 16:36:51 -04:00
36b5ba2855 remove change log bc will get replaced by auto generated one soon 2023-09-14 16:35:13 -04:00
44d2a6c553 clearing npm cache to save space 2023-09-14 13:57:58 -04:00
a073a746f2 changing CMD for Dockerfile to use node instead of npm to address issue #984 2023-09-14 13:33:15 -04:00
edb3e66267 fix(integrations): resolved integration bot deactive revoke bug 2023-09-14 21:56:57 +05:30
942e1a82c2 feat(rbac): removed audit log option for time being, v3 secret patch and reload permission flash screen fix 2023-09-14 21:34:13 +05:30
18d843f3e6 feat(rbac): fixed ip allow list api 2023-09-14 20:40:57 +05:30
ee96325034 Update FAQ for Alpine CDN error 2023-09-14 23:24:32 +10:00
3d2a2651b8 feat(rbac): removed owner role and changed member permissions 2023-09-14 13:20:42 +05:30
86b2b95d11 update readme file and values.yaml documentations 2023-09-11 22:10:47 +03:00
5704dfb35f add resource specification to frontend and backend deployment containers 2023-09-02 09:29:23 +03:00
52 changed files with 1260 additions and 671 deletions

View File

@ -11,9 +11,9 @@ jobs:
- name: 📦 Install dependencies to test all dependencies
run: npm ci --only-production
working-directory: backend
- name: 🧪 Run tests
run: npm run test:ci
working-directory: backend
# - name: 🧪 Run tests
# run: npm run test:ci
# working-directory: backend
- name: Save commit hashes for tag
id: commit
uses: pr-mpt/actions-commit-hash@v2

View File

@ -17,7 +17,7 @@ WORKDIR /app
ENV npm_config_cache /home/node/.npm
COPY package*.json ./
RUN npm ci --only-production
RUN npm ci --only-production && npm cache clean --force
COPY --from=build /app .
@ -30,4 +30,4 @@ HEALTHCHECK --interval=10s --timeout=3s --start-period=10s \
EXPOSE 4000
CMD ["npm", "run", "start"]
CMD ["node", "build/index.js"]

View File

@ -37,6 +37,7 @@
"handlebars": "^4.7.7",
"helmet": "^5.1.1",
"infisical-node": "^1.2.1",
"ioredis": "^5.3.2",
"js-yaml": "^4.1.0",
"jsonwebtoken": "^9.0.0",
"jsrp": "^0.2.4",
@ -3508,8 +3509,7 @@
"node_modules/@ioredis/commands": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz",
"integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==",
"dev": true
"integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg=="
},
"node_modules/@istanbuljs/load-nyc-config": {
"version": "1.1.0",
@ -7031,39 +7031,6 @@
"node": ">=12"
}
},
"node_modules/bull/node_modules/denque": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
"dev": true,
"engines": {
"node": ">=0.10"
}
},
"node_modules/bull/node_modules/ioredis": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.3.2.tgz",
"integrity": "sha512-1DKMMzlIHM02eBBVOFQ1+AolGjs6+xEcM4PDL7NqOS6szq7H9jSaEkIUH6/a5Hl241LzW6JLSiAbNvTQjUupUA==",
"dev": true,
"dependencies": {
"@ioredis/commands": "^1.1.1",
"cluster-key-slot": "^1.1.0",
"debug": "^4.3.4",
"denque": "^2.1.0",
"lodash.defaults": "^4.2.0",
"lodash.isarguments": "^3.1.0",
"redis-errors": "^1.2.0",
"redis-parser": "^3.0.0",
"standard-as-callback": "^2.1.0"
},
"engines": {
"node": ">=12.22.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/ioredis"
}
},
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
@ -9071,30 +9038,36 @@
}
},
"node_modules/ioredis": {
"version": "4.28.5",
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-4.28.5.tgz",
"integrity": "sha512-3GYo0GJtLqgNXj4YhrisLaNNvWSNwSS2wS4OELGfGxH8I69+XfNdnmV1AyN+ZqMh0i7eX+SWjrwFKDBDgfBC1A==",
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.3.2.tgz",
"integrity": "sha512-1DKMMzlIHM02eBBVOFQ1+AolGjs6+xEcM4PDL7NqOS6szq7H9jSaEkIUH6/a5Hl241LzW6JLSiAbNvTQjUupUA==",
"dependencies": {
"@ioredis/commands": "^1.1.1",
"cluster-key-slot": "^1.1.0",
"debug": "^4.3.1",
"denque": "^1.1.0",
"debug": "^4.3.4",
"denque": "^2.1.0",
"lodash.defaults": "^4.2.0",
"lodash.flatten": "^4.4.0",
"lodash.isarguments": "^3.1.0",
"p-map": "^2.1.0",
"redis-commands": "1.7.0",
"redis-errors": "^1.2.0",
"redis-parser": "^3.0.0",
"standard-as-callback": "^2.1.0"
},
"engines": {
"node": ">=6"
"node": ">=12.22.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/ioredis"
}
},
"node_modules/ioredis/node_modules/denque": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
"engines": {
"node": ">=0.10"
}
},
"node_modules/ip": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz",
@ -14445,6 +14418,31 @@
"node": ">=10"
}
},
"node_modules/probot/node_modules/ioredis": {
"version": "4.28.5",
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-4.28.5.tgz",
"integrity": "sha512-3GYo0GJtLqgNXj4YhrisLaNNvWSNwSS2wS4OELGfGxH8I69+XfNdnmV1AyN+ZqMh0i7eX+SWjrwFKDBDgfBC1A==",
"dependencies": {
"cluster-key-slot": "^1.1.0",
"debug": "^4.3.1",
"denque": "^1.1.0",
"lodash.defaults": "^4.2.0",
"lodash.flatten": "^4.4.0",
"lodash.isarguments": "^3.1.0",
"p-map": "^2.1.0",
"redis-commands": "1.7.0",
"redis-errors": "^1.2.0",
"redis-parser": "^3.0.0",
"standard-as-callback": "^2.1.0"
},
"engines": {
"node": ">=6"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/ioredis"
}
},
"node_modules/probot/node_modules/js-yaml": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
@ -19517,8 +19515,7 @@
"@ioredis/commands": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz",
"integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==",
"dev": true
"integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg=="
},
"@istanbuljs/load-nyc-config": {
"version": "1.1.0",
@ -22302,31 +22299,6 @@
"msgpackr": "^1.5.2",
"semver": "^7.3.2",
"uuid": "^8.3.0"
},
"dependencies": {
"denque": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
"dev": true
},
"ioredis": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.3.2.tgz",
"integrity": "sha512-1DKMMzlIHM02eBBVOFQ1+AolGjs6+xEcM4PDL7NqOS6szq7H9jSaEkIUH6/a5Hl241LzW6JLSiAbNvTQjUupUA==",
"dev": true,
"requires": {
"@ioredis/commands": "^1.1.1",
"cluster-key-slot": "^1.1.0",
"debug": "^4.3.4",
"denque": "^2.1.0",
"lodash.defaults": "^4.2.0",
"lodash.isarguments": "^3.1.0",
"redis-errors": "^1.2.0",
"redis-parser": "^3.0.0",
"standard-as-callback": "^2.1.0"
}
}
}
},
"bytes": {
@ -23809,21 +23781,26 @@
"dev": true
},
"ioredis": {
"version": "4.28.5",
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-4.28.5.tgz",
"integrity": "sha512-3GYo0GJtLqgNXj4YhrisLaNNvWSNwSS2wS4OELGfGxH8I69+XfNdnmV1AyN+ZqMh0i7eX+SWjrwFKDBDgfBC1A==",
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.3.2.tgz",
"integrity": "sha512-1DKMMzlIHM02eBBVOFQ1+AolGjs6+xEcM4PDL7NqOS6szq7H9jSaEkIUH6/a5Hl241LzW6JLSiAbNvTQjUupUA==",
"requires": {
"@ioredis/commands": "^1.1.1",
"cluster-key-slot": "^1.1.0",
"debug": "^4.3.1",
"denque": "^1.1.0",
"debug": "^4.3.4",
"denque": "^2.1.0",
"lodash.defaults": "^4.2.0",
"lodash.flatten": "^4.4.0",
"lodash.isarguments": "^3.1.0",
"p-map": "^2.1.0",
"redis-commands": "1.7.0",
"redis-errors": "^1.2.0",
"redis-parser": "^3.0.0",
"standard-as-callback": "^2.1.0"
},
"dependencies": {
"denque": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="
}
}
},
"ip": {
@ -27791,6 +27768,24 @@
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz",
"integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g=="
},
"ioredis": {
"version": "4.28.5",
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-4.28.5.tgz",
"integrity": "sha512-3GYo0GJtLqgNXj4YhrisLaNNvWSNwSS2wS4OELGfGxH8I69+XfNdnmV1AyN+ZqMh0i7eX+SWjrwFKDBDgfBC1A==",
"requires": {
"cluster-key-slot": "^1.1.0",
"debug": "^4.3.1",
"denque": "^1.1.0",
"lodash.defaults": "^4.2.0",
"lodash.flatten": "^4.4.0",
"lodash.isarguments": "^3.1.0",
"p-map": "^2.1.0",
"redis-commands": "1.7.0",
"redis-errors": "^1.2.0",
"redis-parser": "^3.0.0",
"standard-as-callback": "^2.1.0"
}
},
"js-yaml": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",

View File

@ -28,6 +28,7 @@
"handlebars": "^4.7.7",
"helmet": "^5.1.1",
"infisical-node": "^1.2.1",
"ioredis": "^5.3.2",
"js-yaml": "^4.1.0",
"jsonwebtoken": "^9.0.0",
"jsrp": "^0.2.4",

View File

@ -148,7 +148,12 @@ export const changeMembershipRole = async (req: Request, res: Response) => {
const membership = await Membership.findByIdAndUpdate(
membershipId,
{
role
$set: {
role
},
$unset: {
customRole: 1
}
},
{
new: true

View File

@ -8,7 +8,7 @@ import {
} from "../../models";
import { createOrganization as create } from "../../helpers/organization";
import { addMembershipsOrg } from "../../helpers/membershipOrg";
import { ACCEPTED, OWNER } from "../../variables";
import { ACCEPTED, ADMIN } from "../../variables";
import { getLicenseServerUrl, getSiteURL } from "../../config";
import { licenseServerKeyRequest } from "../../config/request";
import { validateRequest } from "../../helpers/validation";
@ -55,7 +55,7 @@ export const createOrganization = async (req: Request, res: Response) => {
await addMembershipsOrg({
userIds: [req.user._id.toString()],
organizationId: organization._id.toString(),
roles: [OWNER],
roles: [ADMIN],
statuses: [ACCEPTED]
});

View File

@ -159,7 +159,12 @@ export const updateOrganizationMembership = async (req: Request, res: Response)
const membership = await MembershipOrg.findByIdAndUpdate(
membershipId,
{
role
$set: {
role
},
$unset: {
customRole: 1
}
},
{
new: true

View File

@ -55,6 +55,9 @@ export const getSecretsRaw = async (req: Request, res: Response) => {
secretPath = getFolderWithPathFromId(folder.nodes, folderId).folderPath;
}
if (!environment || !workspaceId)
throw BadRequestError({ message: "Missing environment or workspace id" });
let permissionCheckFn: (env: string, secPath: string) => boolean; // used to pass as callback function to import secret
if (req.user?._id) {
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);

View File

@ -171,18 +171,6 @@ export const getRoles = async (req: Request, res: Response) => {
const customRoles = await Role.find({ organization: orgId, isOrgRole, workspace: workspaceId });
// as this is shared between org and workspace switch the rule set based on it
const roles = [
// owner is only in org level role
...(isOrgRole
? [
{
_id: "owner",
name: "Owner",
slug: "owner",
description: "Complete administration access over the organization.",
permissions: adminPermissions.rules
}
]
: []),
{
_id: "admin",
name: "Admin",
@ -192,7 +180,7 @@ export const getRoles = async (req: Request, res: Response) => {
},
{
_id: "member",
name: "Member",
name: isOrgRole ? "Member" : "Developer",
slug: "member",
description: "Non-administrative role in an organization",
permissions: isOrgRole ? memberPermissions.rules : memberProjectPermissions.rules

View File

@ -1,6 +1,14 @@
import { Request, Response } from "express";
import { PipelineStage, Types } from "mongoose";
import { Folder, Membership, Secret, ServiceTokenData, TFolderSchema, User } from "../../../models";
import {
Folder,
Membership,
Secret,
ServiceTokenData,
TFolderSchema,
User,
Workspace
} from "../../../models";
import {
ActorType,
AuditLog,
@ -41,6 +49,7 @@ import {
getUserProjectPermissions
} from "../../services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability";
import { BadRequestError } from "../../../utils/errors";
/**
* Return secret snapshots for workspace with id [workspaceId]
@ -781,7 +790,10 @@ export const addWorkspaceTrustedIp = async (req: Request, res: Response) => {
ProjectPermissionSub.IpAllowList
);
const plan = await EELicenseService.getPlan(req.workspace.organization);
const workspace = await Workspace.findById(workspaceId);
if (!workspace) throw BadRequestError({ message: "Workspace not found" });
const plan = await EELicenseService.getPlan(workspace.organization);
if (!plan.ipAllowlisting)
return res.status(400).send({
@ -844,7 +856,10 @@ export const updateWorkspaceTrustedIp = async (req: Request, res: Response) => {
ProjectPermissionSub.IpAllowList
);
const plan = await EELicenseService.getPlan(req.workspace.organization);
const workspace = await Workspace.findById(workspaceId);
if (!workspace) throw BadRequestError({ message: "Workspace not found" });
const plan = await EELicenseService.getPlan(workspace.organization);
if (!plan.ipAllowlisting)
return res.status(400).send({
@ -933,7 +948,10 @@ export const deleteWorkspaceTrustedIp = async (req: Request, res: Response) => {
ProjectPermissionSub.IpAllowList
);
const plan = await EELicenseService.getPlan(req.workspace.organization);
const workspace = await Workspace.findById(workspaceId);
if (!workspace) throw BadRequestError({ message: "Workspace not found" });
const plan = await EELicenseService.getPlan(workspace.organization);
if (!plan.ipAllowlisting)
return res.status(400).send({

View File

@ -158,13 +158,39 @@ const buildMemberPermission = () => {
can(ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Role);
can(ProjectPermissionActions.Create, ProjectPermissionSub.Member);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
can(ProjectPermissionActions.Create, ProjectPermissionSub.Integrations);
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Integrations);
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Integrations);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks);
can(ProjectPermissionActions.Create, ProjectPermissionSub.Webhooks);
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Webhooks);
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Webhooks);
can(ProjectPermissionActions.Read, ProjectPermissionSub.ServiceTokens);
can(ProjectPermissionActions.Create, ProjectPermissionSub.ServiceTokens);
can(ProjectPermissionActions.Edit, ProjectPermissionSub.ServiceTokens);
can(ProjectPermissionActions.Delete, ProjectPermissionSub.ServiceTokens);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Settings);
can(ProjectPermissionActions.Create, ProjectPermissionSub.Settings);
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings);
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Settings);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Environments);
can(ProjectPermissionActions.Create, ProjectPermissionSub.Environments);
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Environments);
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Environments);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Tags);
can(ProjectPermissionActions.Create, ProjectPermissionSub.Tags);
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Tags);
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Tags);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Role);
can(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs);
can(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList);

View File

@ -86,12 +86,17 @@ const buildMemberPermission = () => {
can(OrgPermissionActions.Read, OrgPermissionSubjects.Workspace);
can(OrgPermissionActions.Create, OrgPermissionSubjects.Workspace);
can(OrgPermissionActions.Read, OrgPermissionSubjects.Member);
can(OrgPermissionActions.Create, OrgPermissionSubjects.Member);
can(OrgPermissionActions.Read, OrgPermissionSubjects.Role);
can(OrgPermissionActions.Read, OrgPermissionSubjects.Settings);
can(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
can(OrgPermissionActions.Read, OrgPermissionSubjects.Sso);
can(OrgPermissionActions.Read, OrgPermissionSubjects.IncidentAccount);
can(OrgPermissionActions.Read, OrgPermissionSubjects.SecretScanning);
can(OrgPermissionActions.Create, OrgPermissionSubjects.SecretScanning);
can(OrgPermissionActions.Edit, OrgPermissionSubjects.SecretScanning);
can(OrgPermissionActions.Delete, OrgPermissionSubjects.SecretScanning);
return build({ conditionsMatcher });
};
@ -114,8 +119,7 @@ export const getUserOrgPermissions = async (userId: string, orgId: string) => {
throw UnauthorizedRequestError({ message: "User doesn't belong to organization" });
}
if (membership.role === "admin" || membership.role === "owner")
return { permission: adminPermissions, membership };
if (membership.role === "admin") return { permission: adminPermissions, membership };
if (membership.role === "member") return { permission: memberPermissions, membership };

View File

@ -1,7 +1,7 @@
import { IUser } from "../models";
import { createOrganization } from "./organization";
import { addMembershipsOrg } from "./membershipOrg";
import { ACCEPTED, OWNER } from "../variables";
import { ACCEPTED, ADMIN } from "../variables";
import { sendMail } from "../helpers/nodemailer";
import { TokenService } from "../services";
import { TOKEN_EMAIL_CONFIRMATION } from "../variables";
@ -14,10 +14,10 @@ import { TOKEN_EMAIL_CONFIRMATION } from "../variables";
* @returns {Boolean} success - whether or not operation was successful
*/
export const sendEmailVerification = async ({ email }: { email: string }) => {
const token = await TokenService.createToken({
type: TOKEN_EMAIL_CONFIRMATION,
email,
});
const token = await TokenService.createToken({
type: TOKEN_EMAIL_CONFIRMATION,
email
});
// send mail
await sendMail({
@ -25,8 +25,8 @@ export const sendEmailVerification = async ({ email }: { email: string }) => {
subjectLine: "Infisical confirmation code",
recipients: [email],
substitutions: {
code: token,
},
code: token
}
});
};
@ -36,17 +36,11 @@ export const sendEmailVerification = async ({ email }: { email: string }) => {
* @param {String} obj.email - emai
* @param {String} obj.code - code that was sent to [email]
*/
export const checkEmailVerification = async ({
email,
code,
}: {
email: string;
code: string;
}) => {
export const checkEmailVerification = async ({ email, code }: { email: string; code: string }) => {
await TokenService.validateToken({
type: TOKEN_EMAIL_CONFIRMATION,
email,
token: code,
token: code
});
};
@ -58,27 +52,27 @@ export const checkEmailVerification = async ({
* @param {IUser} obj.user - user who we are initializing for
*/
export const initializeDefaultOrg = async ({
organizationName,
user,
organizationName,
user
}: {
organizationName: string;
user: IUser;
organizationName: string;
user: IUser;
}) => {
try {
// create organization with user as owner and initialize a free
// subscription
const organization = await createOrganization({
email: user.email,
name: organizationName,
});
try {
// create organization with user as owner and initialize a free
// subscription
const organization = await createOrganization({
email: user.email,
name: organizationName
});
await addMembershipsOrg({
userIds: [user._id.toString()],
organizationId: organization._id.toString(),
roles: [OWNER],
statuses: [ACCEPTED],
});
} catch (err) {
throw new Error(`Failed to initialize default organization and workspace [err=${err}]`);
}
};
await addMembershipsOrg({
userIds: [user._id.toString()],
organizationId: organization._id.toString(),
roles: [ADMIN],
statuses: [ACCEPTED]
});
} catch (err) {
throw new Error(`Failed to initialize default organization and workspace [err=${err}]`);
}
};

View File

@ -328,15 +328,19 @@ const syncSecretsGCPSecretManager = async ({
const pageSize = 100;
let pageToken: string | undefined;
let hasMorePages = true;
const filterParam = integration.metadata.secretGCPLabel
? `?filter=labels.${integration.metadata.secretGCPLabel.labelName}=${integration.metadata.secretGCPLabel.labelValue}`
: "";
while (hasMorePages) {
const params = new URLSearchParams({
pageSize: String(pageSize),
...(pageToken ? { pageToken } : {})
});
const res: GCPSMListSecretsRes = (await standardRequest.get(
`${INTEGRATION_GCP_SECRET_MANAGER_URL}/v1/projects/${integration.appId}/secrets?filter=labels.managed-by=infisical`,
`${INTEGRATION_GCP_SECRET_MANAGER_URL}/v1/projects/${integration.appId}/secrets${filterParam}`,
{
params,
headers: {
@ -347,7 +351,24 @@ const syncSecretsGCPSecretManager = async ({
)).data;
if (res.secrets) {
gcpSecrets = gcpSecrets.concat(res.secrets);
const filteredSecrets = res.secrets?.filter((gcpSecret) => {
const arr = gcpSecret.name.split("/");
const key = arr[arr.length - 1];
let isValid = true;
if (integration.metadata.secretPrefix && !key.startsWith(integration.metadata.secretPrefix)) {
isValid = false;
}
if (integration.metadata.secretSuffix && !key.endsWith(integration.metadata.secretSuffix)) {
isValid = false;
}
return isValid;
});
gcpSecrets = gcpSecrets.concat(filteredSecrets);
}
if (!res.nextPageToken) {
@ -371,7 +392,7 @@ const syncSecretsGCPSecretManager = async ({
const key = arr[arr.length - 1];
const secretLatest: GCPLatestSecretVersionAccess = (await standardRequest.get(
`${INTEGRATION_GCP_SECRET_MANAGER_URL}/v1beta1/projects/${integration.appId}/secrets/${key}/versions/latest:access`,
`${INTEGRATION_GCP_SECRET_MANAGER_URL}/v1/projects/${integration.appId}/secrets/${key}/versions/latest:access`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
@ -379,6 +400,7 @@ const syncSecretsGCPSecretManager = async ({
}
}
)).data;
res[key] = Buffer.from(secretLatest.payload.data, "base64").toString("utf-8");
}
@ -387,14 +409,16 @@ const syncSecretsGCPSecretManager = async ({
if (!(key in res)) {
// case: create secret
await standardRequest.post(
`${INTEGRATION_GCP_SECRET_MANAGER_URL}/v1beta1/projects/${integration.appId}/secrets`,
`${INTEGRATION_GCP_SECRET_MANAGER_URL}/v1/projects/${integration.appId}/secrets`,
{
replication: {
automatic: {}
},
labels: {
"managed-by": "infisical"
}
...(integration.metadata.secretGCPLabel ? {
labels: {
[integration.metadata.secretGCPLabel.labelName]: integration.metadata.secretGCPLabel.labelValue
}
} : {})
},
{
params: {
@ -408,7 +432,7 @@ const syncSecretsGCPSecretManager = async ({
);
await standardRequest.post(
`${INTEGRATION_GCP_SECRET_MANAGER_URL}/v1beta1/projects/${integration.appId}/secrets/${key}:addVersion`,
`${INTEGRATION_GCP_SECRET_MANAGER_URL}/v1/projects/${integration.appId}/secrets/${key}:addVersion`,
{
payload: {
data: Buffer.from(secrets[key].value).toString("base64")
@ -428,7 +452,7 @@ const syncSecretsGCPSecretManager = async ({
if (!(key in secrets)) {
// case: delete secret
await standardRequest.delete(
`${INTEGRATION_GCP_SECRET_MANAGER_URL}/v1beta1/projects/${integration.appId}/secrets/${key}`,
`${INTEGRATION_GCP_SECRET_MANAGER_URL}/v1/projects/${integration.appId}/secrets/${key}`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
@ -440,7 +464,7 @@ const syncSecretsGCPSecretManager = async ({
// case: update secret
if (secrets[key].value !== res[key]) {
await standardRequest.post(
`${INTEGRATION_GCP_SECRET_MANAGER_URL}/v1beta1/projects/${integration.appId}/secrets/${key}:addVersion`,
`${INTEGRATION_GCP_SECRET_MANAGER_URL}/v1/projects/${integration.appId}/secrets/${key}:addVersion`,
{
payload: {
data: Buffer.from(secrets[key].value).toString("base64")
@ -1863,10 +1887,24 @@ const syncSecretsGitLab = async ({
};
const allEnvVariables = await getAllEnvVariables(integration?.appId, accessToken);
const getSecretsRes: GitLabSecret[] = allEnvVariables.filter(
(secret: GitLabSecret) => secret.environment_scope === integration.targetEnvironment
);
const getSecretsRes: GitLabSecret[] = allEnvVariables
.filter(
(secret: GitLabSecret) => secret.environment_scope === integration.targetEnvironment
)
.filter((gitLabSecret) => {
let isValid = true;
if (integration.metadata.secretPrefix && !gitLabSecret.key.startsWith(integration.metadata.secretPrefix)) {
isValid = false;
}
if (integration.metadata.secretSuffix && !gitLabSecret.key.endsWith(integration.metadata.secretSuffix)) {
isValid = false;
}
return isValid;
});
for await (const key of Object.keys(secrets)) {
const existingSecret = getSecretsRes.find((s: any) => s.key == key);
if (!existingSecret) {

View File

@ -1,3 +1,11 @@
// TODO: in the future separate metadata
// into distinct types by integration
export type Metadata = {
secretPrefix?: string;
secretSuffix?: string;
secretGCPLabel?: {
labelName: string;
labelValue: string;
}
}

View File

@ -1,5 +1,5 @@
import { Document, Schema, Types, model } from "mongoose";
import { ACCEPTED, ADMIN, CUSTOM, INVITED, MEMBER, OWNER } from "../variables";
import { ACCEPTED, ADMIN, CUSTOM, INVITED, MEMBER } from "../variables";
export interface IMembershipOrg extends Document {
_id: Types.ObjectId;
@ -26,7 +26,7 @@ const membershipOrgSchema = new Schema(
},
role: {
type: String,
enum: [OWNER, ADMIN, MEMBER, CUSTOM],
enum: [ADMIN, MEMBER, CUSTOM],
required: true
},
status: {

View File

@ -35,9 +35,12 @@ syncSecretsToThirdPartyServices.process(async (job: Job) => {
});
const suffixedSecrets: any = {};
if (integration.metadata?.secretSuffix) {
if (integration.metadata) {
for (const key in secrets) {
const newKey = key + integration.metadata?.secretSuffix;
const prefix = (integration.metadata?.secretPrefix || "");
const suffix = (integration.metadata?.secretSuffix || "");
const newKey = prefix + key + suffix;
suffixedSecrets[newKey] = secrets[key];
}
}

View File

@ -4,7 +4,7 @@ import TelemetryService from "../../services/TelemetryService";
import { sendMail } from "../../helpers";
import GitRisks from "../../ee/models/gitRisks";
import { MembershipOrg, User } from "../../models";
import { ADMIN, OWNER } from "../../variables";
import { ADMIN } from "../../variables";
import { convertKeysToLowercase, scanFullRepoContentAndGetFindings } from "../../ee/services/GithubSecretScanning/helper";
import { getSecretScanningGitAppId, getSecretScanningPrivateKey } from "../../config";
import { SecretMatch } from "../../ee/services/GithubSecretScanning/types";
@ -13,7 +13,7 @@ export const githubFullRepositorySecretScan = new Queue("github-full-repository-
type TScanPushEventQueueDetails = {
organizationId: string,
installationId: number,
installationId: number,
repository: {
id: number,
fullName: string,
@ -24,22 +24,22 @@ githubFullRepositorySecretScan.process(async (job: Job, done: Queue.DoneCallback
const { organizationId, repository, installationId }: TScanPushEventQueueDetails = job.data
try {
const octokit = new ProbotOctokit({
auth: {
auth: {
appId: await getSecretScanningGitAppId(),
privateKey: await getSecretScanningPrivateKey(),
installationId: installationId
},
},
});
const findings : SecretMatch[] = await scanFullRepoContentAndGetFindings(octokit, installationId, repository.fullName)
const findings: SecretMatch[] = await scanFullRepoContentAndGetFindings(octokit, installationId, repository.fullName)
for (const finding of findings) {
await GitRisks.findOneAndUpdate({ fingerprint: finding.Fingerprint},
await GitRisks.findOneAndUpdate({ fingerprint: finding.Fingerprint },
{
...convertKeysToLowercase(finding),
installationId: installationId,
organization: organizationId,
repositoryFullName: repository.fullName,
repositoryId: repository.id
}, {
...convertKeysToLowercase(finding),
installationId: installationId,
organization: organizationId,
repositoryFullName: repository.fullName,
repositoryId: repository.id
}, {
upsert: true
}).lean()
}
@ -47,10 +47,7 @@ githubFullRepositorySecretScan.process(async (job: Job, done: Queue.DoneCallback
// get emails of admins
const adminsOfWork = await MembershipOrg.find({
organization: organizationId,
$or: [
{ role: OWNER },
{ role: ADMIN }
]
role: ADMIN,
}).lean()
const userEmails = await User.find({

View File

@ -5,7 +5,7 @@ import TelemetryService from "../../services/TelemetryService";
import { sendMail } from "../../helpers";
import GitRisks from "../../ee/models/gitRisks";
import { MembershipOrg, User } from "../../models";
import { ADMIN, OWNER } from "../../variables";
import { ADMIN } from "../../variables";
import { convertKeysToLowercase, scanContentAndGetFindings } from "../../ee/services/GithubSecretScanning/helper";
import { getSecretScanningGitAppId, getSecretScanningPrivateKey } from "../../config";
import { SecretMatch } from "../../ee/services/GithubSecretScanning/types";
@ -88,10 +88,7 @@ githubPushEventSecretScan.process(async (job: Job, done: Queue.DoneCallback) =>
// get emails of admins
const adminsOfWork = await MembershipOrg.find({
organization: organizationId,
$or: [
{ role: OWNER },
{ role: ADMIN }
]
role: ADMIN
}).lean()
const userEmails = await User.find({

View File

@ -4,7 +4,7 @@ import {
requireAuth,
requireOrganizationAuth
} from "../../middleware";
import { ACCEPTED, ADMIN, AuthMode, OWNER } from "../../variables";
import { ACCEPTED, ADMIN, AuthMode } from "../../variables";
import { organizationsController } from "../../controllers/v2";
// TODO: /POST to create membership
@ -48,7 +48,7 @@ router.get(
acceptedAuthModes: [AuthMode.JWT]
}),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN],
acceptedRoles: [ADMIN],
acceptedStatuses: [ACCEPTED]
}),
organizationsController.getOrganizationServiceAccounts

View File

@ -0,0 +1,12 @@
import { Redis } from "ioredis"
let redisClient: Redis | null;
if (process.env.REDIS_URL) {
redisClient = new Redis(process.env.REDIS_URL as string);
} else {
console.warn("Redis URL not set, skipping Redis initialization.");
redisClient = null
}
export { redisClient }

View File

@ -80,7 +80,7 @@ export const createTestUserForDevelopment = async () => {
const testMembershipOrg = {
_id: testMembershipOrgId,
organization: testOrgId,
role: "owner",
role: "admin",
status: "accepted",
user: testUserId,
}
@ -121,7 +121,7 @@ export const createTestUserForDevelopment = async () => {
const workspaceInDB = await Workspace.findById(testWorkspaceId)
if (!workspaceInDB) {
const workspace = await Workspace.create(testWorkspace)
// initialize blind index salt for workspace
await SecretService.createSecretBlindIndexData({
workspaceId: workspace._id,

View File

@ -3,6 +3,7 @@ import crypto from "crypto";
import { Types } from "mongoose";
import { encryptSymmetric128BitHexKeyUTF8 } from "../crypto";
import { EESecretService } from "../../ee/services";
import { redisClient } from "../../services/RedisService"
import { IPType, ISecretVersion, SecretSnapshot, SecretVersion, TrustedIP } from "../../ee/models";
import {
AuthMethod,
@ -10,9 +11,11 @@ import {
Bot,
BotOrg,
ISecret,
IWorkspace,
Integration,
IntegrationAuth,
Membership,
MembershipOrg,
Organization,
Secret,
SecretBlindIndexData,
@ -23,13 +26,22 @@ import {
import { generateKeyPair } from "../../utils/crypto";
import { client, getEncryptionKey, getRootEncryptionKey } from "../../config";
import {
ADMIN,
ALGORITHM_AES_256_GCM,
CUSTOM,
ENCODING_SCHEME_BASE64,
ENCODING_SCHEME_UTF8,
MEMBER,
VIEWER
OWNER
} from "../../variables";
import { InternalServerError } from "../errors";
import {
ProjectPermissionActions,
ProjectPermissionSub,
memberProjectPermissions
} from "../../ee/services/ProjectRoleService";
import Role from "../../ee/models/role";
/**
* Backfill secrets to ensure that they're all versioned and have
@ -675,21 +687,135 @@ export const backfillUserAuthMethods = async () => {
};
export const backfillPermission = async () => {
await Membership.updateMany(
{
deniedPermissions: {
$exists: true,
$ne: []
},
role: MEMBER
},
[
{
$set: {
role: VIEWER
const lockKey = "backfill_permission_lock";
const timeout = 900000; // 15 min lock timeout in milliseconds
const lock = await redisClient?.set(lockKey, 1, "PX", timeout, "NX");
if (lock) {
try {
console.info("Lock acquired for script [backfillPermission]");
const memberships = await Membership.find({
deniedPermissions: {
$exists: true,
$ne: []
},
role: MEMBER,
})
.populate<{ workspace: IWorkspace }>("workspace")
.lean();
// group memberships that need the same permission set
const roleMap = new Map<string, { membershipIds: string[], permissions: any[], organizationId: string, workspaceId: string }>();
for (const membership of memberships) {
// get permissions of members except secret permission
const customPermissions = memberProjectPermissions.rules.filter(
({ subject }) => subject !== ProjectPermissionSub.Secrets
);
const secretAccessRule: Record<string, { read: boolean; write: boolean }> = {};
// iterate and record true and false ones
membership.deniedPermissions.forEach(({ ability, environmentSlug }) => {
if (!secretAccessRule?.[environmentSlug])
secretAccessRule[environmentSlug] = { read: true, write: true };
if (ability === "write") secretAccessRule[environmentSlug].write = false;
if (ability === "read") secretAccessRule[environmentSlug].read = false;
});
// environments that are not listed in deniedPermissions should be set to allowed for both read & and write
membership.workspace.environments.forEach(env => {
if (!secretAccessRule?.[env.slug]) {
secretAccessRule[env.slug] = { read: true, write: true };
}
})
const secretPermissions: any = [];
Object.entries(secretAccessRule).forEach(([envSlug, { read, write }]) => {
if (read) {
secretPermissions.push({
subject: ProjectPermissionSub.Secrets,
action: ProjectPermissionActions.Read,
conditions: { environment: envSlug }
});
}
if (write) {
secretPermissions.push(
{
subject: ProjectPermissionSub.Secrets,
action: ProjectPermissionActions.Edit,
conditions: { environment: envSlug }
},
{
subject: ProjectPermissionSub.Secrets,
action: ProjectPermissionActions.Delete,
conditions: { environment: envSlug }
},
{
subject: ProjectPermissionSub.Secrets,
action: ProjectPermissionActions.Create,
conditions: { environment: envSlug }
}
);
}
});
const key = `${JSON.stringify(secretPermissions)}-${membership.workspace._id.toString()}`; // group roles that have same permission with in the same workspace
const value = roleMap.get(key);
if (value) {
value.membershipIds.push(membership._id.toString());
value.organizationId = membership.workspace.organization.toString()
value.workspaceId = membership.workspace._id.toString()
} else {
roleMap.set(key, { membershipIds: [membership._id.toString()], permissions: [...customPermissions, ...secretPermissions], organizationId: membership.workspace.organization.toString(), workspaceId: membership.workspace._id.toString() });
}
}
]
);
console.log("Backfill: Finishing converting old denied permission in workspace to viewers");
for (const [key, value] of roleMap.entries()) {
const { membershipIds, permissions, workspaceId, organizationId } = value
const membership_identity = crypto.randomBytes(3).toString("hex")
const role = new Role({
name: `Limited [${membership_identity.toUpperCase()}]`,
organization: organizationId,
workspace: workspaceId,
description: "This role was auto generated by Infisical in effort to migrate your project members to our new permission system",
isOrgRole: false,
slug: `custom-role-${membership_identity}`,
permissions: permissions
});
await role.save();
for (const id of membershipIds) {
await Membership.findByIdAndUpdate(id, { // document db doesn't support update many so we must loop
$set: {
role: CUSTOM,
customRole: role
}
});
}
}
console.info("Backfill: Finished converting old denied permission in workspace to viewers");
await MembershipOrg.updateMany(
{
role: OWNER
},
{
$set: {
role: ADMIN
}
}
);
console.info("Backfill: Finished converting owner role to member");
} catch (error) {
console.error("An error occurred when running script [backfillPermission]:", error);
}
} else {
console.info("Could not acquire lock for script [backfillPermission], skipping");
}
};

View File

@ -77,7 +77,12 @@ export const CreateIntegrationV1 = z.object({
path: z.string().trim().optional(),
region: z.string().trim().optional(),
metadata: z.object({
secretSuffix: z.string().optional()
secretPrefix: z.string().optional(),
secretSuffix: z.string().optional(),
secretGCPLabel: z.object({
labelName: z.string(),
labelValue: z.string()
}).optional()
}).optional()
})
});

View File

@ -225,8 +225,8 @@ export const GetSecretsV2 = z.object({
export const GetSecretsRawV3 = z.object({
query: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
workspaceId: z.string().trim().optional(),
environment: z.string().trim().optional(),
secretPath: z.string().trim().default("/"),
folderId: z.string().trim().optional(),
include_imports: z

View File

@ -1,5 +1,5 @@
// membership roles
export const OWNER = "owner";
export const OWNER = "owner"; // depreciated
export const ADMIN = "admin";
export const MEMBER = "member";
export const VIEWER = "viewer";

93
docs/contributing/faq.mdx Normal file
View File

@ -0,0 +1,93 @@
---
title: "FAQ"
description: "Frequently Asked Questions about contributing to Infisical"
---
Frequently asked questions about contributing to Infisical can be found on this page.
If you can't find the answer you are looking for, please create an issue on our GitHub repository or join our Slack channel for additional support.
<Accordion title="Error building backend (Alpine Linux CDN temporary error)">
The Alpine Linux CDN may be unavailable/down in your region infrequently (eg. there is an unplanned outage). One possible fix is to add a retry mechanism and a fallback mirrors array to the Dockerfile. You can also use this as an opportunity to pin the Alpine Linux version for Docker to use in case there are issues with the latest version. Ensure to use https for the mirrors.
#### Make the following changes to the backend Dockerfile
```bash
# Pin Alpine version from list: https://dl-cdn.alpinelinux.org/alpine/
ARG ALPINE_VERSION=3.17
ARG ALPINE_APPEND=v3.17/main
# Specify number of retries for each mirror
ARG MAX_RETRIES=3
# Define base Alpine mirror URLs in attempt order from list: https://dl-cdn.alpinelinux.org/alpine/MIRRORS.txt
ARG BASE_ALPINE_MIRRORS="https://dl-cdn.alpinelinux.org/alpine https://ftp.halifax.rwth-aachen.de/alpine https://uk.alpinelinux.org/alpine"
# Build stage
# Add the Alpine version arg
FROM node:16-alpine$ALPINE_VERSION AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci --only-production
COPY . .
RUN npm run build
# Production stage
# Add the Alpine version arg
FROM node:16-alpine$ALPINE_VERSION
WORKDIR /app
ENV npm_config_cache /home/node/.npm
COPY package*.json ./
RUN npm ci --only-production
COPY --from=build /app .
# Add retry mechanism and loop through the specified mirrors
RUN retries_left=$MAX_RETRIES; \
for mirror in $ALPINE_MIRRORS; do \
full_mirror="$mirror/$ALPINE_APPEND"; \
echo "Trying mirror: $full_mirror"; \
echo >>/etc/apk/repositories "$full_mirror"; \
for i in $(seq $retries_left); do \
echo "Retrying... Attempt $i (Retries Left: $((retries_left - i)))"; \
if apk add --no-cache bash curl git && \
curl -1sLf 'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.alpine.sh' | bash && \
apk add --no-cache infisical=0.8.1; then \
break; \
fi; \
sleep 10; \
done; \
if [ $? -eq 0 ]; then \
break; \
fi; \
done
HEALTHCHECK --interval=10s --timeout=3s --start-period=10s \
CMD node healthcheck.js
EXPOSE 4000
CMD ["npm", "run", "start"]
```
<Info>
[Alpine Linux (mirrors) - official site](https://dl-cdn.alpinelinux.org/alpine/MIRRORS.txt)
</Info>
<Info>
[Alpine Linux (mirrors) - archived site](https://web.archive.org/web/20230914123159/https://dl-cdn.alpinelinux.org/alpine/MIRRORS.txt)
</Info>
<Info>
[Alpine Linux (versions) - official site](https://dl-cdn.alpinelinux.org/alpine/)
</Info>
<Info>
[Alpine Linux (versions) - archived site](https://web.archive.org/web/20230914123455/https://dl-cdn.alpinelinux.org/alpine/)
</Info>
</Accordion>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 185 KiB

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

@ -32,6 +32,16 @@ Press on the GitLab tile and grant Infisical access to your GitLab account.
Select which Infisical environment secrets you want to sync to which GitLab repository and press create integration to start syncing secrets to GitLab.
![integrations gitlab](../../images/integrations/gitlab/integrations-gitlab-create.png)
Note that the GitLab integration supports a few options in the **Options** tab:
- Secret Prefix: If inputted, the prefix is appended to the front of every secret name prior to being synced.
- Secret Suffix: If inputted, the suffix to appended to the back of every name of every secret prior to being synced.
Setting a secret prefix or suffix ensures that existing secrets in GCP Secret Manager are not overwritten during the sync. As part of this process, Infisical abstains from mutating any secrets in GitLab without the specified prefix or suffix.
![integrations gitlab options](../../images/integrations/gitlab/integrations-gitlab-create-options.png)
![integrations gitlab](../../images/integrations/gitlab/integrations-gitlab.png)
</Accordion>
<Accordion title="Pipeline">

View File

@ -35,14 +35,21 @@ Grant Infisical access to GCP.
## Start integration
Select which Infisical environment secrets you want to sync to which GCP secret manager project. Lastly, press create integration to start syncing secrets to GCP secret manager.
In the **Connection** tab, select which Infisical environment secrets you want to sync to which GCP secret manager project. Lastly, press create integration to start syncing secrets to GCP secret manager.
![integrations GCP secret manager](../../images/integrations/gcp-secret-manager/integrations-gcp-secret-manager-create.png)
![integrations GCP secret manager](../../images/integrations/gcp-secret-manager/integrations-gcp-secret-manager.png)
<Note>
Secrets synced from Infisical to GCP Secret Manager are automatically labeled `managed-by:infisical` to avoid overwriting existing values in GCP Secret Manager.
</Note>
Note that the GCP Secret Manager integration supports a few options in the **Options** tab:
- Secret Prefix: If inputted, the prefix is appended to the front of every secret name prior to being synced.
- Secret Suffix: If inputted, the suffix to appended to the back of every name of every secret prior to being synced.
- Label in GCP Secret Manager: If selected, every secret will be labeled in GCP Secret Manager (e.g. as `managed-by:infisical`); labels can be customized.
Setting a secret prefix, suffix, or enabling the labeling option ensures that existing secrets in GCP Secret Manager are not overwritten during the sync. As part of this process, Infisical abstains from mutating any secrets in GCP Secret Manager without the specified prefix, suffix, or attached label.
![integrations GCP secret manager options](../../images/integrations/gcp-secret-manager/integrations-gcp-secret-manager-create-options.png)
![integrations GCP secret manager](../../images/integrations/gcp-secret-manager/integrations-gcp-secret-manager.png)
<Warning>
Using Infisical to sync secrets to GCP Secret Manager requires that you enable
@ -89,14 +96,21 @@ service account in IAM & Admin > Service Accounts > Service Account > Keys).
## Start integration
Select which Infisical environment secrets you want to sync to the GCP secret manager project. Lastly, press create integration to start syncing secrets to GCP secret manager.
In the **Connection** tab, select which Infisical environment secrets you want to sync to the GCP secret manager project. Lastly, press create integration to start syncing secrets to GCP secret manager.
![integrations GCP secret manager](../../images/integrations/gcp-secret-manager/integrations-gcp-secret-manager-create.png)
![integrations GCP secret manager](../../images/integrations/gcp-secret-manager/integrations-gcp-secret-manager.png)
<Note>
Secrets synced from Infisical to GCP Secret Manager are automatically labeled `managed-by:infisical` to avoid overwriting existing values in GCP Secret Manager.
</Note>
Note that the GCP Secret Manager integration supports a few options in the **Options** tab:
- Secret Prefix: If inputted, the prefix is appended to the front of every secret name prior to being synced.
- Secret Suffix: If inputted, the suffix to appended to the back of every name of every secret prior to being synced.
- Label in GCP Secret Manager: If selected, every secret will be labeled in GCP Secret Manager (e.g. as `managed-by:infisical`); labels can be customized.
Setting a secret prefix, suffix, or enabling the labeling option ensures that existing secrets in GCP Secret Manager are not overwritten during the sync. As part of this process, Infisical abstains from mutating any secrets in GCP Secret Manager without the specified prefix, suffix, or attached label.
![integrations GCP secret manager options](../../images/integrations/gcp-secret-manager/integrations-gcp-secret-manager-create-options.png)
![integrations GCP secret manager](../../images/integrations/gcp-secret-manager/integrations-gcp-secret-manager.png)
<Warning>
Using Infisical to sync secrets to GCP Secret Manager requires that you enable

View File

@ -396,7 +396,8 @@
"contributing/overview",
"contributing/code-of-conduct",
"contributing/developing",
"contributing/pull-requests"
"contributing/pull-requests",
"contributing/faq"
]
}
],

View File

@ -1,5 +1,5 @@
const LINE =
/(?:^|^)\s*(?:export\s+)?([\w.-:]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/gm;
/(?:^|^)\s*(?:export\s+)?([\w.:-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/gm;
/**
* Return text that is the buffer parsed

View File

@ -16,14 +16,6 @@ export const ProjectPermissionProvider = ({ children }: Props): JSX.Element => {
const workspaceId = currentWorkspace?._id || "";
const { data: permission, isLoading } = useGetUserProjectPermissions({ workspaceId });
if (!permission && currentWorkspace) {
return (
<div className="flex items-center justify-center w-screen h-screen bg-bunker-800">
Failed to load user permissions
</div>
);
}
if ((isLoading && currentWorkspace) || isWsLoading) {
return (
<div className="flex items-center justify-center w-screen h-screen bg-bunker-800">
@ -37,6 +29,14 @@ export const ProjectPermissionProvider = ({ children }: Props): JSX.Element => {
);
}
if (!permission && currentWorkspace) {
return (
<div className="flex items-center justify-center w-screen h-screen bg-bunker-800">
Failed to load user permissions
</div>
);
}
return (
<ProjectPermissionContext.Provider value={permission!}>
{children}

View File

@ -57,6 +57,7 @@ export const useCreateIntegration = () => {
path?: string;
region?: string;
metadata?: {
secretPrefix?: string;
secretSuffix?: string;
}
}) => {

View File

@ -138,7 +138,6 @@ export const AppLayout = ({ children }: LayoutProps) => {
const { t } = useTranslation();
const logout = useLogoutUser();
const logOutUser = async () => {
try {
@ -488,7 +487,7 @@ export const AppLayout = ({ children }: LayoutProps) => {
</MenuItem>
</a>
</Link>
<Link href={`/project/${currentWorkspace?._id}/audit-logs`} passHref>
{/* <Link href={`/project/${currentWorkspace?._id}/audit-logs`} passHref>
<a>
<MenuItem
isSelected={
@ -499,30 +498,7 @@ export const AppLayout = ({ children }: LayoutProps) => {
Audit Logs
</MenuItem>
</a>
</Link>
{/* <Link href={`/project/${currentWorkspace?._id}/logs`} passHref>
<a>
<MenuItem
isSelected={
router.asPath === `/project/${currentWorkspace?._id}/logs`
}
icon="system-outline-168-view-headline"
>
Audit Logs
</MenuItem>
</a>
</Link> */}
{/* <Link href={`/project/${currentWorkspace?._id}/secret-scanning`} passHref>
<a>
<MenuItem
isSelected={router.asPath === `/project/${currentWorkspace?._id}/secret-scanning`}
// icon={<FontAwesomeIcon icon={faFileLines} size="lg" />}
icon="system-outline-82-extension"
>
Audit Logs
</MenuItem>
</a>
</Link> */}
<Link href={`/project/${currentWorkspace?._id}/settings`} passHref>
<a>
<MenuItem

View File

@ -39,7 +39,7 @@ export default function GCPSecretManagerAuthorizeIntegrationPage() {
setIsLoading(false);
router.push(`/integrations/gcp-secret-manager/pat/create?integrationAuthId=${integrationAuth._id}`);
router.push(`/integrations/gcp-secret-manager/create?integrationAuthId=${integrationAuth._id}`);
} catch (err) {
console.error(err);
}

View File

@ -1,6 +1,10 @@
import { useEffect, useState } from "react";
import { Controller, useForm } from "react-hook-form";
import { useRouter } from "next/router";
import { yupResolver } from "@hookform/resolvers/yup";
import { motion } from "framer-motion";
import queryString from "query-string";
import * as yup from "yup";
import {
useCreateIntegration
@ -13,7 +17,12 @@ import {
FormControl,
Input,
Select,
SelectItem
SelectItem,
Switch,
Tab,
TabList,
TabPanel,
Tabs
} from "../../../components/v2";
import {
useGetIntegrationAuthApps,
@ -21,8 +30,44 @@ import {
} from "../../../hooks/api/integrationAuth";
import { useGetWorkspaceById } from "../../../hooks/api/workspace";
enum TabSections {
Connection = "connection",
Options = "options"
}
const schema = yup.object({
selectedSourceEnvironment: yup.string().required("Source environment is required"),
secretPath: yup.string().required("Secret path is required"),
targetAppId: yup.string().required("GCP project is required"),
secretPrefix: yup.string(),
secretSuffix: yup.string(),
shouldLabel: yup.boolean(),
labelName: yup.string(),
labelValue: yup.string()
});
type FormData = yup.InferType<typeof schema>;
export default function GCPSecretManagerCreateIntegrationPage() {
const router = useRouter();
const {
control,
handleSubmit,
setValue,
watch
} = useForm<FormData>({
resolver: yupResolver(schema),
defaultValues: {
secretPath: "/",
shouldLabel: false,
labelName: "managed-by",
labelValue: "infisical"
}
});
const shouldLabel = watch("shouldLabel");
const selectedSourceEnvironment = watch("selectedSourceEnvironment");
const { mutateAsync } = useCreateIntegration();
const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]);
@ -33,29 +78,45 @@ export default function GCPSecretManagerCreateIntegrationPage() {
integrationAuthId: (integrationAuthId as string) ?? ""
});
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState("");
const [targetAppId, setTargetAppId] = useState("");
const [secretPath, setSecretPath] = useState("/");
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
if (shouldLabel) {
setValue("labelName", "managed-by");
setValue("labelValue", "infisical");
return;
}
setValue("labelName", undefined);
setValue("labelValue", undefined);
}, [shouldLabel]);
useEffect(() => {
if (workspace) {
setSelectedSourceEnvironment(workspace.environments[0].slug);
setValue("selectedSourceEnvironment", workspace.environments[0].slug);
}
}, [workspace]);
useEffect(() => {
if (integrationAuthApps) {
if (integrationAuthApps.length > 0) {
setTargetAppId(integrationAuthApps[0].appId as string);
setValue("targetAppId", integrationAuthApps[0].appId as string);
} else {
setTargetAppId("none");
setValue("targetAppId", "none");
}
}
}, [integrationAuthApps]);
const handleButtonClick = async () => {
const onFormSubmit = async ({
selectedSourceEnvironment: sce,
secretPath,
targetAppId,
secretPrefix,
secretSuffix,
shouldLabel: sl,
labelName,
labelValue
}: FormData) => {
try {
setIsLoading(true);
@ -66,82 +127,232 @@ export default function GCPSecretManagerCreateIntegrationPage() {
isActive: true,
app: integrationAuthApps?.find((integrationAuthApp) => integrationAuthApp.appId === targetAppId)?.name,
appId: targetAppId,
sourceEnvironment: selectedSourceEnvironment,
secretPath
sourceEnvironment: sce,
secretPath,
metadata: {
secretPrefix,
secretSuffix,
...(sl ? {
secretGCPLabel: {
labelName,
labelValue
}
} : {})
}
});
setIsLoading(false);
router.push(`/integrations/${localStorage.getItem("projectData.id")}`);
} catch (err) {
console.error(err);
setIsLoading(false);
}
};
}
return integrationAuth &&
workspace &&
selectedSourceEnvironment &&
integrationAuthApps &&
targetAppId ? (
<div className="flex h-full w-full items-center justify-center">
integrationAuthApps ? (
<form
onSubmit={handleSubmit(onFormSubmit)}
className="flex h-full w-full items-center justify-center"
>
<Card className="max-w-md rounded-md p-8">
<CardTitle className="text-center">GCP Secret Manager Integration</CardTitle>
<FormControl label="Project Environment" className="mt-4">
<Select
value={selectedSourceEnvironment}
onValueChange={(val) => setSelectedSourceEnvironment(val)}
className="w-full border border-mineshaft-500"
>
{workspace?.environments.map((sourceEnvironment) => (
<SelectItem
value={sourceEnvironment.slug}
key={`source-environment-${sourceEnvironment.slug}`}
>
{sourceEnvironment.name}
</SelectItem>
))}
</Select>
</FormControl>
<FormControl label="Secrets Path">
<Input
value={secretPath}
onChange={(evt) => setSecretPath(evt.target.value)}
placeholder="Provide a path, default is /"
/>
</FormControl>
<FormControl label="GCP Project">
<Select
value={targetAppId}
onValueChange={(val) => setTargetAppId(val)}
className="w-full border border-mineshaft-500"
isDisabled={integrationAuthApps.length === 0}
>
{integrationAuthApps.length > 0 ? (
integrationAuthApps.map((integrationAuthApp) => (
<SelectItem
value={integrationAuthApp.appId as string}
key={`target-app-${integrationAuthApp.appId}`}
<Tabs defaultValue={TabSections.Connection}>
<TabList>
<Tab value={TabSections.Connection}>Connection</Tab>
<Tab value={TabSections.Options}>Options</Tab>
</TabList>
<TabPanel value={TabSections.Connection}>
<motion.div
key="panel-1"
transition={{ duration: 0.15 }}
initial={{ opacity: 0, translateX: 30 }}
animate={{ opacity: 1, translateX: 0 }}
exit={{ opacity: 0, translateX: 30 }}
>
<div>
<Controller
control={control}
name="selectedSourceEnvironment"
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
<FormControl
label="Project Environment"
errorText={error?.message}
isError={Boolean(error)}
>
<Select
defaultValue={field.value}
{...field}
onValueChange={(e) => onChange(e)}
className="w-full"
>
{workspace?.environments.map((sourceEnvironment) => (
<SelectItem
value={sourceEnvironment.slug}
key={`source-environment-${sourceEnvironment.slug}`}
>
{sourceEnvironment.name}
</SelectItem>
))}
</Select>
</FormControl>
)}
/>
<Controller
control={control}
defaultValue=""
name="secretPath"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Secrets Path"
isError={Boolean(error)}
errorText={error?.message}
>
<Input
{...field}
placeholder="/"
/>
</FormControl>
)}
/>
<Controller
control={control}
name="targetAppId"
render={({ field: { onChange, ...field }, fieldState: { error } }) => {
return (
<FormControl
label="GCP Project"
errorText={error?.message}
isError={Boolean(error)}
>
<Select
{...field}
onValueChange={(e) => {
if (e === "") return;
onChange(e)
}}
className="w-full"
>
{integrationAuthApps.length > 0 ? (
integrationAuthApps.map((integrationAuthApp) => (
<SelectItem
value={String(integrationAuthApp.appId as string)}
key={`target-app-${String(integrationAuthApp.appId)}`}
>
{integrationAuthApp.name}
</SelectItem>
))
) : (
<SelectItem value="none" key="target-app-none">
No projects found
</SelectItem>
)}
</Select>
</FormControl>
)
}}
/>
<Button
className="mt-4"
size="sm"
type="submit"
isLoading={isLoading}
>
{integrationAuthApp.name}
</SelectItem>
))
) : (
<SelectItem value="none" key="target-app-none">
No projects found
</SelectItem>
Create Integration
</Button>
</div>
</motion.div>
</TabPanel>
<TabPanel value={TabSections.Options}>
<Controller
control={control}
name="secretPrefix"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Secret Prefix"
isError={Boolean(error)}
errorText={error?.message}
>
<Input
{...field}
placeholder="INFISICAL_"
/>
</FormControl>
)}
/>
<Controller
control={control}
name="secretSuffix"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Secret Suffix"
isError={Boolean(error)}
errorText={error?.message}
>
<Input
{...field}
placeholder="_INFISICAL"
/>
</FormControl>
)}
/>
<div className="mt-8">
<Controller
control={control}
name="shouldLabel"
render={({ field: { onChange, value } }) => (
<Switch
id="label-gcp"
onCheckedChange={(isChecked) => onChange(isChecked)}
isChecked={value}
>
Label in GCP Secret Manager
</Switch>
)}
/>
</div>
{shouldLabel && (
<div className="mt-8">
<Controller
control={control}
name="labelName"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Label Name"
isError={Boolean(error)}
errorText={error?.message}
>
<Input
{...field}
placeholder="managed-by"
/>
</FormControl>
)}
/>
<Controller
control={control}
name="labelValue"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Label Name"
isError={Boolean(error)}
errorText={error?.message}
>
<Input
{...field}
placeholder="infisical"
/>
</FormControl>
)}
/>
</div>
)}
</Select>
</FormControl>
<Button
onClick={handleButtonClick}
color="mineshaft"
className="mt-4"
isLoading={isLoading}
isDisabled={integrationAuthApps.length === 0}
>
Create Integration
</Button>
</TabPanel>
</Tabs>
</Card>
</div>
</form>
) : (
<div />
);

View File

@ -1,146 +0,0 @@
import { useEffect, useState } from "react";
import { useRouter } from "next/router";
import queryString from "query-string";
import {
Button,
Card,
CardTitle,
FormControl,
Input,
Select,
SelectItem
} from "@app/components/v2";
import {
useCreateIntegration
} from "@app/hooks/api";
import { useGetIntegrationAuthApps,useGetIntegrationAuthById } from "@app/hooks/api/integrationAuth";
import { useGetWorkspaceById } from "@app/hooks/api/workspace";
export default function GCPSecretManagerCreateIntegrationPage() {
const router = useRouter();
const { mutateAsync } = useCreateIntegration();
const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]);
const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? "");
const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? "");
const { data: integrationAuthApps } = useGetIntegrationAuthApps({
integrationAuthId: (integrationAuthId as string) ?? ""
});
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState("");
const [targetAppId, setTargetAppId] = useState("");
const [secretPath, setSecretPath] = useState("/");
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
if (workspace) {
setSelectedSourceEnvironment(workspace.environments[0].slug);
}
}, [workspace]);
useEffect(() => {
if (integrationAuthApps) {
if (integrationAuthApps.length > 0) {
setTargetAppId(integrationAuthApps[0].appId as string);
} else {
setTargetAppId("none");
}
}
}, [integrationAuthApps]);
const handleButtonClick = async () => {
try {
setIsLoading(true);
if (!integrationAuth?._id) return;
await mutateAsync({
integrationAuthId: integrationAuth?._id,
isActive: true,
app: integrationAuthApps?.find((integrationAuthApp) => integrationAuthApp.appId === targetAppId)?.name,
appId: targetAppId,
sourceEnvironment: selectedSourceEnvironment,
secretPath
});
setIsLoading(false);
router.push(`/integrations/${localStorage.getItem("projectData.id")}`);
} catch (err) {
console.error(err);
}
};
return integrationAuth &&
workspace &&
selectedSourceEnvironment &&
integrationAuthApps
? (
<div className="flex h-full w-full items-center justify-center">
<Card className="max-w-md rounded-md p-8">
<CardTitle className="text-center">GCP Secret Manager Integration</CardTitle>
<FormControl label="Project Environment" className="mt-4">
<Select
value={selectedSourceEnvironment}
onValueChange={(val) => setSelectedSourceEnvironment(val)}
className="w-full border border-mineshaft-500"
>
{workspace?.environments.map((sourceEnvironment) => (
<SelectItem
value={sourceEnvironment.slug}
key={`source-environment-${sourceEnvironment.slug}`}
>
{sourceEnvironment.name}
</SelectItem>
))}
</Select>
</FormControl>
<FormControl label="Secrets Path">
<Input
value={secretPath}
onChange={(evt) => setSecretPath(evt.target.value)}
placeholder="Provide a path, default is /"
/>
</FormControl>
<FormControl label="GCP Project">
<Select
value={targetAppId}
onValueChange={(val) => setTargetAppId(val)}
className="w-full border border-mineshaft-500"
isDisabled={integrationAuthApps.length === 0}
>
{integrationAuthApps.length > 0 ? (
integrationAuthApps.map((integrationAuthApp) => (
<SelectItem
value={integrationAuthApp.appId as string}
key={`target-app-${integrationAuthApp.appId}`}
>
{integrationAuthApp.name}
</SelectItem>
))
) : (
<SelectItem value="none" key="target-app-none">
No projects found
</SelectItem>
)}
</Select>
</FormControl>
<Button
onClick={handleButtonClick}
color="mineshaft"
className="mt-4"
isLoading={isLoading}
// isDisabled={integrationAuthApps.length === 0}
>
Create Integration
</Button>
</Card>
</div>
) : (
<div />
);
}
GCPSecretManagerCreateIntegrationPage.requireAuth = true;

View File

@ -1,6 +1,10 @@
import { useEffect, useState } from "react";
import { Controller, useForm } from "react-hook-form";
import { useRouter } from "next/router";
import { yupResolver } from "@hookform/resolvers/yup";
import { motion } from "framer-motion";
import queryString from "query-string";
import * as yup from "yup";
import {
useCreateIntegration
@ -13,7 +17,11 @@ import {
FormControl,
Input,
Select,
SelectItem
SelectItem,
Tab,
TabList,
TabPanel,
Tabs
} from "../../../components/v2";
import {
useGetIntegrationAuthApps,
@ -27,8 +35,43 @@ const gitLabEntities = [
{ name: "Group", value: "group" }
];
enum TabSections {
Connection = "connection",
Options = "options"
}
const schema = yup.object({
targetEntity: yup.string().oneOf(gitLabEntities.map(entity => entity.value), "Invalid entity type"),
targetTeamId: yup.string(),
selectedSourceEnvironment: yup.string().required("Source environment is required"),
secretPath: yup.string().required("Secret path is required"),
targetAppId: yup.string().required("GitLab project is required"),
targetEnvironment: yup.string(),
secretPrefix: yup.string(),
secretSuffix: yup.string()
});
type FormData = yup.InferType<typeof schema>;
export default function GitLabCreateIntegrationPage() {
const router = useRouter();
const {
control,
handleSubmit,
setValue,
watch
} = useForm<FormData>({
resolver: yupResolver(schema),
defaultValues: {
targetEntity: "individual",
secretPath: "/"
}
});
const selectedSourceEnvironment = watch("selectedSourceEnvironment");
const targetEntity = watch("targetEntity");
const targetTeamId = watch("targetTeamId");
const { mutateAsync } = useCreateIntegration();
const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]);
@ -36,8 +79,6 @@ export default function GitLabCreateIntegrationPage() {
const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? "");
const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? "");
const [targetTeamId, setTargetTeamId] = useState<string | null>(null);
const { data: integrationAuthApps } = useGetIntegrationAuthApps({
integrationAuthId: (integrationAuthId as string) ?? "",
...(targetTeamId ? { teamId: targetTeamId } : {})
@ -46,26 +87,20 @@ export default function GitLabCreateIntegrationPage() {
(integrationAuthId as string) ?? ""
);
const [targetEntity, setTargetEntity] = useState(gitLabEntities[0].value);
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState("");
const [secretPath, setSecretPath] = useState("/");
const [targetAppId, setTargetAppId] = useState("");
const [targetEnvironment, setTargetEnvironment] = useState("");
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
if (workspace) {
setSelectedSourceEnvironment(workspace.environments[0].slug);
setValue("selectedSourceEnvironment", workspace.environments[0].slug);
}
}, [workspace]);
useEffect(() => {
if (integrationAuthApps) {
if (integrationAuthApps.length > 0) {
setTargetAppId(integrationAuthApps[0].appId as string);
setValue("targetAppId", String(integrationAuthApps[0].appId as string));
} else {
setTargetAppId("none");
setValue("targetAppId", "none");
}
}
}, [integrationAuthApps]);
@ -75,151 +110,283 @@ export default function GitLabCreateIntegrationPage() {
if (integrationAuthTeams) {
if (integrationAuthTeams.length > 0) {
// case: user is part of at least 1 group in GitLab
setTargetTeamId(integrationAuthTeams[0].teamId);
setValue("targetTeamId", String(integrationAuthTeams[0].teamId));
} else {
// case: user is not part of any groups in GitLab
setTargetTeamId("none");
setValue("targetTeamId", "none");
}
}
} else if (targetEntity === "individual") {
setTargetTeamId(null);
setValue("targetTeamId", undefined);
}
}, [targetEntity, integrationAuthTeams]);
const handleButtonClick = async () => {
const onFormSubmit = async ({
selectedSourceEnvironment: sse,
secretPath,
targetAppId,
targetEnvironment,
secretPrefix,
secretSuffix
}: FormData) => {
try {
setIsLoading(true);
if (!integrationAuth?._id) return;
await mutateAsync({
integrationAuthId: integrationAuth?._id,
isActive: true,
app: integrationAuthApps?.find((integrationAuthApp) => integrationAuthApp.appId === targetAppId)?.name,
app: integrationAuthApps?.find((integrationAuthApp) => String(integrationAuthApp.appId) === targetAppId)?.name,
appId: String(targetAppId),
sourceEnvironment: selectedSourceEnvironment,
sourceEnvironment: sse,
targetEnvironment: targetEnvironment === "" ? "*" : targetEnvironment,
secretPath
secretPath,
metadata: {
secretPrefix,
secretSuffix
}
});
setIsLoading(false);
router.push(`/integrations/${localStorage.getItem("projectData.id")}`);
} catch (err) {
console.error(err);
setIsLoading(false);
}
};
}
return integrationAuth &&
workspace &&
selectedSourceEnvironment &&
integrationAuthApps &&
integrationAuthTeams &&
targetAppId ? (
<div className="flex h-full w-full items-center justify-center">
integrationAuthTeams ? (
<form
onSubmit={handleSubmit(onFormSubmit)}
className="flex h-full w-full items-center justify-center"
>
<Card className="max-w-md rounded-md p-8">
<CardTitle className="text-center">GitLab Integration</CardTitle>
<FormControl label="Project Environment">
<Select
value={selectedSourceEnvironment}
onValueChange={(val) => setSelectedSourceEnvironment(val)}
className="w-full border border-mineshaft-500"
>
{workspace?.environments.map((sourceEnvironment) => (
<SelectItem
value={sourceEnvironment.slug}
key={`source-environment-${sourceEnvironment.slug}`}
>
{sourceEnvironment.name}
</SelectItem>
))}
</Select>
</FormControl>
<FormControl label="Secrets Path">
<Input
value={secretPath}
onChange={(evt) => setSecretPath(evt.target.value)}
placeholder="Provide a path, default is /"
/>
</FormControl>
<FormControl label="GitLab Integration Type">
<Select
value={targetEntity}
onValueChange={(val) => setTargetEntity(val)}
className="w-full border border-mineshaft-500"
>
{gitLabEntities.map((entity) => {
return (
<SelectItem value={entity.value} key={`target-entity-${entity.value}`}>
{entity.name}
</SelectItem>
);
})}
</Select>
</FormControl>
{targetEntity === "group" && targetTeamId && (
<FormControl label="GitLab Group">
<Select
value={targetTeamId}
onValueChange={(val) => setTargetTeamId(val)}
className="w-full border border-mineshaft-500"
<Tabs defaultValue={TabSections.Connection}>
<TabList>
<Tab value={TabSections.Connection}>Connection</Tab>
<Tab value={TabSections.Options}>Options</Tab>
</TabList>
<TabPanel value={TabSections.Connection}>
<motion.div
key="panel-1"
transition={{ duration: 0.15 }}
initial={{ opacity: 0, translateX: 30 }}
animate={{ opacity: 1, translateX: 0 }}
exit={{ opacity: 0, translateX: 30 }}
>
{integrationAuthTeams.length > 0 ? (
integrationAuthTeams.map((integrationAuthTeam) => (
<SelectItem
value={integrationAuthTeam.teamId}
key={`target-team-${integrationAuthTeam.teamId}`}
>
{integrationAuthTeam.name}
</SelectItem>
))
) : (
<SelectItem value="none" key="target-team-none">
No groups found
</SelectItem>
)}
</Select>
</FormControl>
)}
<FormControl label="GitLab Project">
<Select
value={targetAppId}
onValueChange={(val) => setTargetAppId(val)}
className="w-full border border-mineshaft-500"
isDisabled={integrationAuthApps.length === 0}
>
{integrationAuthApps.length > 0 ? (
integrationAuthApps.map((integrationAuthApp) => (
<SelectItem
value={integrationAuthApp.appId as string}
key={`target-app-${integrationAuthApp.appId}`}
>
{integrationAuthApp.name}
</SelectItem>
))
) : (
<SelectItem value="none" key="target-app-none">
No projects found
</SelectItem>
)}
</Select>
</FormControl>
<FormControl label="GitLab Environment Scope (Optional)">
<Input
placeholder="*"
value={targetEnvironment}
onChange={(e) => setTargetEnvironment(e.target.value)}
/>
</FormControl>
<Button
onClick={handleButtonClick}
color="mineshaft"
className="mt-4"
isLoading={isLoading}
isDisabled={integrationAuthApps.length === 0}
>
Create Integration
</Button>
<div>
<Controller
control={control}
name="selectedSourceEnvironment"
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
<FormControl
label="Project Environment"
errorText={error?.message}
isError={Boolean(error)}
>
<Select
defaultValue={field.value}
{...field}
onValueChange={(e) => onChange(e)}
className="w-full"
>
{workspace?.environments.map((sourceEnvironment) => (
<SelectItem
value={sourceEnvironment.slug}
key={`source-environment-${sourceEnvironment.slug}`}
>
{sourceEnvironment.name}
</SelectItem>
))}
</Select>
</FormControl>
)}
/>
<Controller
control={control}
defaultValue=""
name="secretPath"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Secrets Path"
isError={Boolean(error)}
errorText={error?.message}
>
<Input
{...field}
placeholder="/"
/>
</FormControl>
)}
/>
<Controller
control={control}
name="targetEntity"
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
<FormControl
label="GitLab Integration Type"
errorText={error?.message}
isError={Boolean(error)}
>
<Select
{...field}
onValueChange={(e) => onChange(e)}
className="w-full"
>
{gitLabEntities.map((entity) => {
return (
<SelectItem value={entity.value} key={`target-entity-${entity.value}`}>
{entity.name}
</SelectItem>
);
})}
</Select>
</FormControl>
)}
/>
{targetEntity === "group" && targetTeamId && (
<Controller
control={control}
name="targetTeamId"
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
<FormControl
label="GitLab Group"
errorText={error?.message}
isError={Boolean(error)}
>
<Select
{...field}
onValueChange={(e) => onChange(e)}
className="w-full"
>
{integrationAuthTeams.length > 0 ? (
integrationAuthTeams.map((integrationAuthTeam) =>
(
<SelectItem
value={String(integrationAuthTeam.teamId as string)}
key={`target-team-${String(integrationAuthTeam.teamId)}`}
>
{integrationAuthTeam.name}
</SelectItem>
))
) : (
<SelectItem value="none" key="target-team-none">
No groups found
</SelectItem>
)}
</Select>
</FormControl>
)}
/>
)}
<Controller
control={control}
name="targetAppId"
render={({ field: { onChange, ...field }, fieldState: { error } }) => {
return (
<FormControl
label="GitLab Project"
errorText={error?.message}
isError={Boolean(error)}
>
<Select
{...field}
onValueChange={(e) => {
if (e === "") return;
onChange(e)
}}
className="w-full"
>
{integrationAuthApps.length > 0 ? (
integrationAuthApps.map((integrationAuthApp) => (
<SelectItem
value={String(integrationAuthApp.appId as string)}
key={`target-app-${String(integrationAuthApp.appId)}`}
>
{integrationAuthApp.name}
</SelectItem>
))
) : (
<SelectItem value="none" key="target-app-none">
No projects found
</SelectItem>
)}
</Select>
</FormControl>
)}}
/>
<Controller
control={control}
defaultValue=""
name="targetEnvironment"
render={({ field, fieldState: { error } }) => (
<FormControl
label="GitLab Environment Scope (Optional)"
isError={Boolean(error)}
errorText={error?.message}
>
<Input
{...field}
placeholder="*"
/>
</FormControl>
)}
/>
</div>
<Button
className="mt-4"
size="sm"
type="submit"
isLoading={isLoading}
>
Create Integration
</Button>
</motion.div>
</TabPanel>
<TabPanel value={TabSections.Options}>
<div>
<Controller
control={control}
name="secretPrefix"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Secret Prefix"
isError={Boolean(error)}
errorText={error?.message}
>
<Input
{...field}
placeholder="INFISICAL_"
/>
</FormControl>
)}
/>
<Controller
control={control}
name="secretSuffix"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Secret Suffix"
isError={Boolean(error)}
errorText={error?.message}
>
<Input
{...field}
placeholder="_INFISICAL"
/>
</FormControl>
)}
/>
</div>
</TabPanel>
</Tabs>
</Card>
</div>
</form>
) : (
<div />
);

View File

@ -1,6 +1,10 @@
@tailwind base;
@tailwind components;
html {
@apply overflow-hidden;
}
.rdp-day,
.rdp-nav_button {
@apply rounded-md hover:text-mineshaft-500;

View File

@ -85,7 +85,13 @@ export const IntegrationsPage = withProjectPermission(
// details: so onsuccessfully deleting an integration auth, immediately integration list is refeteched
// After the refetch is completed check if its empty. Then set bot active and reset the submit hook
useEffect(() => {
if (isDeleteIntegrationAuthSuccess && !isIntegrationFetching && !integrations?.length) {
if (
isDeleteIntegrationAuthSuccess &&
!isIntegrationFetching &&
!isIntegrationAuthLoading &&
!integrations?.length &&
!integrationAuths?.length
) {
if (bot?._id)
updateBotActiveStatusSync({
isActive: false,
@ -94,7 +100,13 @@ export const IntegrationsPage = withProjectPermission(
});
resetDeleteIntegrationAuth();
}
}, [isIntegrationFetching, isDeleteIntegrationAuthSuccess, integrations?.length]);
}, [
isIntegrationFetching,
isDeleteIntegrationAuthSuccess,
isIntegrationAuthLoading,
integrationAuths?.length,
integrations?.length
]);
const handleProviderIntegration = async (provider: string) => {
const selectedCloudIntegration = cloudIntegrations?.find(({ slug }) => provider === slug);

View File

@ -1,4 +1,3 @@
import { useState } from "react";
import { useForm } from "react-hook-form";
import {
faArrowLeft,
@ -80,7 +79,6 @@ const SIMPLE_PERMISSION_OPTIONS = [
] as const;
export const OrgRoleModifySection = ({ role, onGoBack }: Props) => {
const [searchPermission, setSearchPermission] = useState("");
const { subscription } = useSubscription();
const { popUp, handlePopUpToggle, handlePopUpOpen } = usePopUp(["upgradePlan"] as const);
@ -196,14 +194,6 @@ export const OrgRoleModifySection = ({ role, onGoBack }: Props) => {
<div>
<h2 className="text-xl font-medium">Add Permission</h2>
</div>
<div className="flex-1 max-w-md">
<Input
value={searchPermission}
onChange={(e) => setSearchPermission(e.target.value)}
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
placeholder="Search permissions..."
/>
</div>
</div>
<div className="">
<WorkspacePermission

View File

@ -1,6 +1,7 @@
import { useCallback, useMemo, useState } from "react";
import { Controller, useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import Link from "next/link";
import { faMagnifyingGlass, faPlus, faTrash, faUsers } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod";
@ -359,48 +360,57 @@ export const MemberListTab = ({ roles = [], isRolesLoading }: Props) => {
title={t("section.members.add-dialog.add-member-to-project") as string}
subTitle={t("section.members.add-dialog.user-will-email")}
>
<form onSubmit={handleSubmit(onAddMember)}>
<Controller
control={control}
defaultValue={filteredOrgUsers?.[0]?.user?.email}
name="email"
render={({ field, fieldState: { error } }) => (
<FormControl label="Email" isError={Boolean(error)} errorText={error?.message}>
<Select
position="popper"
className="w-full"
defaultValue={filteredOrgUsers?.[0]?.user?.email}
value={field.value}
onValueChange={field.onChange}
>
{filteredOrgUsers.map(({ _id: orgUserId, user: u }) => (
<SelectItem value={u?.email} key={`org-membership-join-${orgUserId}`}>
{u?.email}
</SelectItem>
))}
</Select>
</FormControl>
)}
/>
<div className="mt-8 flex items-center">
<Button
className="mr-4"
size="sm"
type="submit"
isLoading={isSubmitting}
isDisabled={isSubmitting}
>
Add Member
</Button>
<Button
colorSchema="secondary"
variant="plain"
onClick={() => handlePopUpClose("addMember")}
>
Cancel
</Button>
{filteredOrgUsers.length ? (
<form onSubmit={handleSubmit(onAddMember)}>
<Controller
control={control}
defaultValue={filteredOrgUsers?.[0]?.user?.email}
name="email"
render={({ field, fieldState: { error } }) => (
<FormControl label="Email" isError={Boolean(error)} errorText={error?.message}>
<Select
position="popper"
className="w-full"
defaultValue={filteredOrgUsers?.[0]?.user?.email}
value={field.value}
onValueChange={field.onChange}
>
{filteredOrgUsers.map(({ _id: orgUserId, user: u }) => (
<SelectItem value={u?.email} key={`org-membership-join-${orgUserId}`}>
{u?.email}
</SelectItem>
))}
</Select>
</FormControl>
)}
/>
<div className="mt-8 flex items-center">
<Button
className="mr-4"
size="sm"
type="submit"
isLoading={isSubmitting}
isDisabled={isSubmitting}
>
Add Member
</Button>
<Button
colorSchema="secondary"
variant="plain"
onClick={() => handlePopUpClose("addMember")}
>
Cancel
</Button>
</div>
</form>
) : (
<div className="flex flex-col space-y-4">
<div>All the users in your organization are already invited.</div>
<Link href={`/org/${currentWorkspace?.organization}/members`}>
<Button variant="outline_bg">Add users to organization</Button>
</Link>
</div>
</form>
)}
</ModalContent>
</Modal>
<DeleteActionModal

View File

@ -1,14 +1,11 @@
import { useState } from "react";
import { useForm } from "react-hook-form";
import { faElementor } from "@fortawesome/free-brands-svg-icons";
import {
faAnchorLock,
faArrowLeft,
faBook,
faCog,
faKey,
faLock,
faMagnifyingGlass,
faNetworkWired,
faPuzzlePiece,
faTags,
@ -85,12 +82,6 @@ const SINGLE_PERMISSION_LIST = [
icon: faTags,
formName: "tags"
},
{
title: "Audit Logs",
subtitle: "Audit log management control",
icon: faBook,
formName: "audit-logs"
},
{
title: "IP Allowlist",
subtitle: "IP allowlist management control",
@ -105,8 +96,6 @@ type Props = {
};
export const ProjectRoleModifySection = ({ role, onGoBack }: Props) => {
const [searchPermission, setSearchPermission] = useState("");
const { popUp, handlePopUpToggle, handlePopUpOpen } = usePopUp(["upgradePlan"] as const);
const isNonEditable = ["admin", "member", "viewer"].includes(role?.slug || "");
@ -226,14 +215,6 @@ export const ProjectRoleModifySection = ({ role, onGoBack }: Props) => {
<div>
<h2 className="text-xl font-medium">Add Permission</h2>
</div>
<div className="flex-1 max-w-md">
<Input
value={searchPermission}
onChange={(e) => setSearchPermission(e.target.value)}
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
placeholder="Search permissions..."
/>
</div>
</div>
<div>
<MultiEnvProjectPermission

View File

@ -44,6 +44,22 @@ const PERMISSIONS = [
{ action: "delete", label: "Remove" }
] as const;
const MEMBERS_PERMISSIONS = [
{ action: "read", label: "View all members" },
{ action: "create", label: "Invite members" },
{ action: "edit", label: "Edit members" },
{ action: "delete", label: "Remove members" }
] as const;
const getPermissionList = (option: Props["formName"]) => {
switch (option) {
case "member":
return MEMBERS_PERMISSIONS;
default:
return PERMISSIONS;
}
};
export const SingleProjectPermission = ({
isNonEditable,
setValue,
@ -148,7 +164,7 @@ export const SingleProjectPermission = ({
className="overflow-hidden grid gap-8 grid-flow-col auto-cols-min"
>
{isCustom &&
PERMISSIONS.map(({ action, label }) => (
getPermissionList(formName).map(({ action, label }) => (
<Controller
name={`permissions.${formName}.${action}`}
key={`permissions.${formName}.${action}`}

View File

@ -72,6 +72,8 @@ kubectl get secrets -n <namespace> <secret-name> \
| `frontend.image.repository` | Backend image repository | `infisical/frontend` |
| `frontend.image.tag` | Backend image tag | `latest` |
| `frontend.image.pullPolicy` | Backend image pullPolicy | `IfNotPresent` |
| `frontend.resources.limits.memory` | container memory limit [check the offical kubernetes documentations](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) | `100Mi` |
| `frontend.resources.requests.cpu` | container CPU request [check the offical kubernetes documentations](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) | `10m` |
| `frontend.kubeSecretRef` | Backend secret resource reference name (containing required [frontend configuration variables](https://infisical.com/docs/self-hosting/configuration/envars)) | `""` |
| `frontend.service.annotations` | Backend service annotations | `{}` |
| `frontend.service.type` | Backend service type | `ClusterIP` |
@ -91,6 +93,8 @@ kubectl get secrets -n <namespace> <secret-name> \
| `backend.image.repository` | Backend image repository | `infisical/backend` |
| `backend.image.tag` | Backend image tag | `latest` |
| `backend.image.pullPolicy` | Backend image pullPolicy | `IfNotPresent` |
| `backend.resources.limits.memory` | container memory limit [check the offical kubernetes documentations](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) | `200Mi` |
| `backend.resources.requests.cpu` | container CPU request [check the offical kubernetes documentations](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) | `150m` |
| `backend.kubeSecretRef` | Backend secret resource reference name (containing required [backend configuration variables](https://infisical.com/docs/self-hosting/configuration/envars)) | `""` |
| `backend.service.annotations` | Backend service annotations | `{}` |
| `backend.service.type` | Backend service type | `ClusterIP` |

View File

@ -40,7 +40,9 @@ spec:
envFrom:
- secretRef:
name: {{ $backend.kubeSecretRef | default (include "infisical.backend.fullname" .) }}
{{- if $backend.resources }}
resources: {{- toYaml $backend.resources | nindent 12 }}
{{- end }}
---
apiVersion: v1

View File

@ -40,7 +40,9 @@ spec:
name: {{ $frontend.kubeSecretRef | default (include "infisical.frontend.fullname" .) }}
ports:
- containerPort: 3000
{{- if $frontend.resources }}
resources: {{- toYaml $frontend.resources | nindent 12 }}
{{- end }}
---
apiVersion: v1

View File

@ -43,6 +43,14 @@ frontend:
## @param frontend.image.pullPolicy Backend image pullPolicy
##
pullPolicy: IfNotPresent
## @param frontend.resources.limits.memory container memory limit [check the offical kubernetes documentations](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/)
## @param frontend.resources.requests.cpu container CPU request [check the offical kubernetes documentations](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/)
##
resources:
limits:
memory: 100Mi
requests:
cpu: 10m
## @param frontend.kubeSecretRef Backend secret resource reference name (containing required [frontend configuration variables](https://infisical.com/docs/self-hosting/configuration/envars))
##
kubeSecretRef: ""
@ -102,6 +110,14 @@ backend:
## @param backend.image.pullPolicy Backend image pullPolicy
##
pullPolicy: IfNotPresent
## @param backend.resources.limits.memory container memory limit [check the offical kubernetes documentations](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/)
## @param backend.resources.requests.cpu container CPU request [check the offical kubernetes documentations](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/)
##
resources:
limits:
memory: 200Mi
requests:
cpu: 150m
## @param backend.kubeSecretRef Backend secret resource reference name (containing required [backend configuration variables](https://infisical.com/docs/self-hosting/configuration/envars))
##
kubeSecretRef: ""