mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-08 12:28:54 +00:00
Merge remote-tracking branch 'origin' into ssh-telemetry
This commit is contained in:
@@ -0,0 +1,27 @@
|
|||||||
|
name: Release K8 Operator Helm Chart
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release-helm:
|
||||||
|
name: Release Helm Chart
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Install Helm
|
||||||
|
uses: azure/setup-helm@v3
|
||||||
|
with:
|
||||||
|
version: v3.10.0
|
||||||
|
|
||||||
|
- name: Install python
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
|
||||||
|
- name: Install Cloudsmith CLI
|
||||||
|
run: pip install --upgrade cloudsmith-cli
|
||||||
|
|
||||||
|
- name: Build and push helm package to CloudSmith
|
||||||
|
run: cd helm-charts && sh upload-k8s-operator-cloudsmith.sh
|
||||||
|
env:
|
||||||
|
CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }}
|
||||||
@@ -1,52 +1,103 @@
|
|||||||
name: Release image + Helm chart K8s Operator
|
name: Release K8 Operator Docker Image
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- "infisical-k8-operator/v*.*.*"
|
- "infisical-k8-operator/v*.*.*"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release-image:
|
||||||
runs-on: ubuntu-latest
|
name: Generate Helm Chart PR
|
||||||
steps:
|
runs-on: ubuntu-latest
|
||||||
- name: Extract version from tag
|
outputs:
|
||||||
id: extract_version
|
pr_number: ${{ steps.create-pr.outputs.pull-request-number }}
|
||||||
run: echo "::set-output name=version::${GITHUB_REF_NAME#infisical-k8-operator/}"
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- name: Extract version from tag
|
||||||
|
id: extract_version
|
||||||
|
run: echo "::set-output name=version::${GITHUB_REF_NAME#infisical-k8-operator/}"
|
||||||
|
|
||||||
- name: 🔧 Set up QEMU
|
- name: Checkout code
|
||||||
uses: docker/setup-qemu-action@v1
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: 🔧 Set up Docker Buildx
|
# Dependency for helm generation
|
||||||
uses: docker/setup-buildx-action@v1
|
- name: Install Helm
|
||||||
|
uses: azure/setup-helm@v3
|
||||||
|
with:
|
||||||
|
version: v3.10.0
|
||||||
|
|
||||||
- name: 🐋 Login to Docker Hub
|
# Dependency for helm generation
|
||||||
uses: docker/login-action@v1
|
- name: Install Go
|
||||||
with:
|
uses: actions/setup-go@v4
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
with:
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
go-version: 1.21
|
||||||
|
|
||||||
- name: Build and push
|
# Install binaries for helm generation
|
||||||
id: docker_build
|
- name: Install dependencies
|
||||||
uses: docker/build-push-action@v2
|
working-directory: k8-operator
|
||||||
with:
|
run: |
|
||||||
context: k8-operator
|
make helmify
|
||||||
push: true
|
make kustomize
|
||||||
platforms: linux/amd64,linux/arm64
|
make controller-gen
|
||||||
tags: |
|
|
||||||
infisical/kubernetes-operator:latest
|
|
||||||
infisical/kubernetes-operator:${{ steps.extract_version.outputs.version }}
|
|
||||||
|
|
||||||
- name: Checkout
|
- name: Generate Helm Chart
|
||||||
uses: actions/checkout@v2
|
working-directory: k8-operator
|
||||||
- name: Install Helm
|
run: make helm
|
||||||
uses: azure/setup-helm@v3
|
|
||||||
with:
|
- name: Update Helm Chart Version
|
||||||
version: v3.10.0
|
run: ./k8-operator/scripts/update-version.sh ${{ steps.extract_version.outputs.version }}
|
||||||
- name: Install python
|
|
||||||
uses: actions/setup-python@v4
|
- name: Debug - Check file changes
|
||||||
- name: Install Cloudsmith CLI
|
run: |
|
||||||
run: pip install --upgrade cloudsmith-cli
|
echo "Current git status:"
|
||||||
- name: Build and push helm package to Cloudsmith
|
git status
|
||||||
run: cd helm-charts && sh upload-k8s-operator-cloudsmith.sh
|
echo ""
|
||||||
env:
|
echo "Modified files:"
|
||||||
CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }}
|
git diff --name-only
|
||||||
|
|
||||||
|
# If there is no diff, exit with error. Version should always be changed, so if there is no diff, something is wrong and we should exit.
|
||||||
|
if [ -z "$(git diff --name-only)" ]; then
|
||||||
|
echo "No helm changes or version changes. Invalid release detected, Exiting."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Create Helm Chart PR
|
||||||
|
id: create-pr
|
||||||
|
uses: peter-evans/create-pull-request@v5
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
commit-message: "Update Helm chart to version ${{ steps.extract_version.outputs.version }}"
|
||||||
|
committer: GitHub <[email protected]>
|
||||||
|
author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>
|
||||||
|
branch: helm-update-${{ steps.extract_version.outputs.version }}
|
||||||
|
delete-branch: true
|
||||||
|
title: "Update Helm chart to version ${{ steps.extract_version.outputs.version }}"
|
||||||
|
body: |
|
||||||
|
This PR updates the Helm chart to version `${{ steps.extract_version.outputs.version }}`.
|
||||||
|
Additionally the helm chart has been updated to match the latest operator code changes.
|
||||||
|
|
||||||
|
Associated Release Workflow: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||||
|
|
||||||
|
Once you have approved this PR, you can trigger the helm release workflow manually.
|
||||||
|
base: main
|
||||||
|
|
||||||
|
- name: 🔧 Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v1
|
||||||
|
|
||||||
|
- name: 🔧 Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v1
|
||||||
|
|
||||||
|
- name: 🐋 Login to Docker Hub
|
||||||
|
uses: docker/login-action@v1
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build and push
|
||||||
|
id: docker_build
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
with:
|
||||||
|
context: k8-operator
|
||||||
|
push: true
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
tags: |
|
||||||
|
infisical/kubernetes-operator:latest
|
||||||
|
infisical/kubernetes-operator:${{ steps.extract_version.outputs.version }}
|
||||||
|
|||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas/models";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (!(await knex.schema.hasColumn(TableName.SecretApprovalPolicy, "allowedSelfApprovals"))) {
|
||||||
|
await knex.schema.alterTable(TableName.SecretApprovalPolicy, (t) => {
|
||||||
|
t.boolean("allowedSelfApprovals").notNullable().defaultTo(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!(await knex.schema.hasColumn(TableName.AccessApprovalPolicy, "allowedSelfApprovals"))) {
|
||||||
|
await knex.schema.alterTable(TableName.AccessApprovalPolicy, (t) => {
|
||||||
|
t.boolean("allowedSelfApprovals").notNullable().defaultTo(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasColumn(TableName.SecretApprovalPolicy, "allowedSelfApprovals")) {
|
||||||
|
await knex.schema.alterTable(TableName.SecretApprovalPolicy, (t) => {
|
||||||
|
t.dropColumn("allowedSelfApprovals");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (await knex.schema.hasColumn(TableName.AccessApprovalPolicy, "allowedSelfApprovals")) {
|
||||||
|
await knex.schema.alterTable(TableName.AccessApprovalPolicy, (t) => {
|
||||||
|
t.dropColumn("allowedSelfApprovals");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,7 +16,8 @@ export const AccessApprovalPoliciesSchema = z.object({
|
|||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
enforcementLevel: z.string().default("hard"),
|
enforcementLevel: z.string().default("hard"),
|
||||||
deletedAt: z.date().nullable().optional()
|
deletedAt: z.date().nullable().optional(),
|
||||||
|
allowedSelfApprovals: z.boolean().default(true)
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TAccessApprovalPolicies = z.infer<typeof AccessApprovalPoliciesSchema>;
|
export type TAccessApprovalPolicies = z.infer<typeof AccessApprovalPoliciesSchema>;
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ export const SecretApprovalPoliciesSchema = z.object({
|
|||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
enforcementLevel: z.string().default("hard"),
|
enforcementLevel: z.string().default("hard"),
|
||||||
deletedAt: z.date().nullable().optional()
|
deletedAt: z.date().nullable().optional(),
|
||||||
|
allowedSelfApprovals: z.boolean().default(true)
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TSecretApprovalPolicies = z.infer<typeof SecretApprovalPoliciesSchema>;
|
export type TSecretApprovalPolicies = z.infer<typeof SecretApprovalPoliciesSchema>;
|
||||||
|
|||||||
@@ -29,7 +29,8 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
.array()
|
.array()
|
||||||
.min(1, { message: "At least one approver should be provided" }),
|
.min(1, { message: "At least one approver should be provided" }),
|
||||||
approvals: z.number().min(1).default(1),
|
approvals: z.number().min(1).default(1),
|
||||||
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard)
|
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard),
|
||||||
|
allowedSelfApprovals: z.boolean().default(true)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@@ -147,7 +148,8 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
.array()
|
.array()
|
||||||
.min(1, { message: "At least one approver should be provided" }),
|
.min(1, { message: "At least one approver should be provided" }),
|
||||||
approvals: z.number().min(1).optional(),
|
approvals: z.number().min(1).optional(),
|
||||||
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard)
|
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard),
|
||||||
|
allowedSelfApprovals: z.boolean().default(true)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
|
|||||||
@@ -110,7 +110,8 @@ export const registerAccessApprovalRequestRouter = async (server: FastifyZodProv
|
|||||||
secretPath: z.string().nullish(),
|
secretPath: z.string().nullish(),
|
||||||
envId: z.string(),
|
envId: z.string(),
|
||||||
enforcementLevel: z.string(),
|
enforcementLevel: z.string(),
|
||||||
deletedAt: z.date().nullish()
|
deletedAt: z.date().nullish(),
|
||||||
|
allowedSelfApprovals: z.boolean()
|
||||||
}),
|
}),
|
||||||
reviewers: z
|
reviewers: z
|
||||||
.object({
|
.object({
|
||||||
|
|||||||
@@ -35,7 +35,8 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
.array()
|
.array()
|
||||||
.min(1, { message: "At least one approver should be provided" }),
|
.min(1, { message: "At least one approver should be provided" }),
|
||||||
approvals: z.number().min(1).default(1),
|
approvals: z.number().min(1).default(1),
|
||||||
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard)
|
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard),
|
||||||
|
allowedSelfApprovals: z.boolean().default(true)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@@ -85,7 +86,8 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
.nullable()
|
.nullable()
|
||||||
.transform((val) => (val ? removeTrailingSlash(val) : val))
|
.transform((val) => (val ? removeTrailingSlash(val) : val))
|
||||||
.transform((val) => (val === "" ? "/" : val)),
|
.transform((val) => (val === "" ? "/" : val)),
|
||||||
enforcementLevel: z.nativeEnum(EnforcementLevel).optional()
|
enforcementLevel: z.nativeEnum(EnforcementLevel).optional(),
|
||||||
|
allowedSelfApprovals: z.boolean().default(true)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
|
|||||||
@@ -49,7 +49,8 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
|||||||
.array(),
|
.array(),
|
||||||
secretPath: z.string().optional().nullable(),
|
secretPath: z.string().optional().nullable(),
|
||||||
enforcementLevel: z.string(),
|
enforcementLevel: z.string(),
|
||||||
deletedAt: z.date().nullish()
|
deletedAt: z.date().nullish(),
|
||||||
|
allowedSelfApprovals: z.boolean()
|
||||||
}),
|
}),
|
||||||
committerUser: approvalRequestUser,
|
committerUser: approvalRequestUser,
|
||||||
commits: z.object({ op: z.string(), secretId: z.string().nullable().optional() }).array(),
|
commits: z.object({ op: z.string(), secretId: z.string().nullable().optional() }).array(),
|
||||||
@@ -267,7 +268,8 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
|||||||
approvers: approvalRequestUser.array(),
|
approvers: approvalRequestUser.array(),
|
||||||
secretPath: z.string().optional().nullable(),
|
secretPath: z.string().optional().nullable(),
|
||||||
enforcementLevel: z.string(),
|
enforcementLevel: z.string(),
|
||||||
deletedAt: z.date().nullish()
|
deletedAt: z.date().nullish(),
|
||||||
|
allowedSelfApprovals: z.boolean()
|
||||||
}),
|
}),
|
||||||
environment: z.string(),
|
environment: z.string(),
|
||||||
statusChangedByUser: approvalRequestUser.optional(),
|
statusChangedByUser: approvalRequestUser.optional(),
|
||||||
|
|||||||
@@ -65,7 +65,8 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
approvers,
|
approvers,
|
||||||
projectSlug,
|
projectSlug,
|
||||||
environment,
|
environment,
|
||||||
enforcementLevel
|
enforcementLevel,
|
||||||
|
allowedSelfApprovals
|
||||||
}: TCreateAccessApprovalPolicy) => {
|
}: TCreateAccessApprovalPolicy) => {
|
||||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||||
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
||||||
@@ -153,7 +154,8 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
approvals,
|
approvals,
|
||||||
secretPath,
|
secretPath,
|
||||||
name,
|
name,
|
||||||
enforcementLevel
|
enforcementLevel,
|
||||||
|
allowedSelfApprovals
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
@@ -216,7 +218,8 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
actorOrgId,
|
actorOrgId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
approvals,
|
approvals,
|
||||||
enforcementLevel
|
enforcementLevel,
|
||||||
|
allowedSelfApprovals
|
||||||
}: TUpdateAccessApprovalPolicy) => {
|
}: TUpdateAccessApprovalPolicy) => {
|
||||||
const groupApprovers = approvers
|
const groupApprovers = approvers
|
||||||
.filter((approver) => approver.type === ApproverType.Group)
|
.filter((approver) => approver.type === ApproverType.Group)
|
||||||
@@ -262,7 +265,8 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
approvals,
|
approvals,
|
||||||
secretPath,
|
secretPath,
|
||||||
name,
|
name,
|
||||||
enforcementLevel
|
enforcementLevel,
|
||||||
|
allowedSelfApprovals
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export type TCreateAccessApprovalPolicy = {
|
|||||||
projectSlug: string;
|
projectSlug: string;
|
||||||
name: string;
|
name: string;
|
||||||
enforcementLevel: EnforcementLevel;
|
enforcementLevel: EnforcementLevel;
|
||||||
|
allowedSelfApprovals: boolean;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TUpdateAccessApprovalPolicy = {
|
export type TUpdateAccessApprovalPolicy = {
|
||||||
@@ -35,6 +36,7 @@ export type TUpdateAccessApprovalPolicy = {
|
|||||||
secretPath?: string;
|
secretPath?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
enforcementLevel?: EnforcementLevel;
|
enforcementLevel?: EnforcementLevel;
|
||||||
|
allowedSelfApprovals: boolean;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TDeleteAccessApprovalPolicy = {
|
export type TDeleteAccessApprovalPolicy = {
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("approvals").withSchema(TableName.AccessApprovalPolicy).as("policyApprovals"),
|
db.ref("approvals").withSchema(TableName.AccessApprovalPolicy).as("policyApprovals"),
|
||||||
db.ref("secretPath").withSchema(TableName.AccessApprovalPolicy).as("policySecretPath"),
|
db.ref("secretPath").withSchema(TableName.AccessApprovalPolicy).as("policySecretPath"),
|
||||||
db.ref("enforcementLevel").withSchema(TableName.AccessApprovalPolicy).as("policyEnforcementLevel"),
|
db.ref("enforcementLevel").withSchema(TableName.AccessApprovalPolicy).as("policyEnforcementLevel"),
|
||||||
|
db.ref("allowedSelfApprovals").withSchema(TableName.AccessApprovalPolicy).as("policyAllowedSelfApprovals"),
|
||||||
db.ref("envId").withSchema(TableName.AccessApprovalPolicy).as("policyEnvId"),
|
db.ref("envId").withSchema(TableName.AccessApprovalPolicy).as("policyEnvId"),
|
||||||
db.ref("deletedAt").withSchema(TableName.AccessApprovalPolicy).as("policyDeletedAt")
|
db.ref("deletedAt").withSchema(TableName.AccessApprovalPolicy).as("policyDeletedAt")
|
||||||
)
|
)
|
||||||
@@ -119,6 +120,7 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
approvals: doc.policyApprovals,
|
approvals: doc.policyApprovals,
|
||||||
secretPath: doc.policySecretPath,
|
secretPath: doc.policySecretPath,
|
||||||
enforcementLevel: doc.policyEnforcementLevel,
|
enforcementLevel: doc.policyEnforcementLevel,
|
||||||
|
allowedSelfApprovals: doc.policyAllowedSelfApprovals,
|
||||||
envId: doc.policyEnvId,
|
envId: doc.policyEnvId,
|
||||||
deletedAt: doc.policyDeletedAt
|
deletedAt: doc.policyDeletedAt
|
||||||
},
|
},
|
||||||
@@ -254,6 +256,7 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
tx.ref("slug").withSchema(TableName.Environment).as("environment"),
|
tx.ref("slug").withSchema(TableName.Environment).as("environment"),
|
||||||
tx.ref("secretPath").withSchema(TableName.AccessApprovalPolicy).as("policySecretPath"),
|
tx.ref("secretPath").withSchema(TableName.AccessApprovalPolicy).as("policySecretPath"),
|
||||||
tx.ref("enforcementLevel").withSchema(TableName.AccessApprovalPolicy).as("policyEnforcementLevel"),
|
tx.ref("enforcementLevel").withSchema(TableName.AccessApprovalPolicy).as("policyEnforcementLevel"),
|
||||||
|
tx.ref("allowedSelfApprovals").withSchema(TableName.AccessApprovalPolicy).as("policyAllowedSelfApprovals"),
|
||||||
tx.ref("approvals").withSchema(TableName.AccessApprovalPolicy).as("policyApprovals"),
|
tx.ref("approvals").withSchema(TableName.AccessApprovalPolicy).as("policyApprovals"),
|
||||||
tx.ref("deletedAt").withSchema(TableName.AccessApprovalPolicy).as("policyDeletedAt")
|
tx.ref("deletedAt").withSchema(TableName.AccessApprovalPolicy).as("policyDeletedAt")
|
||||||
);
|
);
|
||||||
@@ -275,6 +278,7 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
approvals: el.policyApprovals,
|
approvals: el.policyApprovals,
|
||||||
secretPath: el.policySecretPath,
|
secretPath: el.policySecretPath,
|
||||||
enforcementLevel: el.policyEnforcementLevel,
|
enforcementLevel: el.policyEnforcementLevel,
|
||||||
|
allowedSelfApprovals: el.policyAllowedSelfApprovals,
|
||||||
deletedAt: el.policyDeletedAt
|
deletedAt: el.policyDeletedAt
|
||||||
},
|
},
|
||||||
requestedByUser: {
|
requestedByUser: {
|
||||||
|
|||||||
@@ -320,6 +320,11 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
message: "The policy associated with this access request has been deleted."
|
message: "The policy associated with this access request has been deleted."
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (!policy.allowedSelfApprovals && actorId === accessApprovalRequest.requestedByUserId) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Failed to review access approval request. Users are not authorized to review their own request."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const { membership, hasRole } = await permissionService.getProjectPermission({
|
const { membership, hasRole } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
export const BillingPlanRows = {
|
||||||
|
MemberLimit: { name: "Organization member limit", field: "memberLimit" },
|
||||||
|
IdentityLimit: { name: "Organization identity limit", field: "identityLimit" },
|
||||||
|
WorkspaceLimit: { name: "Project limit", field: "workspaceLimit" },
|
||||||
|
EnvironmentLimit: { name: "Environment limit", field: "environmentLimit" },
|
||||||
|
SecretVersioning: { name: "Secret versioning", field: "secretVersioning" },
|
||||||
|
PitRecovery: { name: "Point in time recovery", field: "pitRecovery" },
|
||||||
|
Rbac: { name: "RBAC", field: "rbac" },
|
||||||
|
CustomRateLimits: { name: "Custom rate limits", field: "customRateLimits" },
|
||||||
|
CustomAlerts: { name: "Custom alerts", field: "customAlerts" },
|
||||||
|
AuditLogs: { name: "Audit logs", field: "auditLogs" },
|
||||||
|
SamlSSO: { name: "SAML SSO", field: "samlSSO" },
|
||||||
|
Hsm: { name: "Hardware Security Module (HSM)", field: "hsm" },
|
||||||
|
OidcSSO: { name: "OIDC SSO", field: "oidcSSO" },
|
||||||
|
SecretApproval: { name: "Secret approvals", field: "secretApproval" },
|
||||||
|
SecretRotation: { name: "Secret rotation", field: "secretRotation" },
|
||||||
|
InstanceUserManagement: { name: "Instance User Management", field: "instanceUserManagement" },
|
||||||
|
ExternalKms: { name: "External KMS", field: "externalKms" }
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const BillingPlanTableHead = {
|
||||||
|
Allowed: { name: "Allowed" },
|
||||||
|
Used: { name: "Used" }
|
||||||
|
} as const;
|
||||||
@@ -12,10 +12,13 @@ import { getConfig } from "@app/lib/config/env";
|
|||||||
import { verifyOfflineLicense } from "@app/lib/crypto";
|
import { verifyOfflineLicense } from "@app/lib/crypto";
|
||||||
import { NotFoundError } from "@app/lib/errors";
|
import { NotFoundError } from "@app/lib/errors";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
|
import { TIdentityOrgDALFactory } from "@app/services/identity/identity-org-dal";
|
||||||
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
||||||
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
|
|
||||||
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
|
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
|
||||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||||
|
import { BillingPlanRows, BillingPlanTableHead } from "./licence-enums";
|
||||||
import { TLicenseDALFactory } from "./license-dal";
|
import { TLicenseDALFactory } from "./license-dal";
|
||||||
import { getDefaultOnPremFeatures, setupLicenseRequestWithStore } from "./license-fns";
|
import { getDefaultOnPremFeatures, setupLicenseRequestWithStore } from "./license-fns";
|
||||||
import {
|
import {
|
||||||
@@ -28,6 +31,7 @@ import {
|
|||||||
TFeatureSet,
|
TFeatureSet,
|
||||||
TGetOrgBillInfoDTO,
|
TGetOrgBillInfoDTO,
|
||||||
TGetOrgTaxIdDTO,
|
TGetOrgTaxIdDTO,
|
||||||
|
TOfflineLicense,
|
||||||
TOfflineLicenseContents,
|
TOfflineLicenseContents,
|
||||||
TOrgInvoiceDTO,
|
TOrgInvoiceDTO,
|
||||||
TOrgLicensesDTO,
|
TOrgLicensesDTO,
|
||||||
@@ -39,10 +43,12 @@ import {
|
|||||||
} from "./license-types";
|
} from "./license-types";
|
||||||
|
|
||||||
type TLicenseServiceFactoryDep = {
|
type TLicenseServiceFactoryDep = {
|
||||||
orgDAL: Pick<TOrgDALFactory, "findOrgById">;
|
orgDAL: Pick<TOrgDALFactory, "findOrgById" | "countAllOrgMembers">;
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||||
licenseDAL: TLicenseDALFactory;
|
licenseDAL: TLicenseDALFactory;
|
||||||
keyStore: Pick<TKeyStoreFactory, "setItemWithExpiry" | "getItem" | "deleteItem">;
|
keyStore: Pick<TKeyStoreFactory, "setItemWithExpiry" | "getItem" | "deleteItem">;
|
||||||
|
identityOrgMembershipDAL: TIdentityOrgDALFactory;
|
||||||
|
projectDAL: TProjectDALFactory;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TLicenseServiceFactory = ReturnType<typeof licenseServiceFactory>;
|
export type TLicenseServiceFactory = ReturnType<typeof licenseServiceFactory>;
|
||||||
@@ -57,11 +63,14 @@ export const licenseServiceFactory = ({
|
|||||||
orgDAL,
|
orgDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
licenseDAL,
|
licenseDAL,
|
||||||
keyStore
|
keyStore,
|
||||||
|
identityOrgMembershipDAL,
|
||||||
|
projectDAL
|
||||||
}: TLicenseServiceFactoryDep) => {
|
}: TLicenseServiceFactoryDep) => {
|
||||||
let isValidLicense = false;
|
let isValidLicense = false;
|
||||||
let instanceType = InstanceType.OnPrem;
|
let instanceType = InstanceType.OnPrem;
|
||||||
let onPremFeatures: TFeatureSet = getDefaultOnPremFeatures();
|
let onPremFeatures: TFeatureSet = getDefaultOnPremFeatures();
|
||||||
|
let selfHostedLicense: TOfflineLicense | null = null;
|
||||||
|
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
const licenseServerCloudApi = setupLicenseRequestWithStore(
|
const licenseServerCloudApi = setupLicenseRequestWithStore(
|
||||||
@@ -125,6 +134,7 @@ export const licenseServiceFactory = ({
|
|||||||
instanceType = InstanceType.EnterpriseOnPremOffline;
|
instanceType = InstanceType.EnterpriseOnPremOffline;
|
||||||
logger.info(`Instance type: ${InstanceType.EnterpriseOnPremOffline}`);
|
logger.info(`Instance type: ${InstanceType.EnterpriseOnPremOffline}`);
|
||||||
isValidLicense = true;
|
isValidLicense = true;
|
||||||
|
selfHostedLicense = contents.license;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -348,10 +358,21 @@ export const licenseServiceFactory = ({
|
|||||||
message: `Organization with ID '${orgId}' not found`
|
message: `Organization with ID '${orgId}' not found`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const { data } = await licenseServerCloudApi.request.get(
|
if (instanceType !== InstanceType.OnPrem && instanceType !== InstanceType.EnterpriseOnPremOffline) {
|
||||||
`/api/license-server/v1/customers/${organization.customerId}/cloud-plan/billing`
|
const { data } = await licenseServerCloudApi.request.get(
|
||||||
);
|
`/api/license-server/v1/customers/${organization.customerId}/cloud-plan/billing`
|
||||||
return data;
|
);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
currentPeriodStart: selfHostedLicense?.issuedAt ? Date.parse(selfHostedLicense?.issuedAt) / 1000 : undefined,
|
||||||
|
currentPeriodEnd: selfHostedLicense?.expiresAt ? Date.parse(selfHostedLicense?.expiresAt) / 1000 : undefined,
|
||||||
|
interval: "month",
|
||||||
|
intervalCount: 1,
|
||||||
|
amount: 0,
|
||||||
|
quantity: 1
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// returns org current plan feature table
|
// returns org current plan feature table
|
||||||
@@ -365,10 +386,41 @@ export const licenseServiceFactory = ({
|
|||||||
message: `Organization with ID '${orgId}' not found`
|
message: `Organization with ID '${orgId}' not found`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const { data } = await licenseServerCloudApi.request.get(
|
if (instanceType !== InstanceType.OnPrem && instanceType !== InstanceType.EnterpriseOnPremOffline) {
|
||||||
`/api/license-server/v1/customers/${organization.customerId}/cloud-plan/table`
|
const { data } = await licenseServerCloudApi.request.get(
|
||||||
|
`/api/license-server/v1/customers/${organization.customerId}/cloud-plan/table`
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mappedRows = await Promise.all(
|
||||||
|
Object.values(BillingPlanRows).map(async ({ name, field }: { name: string; field: string }) => {
|
||||||
|
const allowed = onPremFeatures[field as keyof TFeatureSet];
|
||||||
|
let used = "-";
|
||||||
|
|
||||||
|
if (field === BillingPlanRows.MemberLimit.field) {
|
||||||
|
const orgMemberships = await orgDAL.countAllOrgMembers(orgId);
|
||||||
|
used = orgMemberships.toString();
|
||||||
|
} else if (field === BillingPlanRows.WorkspaceLimit.field) {
|
||||||
|
const projects = await projectDAL.find({ orgId });
|
||||||
|
used = projects.length.toString();
|
||||||
|
} else if (field === BillingPlanRows.IdentityLimit.field) {
|
||||||
|
const identities = await identityOrgMembershipDAL.countAllOrgIdentities({ orgId });
|
||||||
|
used = identities.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
allowed,
|
||||||
|
used
|
||||||
|
};
|
||||||
|
})
|
||||||
);
|
);
|
||||||
return data;
|
|
||||||
|
return {
|
||||||
|
head: Object.values(BillingPlanTableHead),
|
||||||
|
rows: mappedRows
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const getOrgBillingDetails = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgBillInfoDTO) => {
|
const getOrgBillingDetails = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgBillInfoDTO) => {
|
||||||
|
|||||||
@@ -62,7 +62,8 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
projectId,
|
projectId,
|
||||||
secretPath,
|
secretPath,
|
||||||
environment,
|
environment,
|
||||||
enforcementLevel
|
enforcementLevel,
|
||||||
|
allowedSelfApprovals
|
||||||
}: TCreateSapDTO) => {
|
}: TCreateSapDTO) => {
|
||||||
const groupApprovers = approvers
|
const groupApprovers = approvers
|
||||||
?.filter((approver) => approver.type === ApproverType.Group)
|
?.filter((approver) => approver.type === ApproverType.Group)
|
||||||
@@ -113,7 +114,8 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
approvals,
|
approvals,
|
||||||
secretPath,
|
secretPath,
|
||||||
name,
|
name,
|
||||||
enforcementLevel
|
enforcementLevel,
|
||||||
|
allowedSelfApprovals
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
@@ -172,7 +174,8 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
approvals,
|
approvals,
|
||||||
secretPolicyId,
|
secretPolicyId,
|
||||||
enforcementLevel
|
enforcementLevel,
|
||||||
|
allowedSelfApprovals
|
||||||
}: TUpdateSapDTO) => {
|
}: TUpdateSapDTO) => {
|
||||||
const groupApprovers = approvers
|
const groupApprovers = approvers
|
||||||
?.filter((approver) => approver.type === ApproverType.Group)
|
?.filter((approver) => approver.type === ApproverType.Group)
|
||||||
@@ -218,7 +221,8 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
approvals,
|
approvals,
|
||||||
secretPath,
|
secretPath,
|
||||||
name,
|
name,
|
||||||
enforcementLevel
|
enforcementLevel,
|
||||||
|
allowedSelfApprovals
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export type TCreateSapDTO = {
|
|||||||
projectId: string;
|
projectId: string;
|
||||||
name: string;
|
name: string;
|
||||||
enforcementLevel: EnforcementLevel;
|
enforcementLevel: EnforcementLevel;
|
||||||
|
allowedSelfApprovals: boolean;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TUpdateSapDTO = {
|
export type TUpdateSapDTO = {
|
||||||
@@ -19,6 +20,7 @@ export type TUpdateSapDTO = {
|
|||||||
approvers: ({ type: ApproverType.Group; id: string } | { type: ApproverType.User; id?: string; name?: string })[];
|
approvers: ({ type: ApproverType.Group; id: string } | { type: ApproverType.User; id?: string; name?: string })[];
|
||||||
name?: string;
|
name?: string;
|
||||||
enforcementLevel?: EnforcementLevel;
|
enforcementLevel?: EnforcementLevel;
|
||||||
|
allowedSelfApprovals?: boolean;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TDeleteSapDTO = {
|
export type TDeleteSapDTO = {
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
tx.ref("secretPath").withSchema(TableName.SecretApprovalPolicy).as("policySecretPath"),
|
tx.ref("secretPath").withSchema(TableName.SecretApprovalPolicy).as("policySecretPath"),
|
||||||
tx.ref("envId").withSchema(TableName.SecretApprovalPolicy).as("policyEnvId"),
|
tx.ref("envId").withSchema(TableName.SecretApprovalPolicy).as("policyEnvId"),
|
||||||
tx.ref("enforcementLevel").withSchema(TableName.SecretApprovalPolicy).as("policyEnforcementLevel"),
|
tx.ref("enforcementLevel").withSchema(TableName.SecretApprovalPolicy).as("policyEnforcementLevel"),
|
||||||
|
tx.ref("allowedSelfApprovals").withSchema(TableName.SecretApprovalPolicy).as("policyAllowedSelfApprovals"),
|
||||||
tx.ref("approvals").withSchema(TableName.SecretApprovalPolicy).as("policyApprovals"),
|
tx.ref("approvals").withSchema(TableName.SecretApprovalPolicy).as("policyApprovals"),
|
||||||
tx.ref("deletedAt").withSchema(TableName.SecretApprovalPolicy).as("policyDeletedAt")
|
tx.ref("deletedAt").withSchema(TableName.SecretApprovalPolicy).as("policyDeletedAt")
|
||||||
);
|
);
|
||||||
@@ -150,7 +151,8 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
secretPath: el.policySecretPath,
|
secretPath: el.policySecretPath,
|
||||||
enforcementLevel: el.policyEnforcementLevel,
|
enforcementLevel: el.policyEnforcementLevel,
|
||||||
envId: el.policyEnvId,
|
envId: el.policyEnvId,
|
||||||
deletedAt: el.policyDeletedAt
|
deletedAt: el.policyDeletedAt,
|
||||||
|
allowedSelfApprovals: el.policyAllowedSelfApprovals
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
childrenMapper: [
|
childrenMapper: [
|
||||||
@@ -336,6 +338,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
),
|
),
|
||||||
db.ref("secretPath").withSchema(TableName.SecretApprovalPolicy).as("policySecretPath"),
|
db.ref("secretPath").withSchema(TableName.SecretApprovalPolicy).as("policySecretPath"),
|
||||||
db.ref("enforcementLevel").withSchema(TableName.SecretApprovalPolicy).as("policyEnforcementLevel"),
|
db.ref("enforcementLevel").withSchema(TableName.SecretApprovalPolicy).as("policyEnforcementLevel"),
|
||||||
|
db.ref("allowedSelfApprovals").withSchema(TableName.SecretApprovalPolicy).as("policyAllowedSelfApprovals"),
|
||||||
db.ref("approvals").withSchema(TableName.SecretApprovalPolicy).as("policyApprovals"),
|
db.ref("approvals").withSchema(TableName.SecretApprovalPolicy).as("policyApprovals"),
|
||||||
db.ref("approverUserId").withSchema(TableName.SecretApprovalPolicyApprover),
|
db.ref("approverUserId").withSchema(TableName.SecretApprovalPolicyApprover),
|
||||||
db.ref("userId").withSchema(TableName.UserGroupMembership).as("approverGroupUserId"),
|
db.ref("userId").withSchema(TableName.UserGroupMembership).as("approverGroupUserId"),
|
||||||
@@ -364,7 +367,8 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
name: el.policyName,
|
name: el.policyName,
|
||||||
approvals: el.policyApprovals,
|
approvals: el.policyApprovals,
|
||||||
secretPath: el.policySecretPath,
|
secretPath: el.policySecretPath,
|
||||||
enforcementLevel: el.policyEnforcementLevel
|
enforcementLevel: el.policyEnforcementLevel,
|
||||||
|
allowedSelfApprovals: el.policyAllowedSelfApprovals
|
||||||
},
|
},
|
||||||
committerUser: {
|
committerUser: {
|
||||||
userId: el.committerUserId,
|
userId: el.committerUserId,
|
||||||
@@ -482,6 +486,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
`DENSE_RANK() OVER (partition by ${TableName.Environment}."projectId" ORDER BY ${TableName.SecretApprovalRequest}."id" DESC) as rank`
|
`DENSE_RANK() OVER (partition by ${TableName.Environment}."projectId" ORDER BY ${TableName.SecretApprovalRequest}."id" DESC) as rank`
|
||||||
),
|
),
|
||||||
db.ref("secretPath").withSchema(TableName.SecretApprovalPolicy).as("policySecretPath"),
|
db.ref("secretPath").withSchema(TableName.SecretApprovalPolicy).as("policySecretPath"),
|
||||||
|
db.ref("allowedSelfApprovals").withSchema(TableName.SecretApprovalPolicy).as("policyAllowedSelfApprovals"),
|
||||||
db.ref("approvals").withSchema(TableName.SecretApprovalPolicy).as("policyApprovals"),
|
db.ref("approvals").withSchema(TableName.SecretApprovalPolicy).as("policyApprovals"),
|
||||||
db.ref("enforcementLevel").withSchema(TableName.SecretApprovalPolicy).as("policyEnforcementLevel"),
|
db.ref("enforcementLevel").withSchema(TableName.SecretApprovalPolicy).as("policyEnforcementLevel"),
|
||||||
db.ref("approverUserId").withSchema(TableName.SecretApprovalPolicyApprover),
|
db.ref("approverUserId").withSchema(TableName.SecretApprovalPolicyApprover),
|
||||||
@@ -511,7 +516,8 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
name: el.policyName,
|
name: el.policyName,
|
||||||
approvals: el.policyApprovals,
|
approvals: el.policyApprovals,
|
||||||
secretPath: el.policySecretPath,
|
secretPath: el.policySecretPath,
|
||||||
enforcementLevel: el.policyEnforcementLevel
|
enforcementLevel: el.policyEnforcementLevel,
|
||||||
|
allowedSelfApprovals: el.policyAllowedSelfApprovals
|
||||||
},
|
},
|
||||||
committerUser: {
|
committerUser: {
|
||||||
userId: el.committerUserId,
|
userId: el.committerUserId,
|
||||||
|
|||||||
@@ -352,6 +352,11 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
message: "The policy associated with this secret approval request has been deleted."
|
message: "The policy associated with this secret approval request has been deleted."
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (!policy.allowedSelfApprovals && actorId === secretApprovalRequest.committerUserId) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Failed to review secret approval request. Users are not authorized to review their own request."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const { hasRole } = await permissionService.getProjectPermission({
|
const { hasRole } = await permissionService.getProjectPermission({
|
||||||
actor: ActorType.USER,
|
actor: ActorType.USER,
|
||||||
|
|||||||
@@ -413,7 +413,14 @@ export const registerRoutes = async (
|
|||||||
serviceTokenDAL,
|
serviceTokenDAL,
|
||||||
projectDAL
|
projectDAL
|
||||||
});
|
});
|
||||||
const licenseService = licenseServiceFactory({ permissionService, orgDAL, licenseDAL, keyStore });
|
const licenseService = licenseServiceFactory({
|
||||||
|
permissionService,
|
||||||
|
orgDAL,
|
||||||
|
licenseDAL,
|
||||||
|
keyStore,
|
||||||
|
identityOrgMembershipDAL,
|
||||||
|
projectDAL
|
||||||
|
});
|
||||||
|
|
||||||
const hsmService = hsmServiceFactory({
|
const hsmService = hsmServiceFactory({
|
||||||
hsmModule,
|
hsmModule,
|
||||||
|
|||||||
@@ -64,9 +64,11 @@ export const identityUaServiceFactory = ({
|
|||||||
ipAddress: ip,
|
ipAddress: ip,
|
||||||
trustedIps: identityUa.clientSecretTrustedIps as TIp[]
|
trustedIps: identityUa.clientSecretTrustedIps as TIp[]
|
||||||
});
|
});
|
||||||
|
const clientSecretPrefix = clientSecret.slice(0, 4);
|
||||||
const clientSecrtInfo = await identityUaClientSecretDAL.find({
|
const clientSecrtInfo = await identityUaClientSecretDAL.find({
|
||||||
identityUAId: identityUa.id,
|
identityUAId: identityUa.id,
|
||||||
isClientSecretRevoked: false
|
isClientSecretRevoked: false,
|
||||||
|
clientSecretPrefix
|
||||||
});
|
});
|
||||||
|
|
||||||
let validClientSecretInfo: (typeof clientSecrtInfo)[0] | null = null;
|
let validClientSecretInfo: (typeof clientSecrtInfo)[0] | null = null;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: "Overview"
|
title: "Overview"
|
||||||
description: "Track evert event action performed within Infisical projects."
|
description: "Track all actions performed within Infisical"
|
||||||
---
|
---
|
||||||
|
|
||||||
<Info>
|
<Info>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export const isInfisicalCloud = () =>
|
export const isInfisicalCloud = () =>
|
||||||
window.location.origin.includes("https://app.infisical.com") ||
|
window.location.origin.includes("https://app.infisical.com") ||
|
||||||
window.location.origin.includes("https://us.infisical.com") ||
|
window.location.origin.includes("https://us.infisical.com") ||
|
||||||
window.location.origin.includes("https://eu.infisical.com");
|
window.location.origin.includes("https://eu.infisical.com") ||
|
||||||
|
window.location.origin.includes("https://gamma.infisical.com");
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ export const useCreateAccessApprovalPolicy = () => {
|
|||||||
approvers,
|
approvers,
|
||||||
name,
|
name,
|
||||||
secretPath,
|
secretPath,
|
||||||
enforcementLevel
|
enforcementLevel,
|
||||||
|
allowedSelfApprovals
|
||||||
}) => {
|
}) => {
|
||||||
const { data } = await apiRequest.post("/api/v1/access-approvals/policies", {
|
const { data } = await apiRequest.post("/api/v1/access-approvals/policies", {
|
||||||
environment,
|
environment,
|
||||||
@@ -32,7 +33,8 @@ export const useCreateAccessApprovalPolicy = () => {
|
|||||||
approvers,
|
approvers,
|
||||||
secretPath,
|
secretPath,
|
||||||
name,
|
name,
|
||||||
enforcementLevel
|
enforcementLevel,
|
||||||
|
allowedSelfApprovals
|
||||||
});
|
});
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
@@ -48,13 +50,22 @@ export const useUpdateAccessApprovalPolicy = () => {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation<object, object, TUpdateAccessPolicyDTO>({
|
return useMutation<object, object, TUpdateAccessPolicyDTO>({
|
||||||
mutationFn: async ({ id, approvers, approvals, name, secretPath, enforcementLevel }) => {
|
mutationFn: async ({
|
||||||
|
id,
|
||||||
|
approvers,
|
||||||
|
approvals,
|
||||||
|
name,
|
||||||
|
secretPath,
|
||||||
|
enforcementLevel,
|
||||||
|
allowedSelfApprovals
|
||||||
|
}) => {
|
||||||
const { data } = await apiRequest.patch(`/api/v1/access-approvals/policies/${id}`, {
|
const { data } = await apiRequest.patch(`/api/v1/access-approvals/policies/${id}`, {
|
||||||
approvals,
|
approvals,
|
||||||
approvers,
|
approvers,
|
||||||
secretPath,
|
secretPath,
|
||||||
name,
|
name,
|
||||||
enforcementLevel
|
enforcementLevel,
|
||||||
|
allowedSelfApprovals
|
||||||
});
|
});
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export type TAccessApprovalPolicy = {
|
|||||||
enforcementLevel: EnforcementLevel;
|
enforcementLevel: EnforcementLevel;
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
approvers?: Approver[];
|
approvers?: Approver[];
|
||||||
|
allowedSelfApprovals: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum ApproverType {
|
export enum ApproverType {
|
||||||
@@ -71,6 +72,7 @@ export type TAccessApprovalRequest = {
|
|||||||
envId: string;
|
envId: string;
|
||||||
enforcementLevel: EnforcementLevel;
|
enforcementLevel: EnforcementLevel;
|
||||||
deletedAt: Date | null;
|
deletedAt: Date | null;
|
||||||
|
allowedSelfApprovals: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
reviewers: {
|
reviewers: {
|
||||||
@@ -144,6 +146,7 @@ export type TCreateAccessPolicyDTO = {
|
|||||||
approvals?: number;
|
approvals?: number;
|
||||||
secretPath?: string;
|
secretPath?: string;
|
||||||
enforcementLevel?: EnforcementLevel;
|
enforcementLevel?: EnforcementLevel;
|
||||||
|
allowedSelfApprovals: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TUpdateAccessPolicyDTO = {
|
export type TUpdateAccessPolicyDTO = {
|
||||||
@@ -154,6 +157,7 @@ export type TUpdateAccessPolicyDTO = {
|
|||||||
environment?: string;
|
environment?: string;
|
||||||
approvals?: number;
|
approvals?: number;
|
||||||
enforcementLevel?: EnforcementLevel;
|
enforcementLevel?: EnforcementLevel;
|
||||||
|
allowedSelfApprovals: boolean;
|
||||||
// for invalidating list
|
// for invalidating list
|
||||||
projectSlug: string;
|
projectSlug: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ export const useCreateSecretApprovalPolicy = () => {
|
|||||||
approvers,
|
approvers,
|
||||||
secretPath,
|
secretPath,
|
||||||
name,
|
name,
|
||||||
enforcementLevel
|
enforcementLevel,
|
||||||
|
allowedSelfApprovals
|
||||||
}) => {
|
}) => {
|
||||||
const { data } = await apiRequest.post("/api/v1/secret-approvals", {
|
const { data } = await apiRequest.post("/api/v1/secret-approvals", {
|
||||||
environment,
|
environment,
|
||||||
@@ -25,7 +26,8 @@ export const useCreateSecretApprovalPolicy = () => {
|
|||||||
approvers,
|
approvers,
|
||||||
secretPath,
|
secretPath,
|
||||||
name,
|
name,
|
||||||
enforcementLevel
|
enforcementLevel,
|
||||||
|
allowedSelfApprovals
|
||||||
});
|
});
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
@@ -41,13 +43,22 @@ export const useUpdateSecretApprovalPolicy = () => {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation<object, object, TUpdateSecretPolicyDTO>({
|
return useMutation<object, object, TUpdateSecretPolicyDTO>({
|
||||||
mutationFn: async ({ id, approvers, approvals, secretPath, name, enforcementLevel }) => {
|
mutationFn: async ({
|
||||||
|
id,
|
||||||
|
approvers,
|
||||||
|
approvals,
|
||||||
|
secretPath,
|
||||||
|
name,
|
||||||
|
enforcementLevel,
|
||||||
|
allowedSelfApprovals
|
||||||
|
}) => {
|
||||||
const { data } = await apiRequest.patch(`/api/v1/secret-approvals/${id}`, {
|
const { data } = await apiRequest.patch(`/api/v1/secret-approvals/${id}`, {
|
||||||
approvals,
|
approvals,
|
||||||
approvers,
|
approvers,
|
||||||
secretPath,
|
secretPath,
|
||||||
name,
|
name,
|
||||||
enforcementLevel
|
enforcementLevel,
|
||||||
|
allowedSelfApprovals
|
||||||
});
|
});
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export type TSecretApprovalPolicy = {
|
|||||||
approvers: Approver[];
|
approvers: Approver[];
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
enforcementLevel: EnforcementLevel;
|
enforcementLevel: EnforcementLevel;
|
||||||
|
allowedSelfApprovals: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum ApproverType {
|
export enum ApproverType {
|
||||||
@@ -42,6 +43,7 @@ export type TCreateSecretPolicyDTO = {
|
|||||||
approvers?: Approver[];
|
approvers?: Approver[];
|
||||||
approvals?: number;
|
approvals?: number;
|
||||||
enforcementLevel: EnforcementLevel;
|
enforcementLevel: EnforcementLevel;
|
||||||
|
allowedSelfApprovals: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TUpdateSecretPolicyDTO = {
|
export type TUpdateSecretPolicyDTO = {
|
||||||
@@ -50,6 +52,7 @@ export type TUpdateSecretPolicyDTO = {
|
|||||||
approvers?: Approver[];
|
approvers?: Approver[];
|
||||||
secretPath?: string | null;
|
secretPath?: string | null;
|
||||||
approvals?: number;
|
approvals?: number;
|
||||||
|
allowedSelfApprovals?: boolean;
|
||||||
enforcementLevel?: EnforcementLevel;
|
enforcementLevel?: EnforcementLevel;
|
||||||
// for invalidating list
|
// for invalidating list
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
|
|||||||
@@ -12,17 +12,13 @@ export const DefaultSideBar = () => (
|
|||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
{(window.location.origin.includes("https://app.infisical.com") ||
|
<Link to="/organization/billing">
|
||||||
window.location.origin.includes("https://eu.infisical.com") ||
|
{({ isActive }) => (
|
||||||
window.location.origin.includes("https://gamma.infisical.com")) && (
|
<MenuItem isSelected={isActive} icon="spinning-coin">
|
||||||
<Link to="/organization/billing">
|
Usage & Billing
|
||||||
{({ isActive }) => (
|
</MenuItem>
|
||||||
<MenuItem isSelected={isActive} icon="spinning-coin">
|
)}
|
||||||
Usage & Billing
|
</Link>
|
||||||
</MenuItem>
|
|
||||||
)}
|
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
</MenuGroup>
|
</MenuGroup>
|
||||||
<MenuGroup title="Other">
|
<MenuGroup title="Other">
|
||||||
<Link to="/organization/access-management">
|
<Link to="/organization/access-management">
|
||||||
|
|||||||
+5
-11
@@ -370,17 +370,11 @@ export const MinimizedOrgSidebar = () => {
|
|||||||
Gateways
|
Gateways
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</Link>
|
</Link>
|
||||||
{(window.location.origin.includes("https://app.infisical.com") ||
|
<Link to="/organization/billing">
|
||||||
window.location.origin.includes("https://eu.infisical.com") ||
|
<DropdownMenuItem icon={<FontAwesomeIcon className="w-3" icon={faMoneyBill} />}>
|
||||||
window.location.origin.includes("https://gamma.infisical.com")) && (
|
Usage & Billing
|
||||||
<Link to="/organization/billing">
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem
|
</Link>
|
||||||
icon={<FontAwesomeIcon className="w-3" icon={faMoneyBill} />}
|
|
||||||
>
|
|
||||||
Usage & Billing
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
<Link to="/organization/audit-logs">
|
<Link to="/organization/audit-logs">
|
||||||
<DropdownMenuItem icon={<FontAwesomeIcon className="w-3" icon={faBook} />}>
|
<DropdownMenuItem icon={<FontAwesomeIcon className="w-3" icon={faBook} />}>
|
||||||
Audit Logs
|
Audit Logs
|
||||||
|
|||||||
+42
-18
@@ -9,6 +9,7 @@ import {
|
|||||||
useOrganization,
|
useOrganization,
|
||||||
useSubscription
|
useSubscription
|
||||||
} from "@app/context";
|
} from "@app/context";
|
||||||
|
import { isInfisicalCloud } from "@app/helpers/platform";
|
||||||
import {
|
import {
|
||||||
useCreateCustomerPortalSession,
|
useCreateCustomerPortalSession,
|
||||||
useGetOrgPlanBillingInfo,
|
useGetOrgPlanBillingInfo,
|
||||||
@@ -47,6 +48,9 @@ export const PreviewSection = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function formatPlanSlug(slug: string) {
|
function formatPlanSlug(slug: string) {
|
||||||
|
if (!slug) {
|
||||||
|
return "-";
|
||||||
|
}
|
||||||
return slug.replace(/(\b[a-z])/g, (match) => match.toUpperCase()).replace(/-/g, " ");
|
return slug.replace(/(\b[a-z])/g, (match) => match.toUpperCase()).replace(/-/g, " ");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,6 +58,11 @@ export const PreviewSection = () => {
|
|||||||
try {
|
try {
|
||||||
if (!subscription || !currentOrg) return;
|
if (!subscription || !currentOrg) return;
|
||||||
|
|
||||||
|
if (!isInfisicalCloud()) {
|
||||||
|
window.open("https://infisical.com/pricing", "_blank");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!subscription.has_used_trial) {
|
if (!subscription.has_used_trial) {
|
||||||
// direct user to start pro trial
|
// direct user to start pro trial
|
||||||
const url = await getOrgTrialUrl.mutateAsync({
|
const url = await getOrgTrialUrl.mutateAsync({
|
||||||
@@ -71,6 +80,19 @@ export const PreviewSection = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getUpgradePlanLabel = () => {
|
||||||
|
if (!isInfisicalCloud()) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
Go to Pricing
|
||||||
|
<FontAwesomeIcon icon={faArrowUpRightFromSquare} className="mb-[0.06rem] ml-1 text-xs" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return !subscription.has_used_trial ? "Start Pro Free Trial" : "Upgrade Plan";
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{subscription &&
|
{subscription &&
|
||||||
@@ -97,7 +119,7 @@ export const PreviewSection = () => {
|
|||||||
color="mineshaft"
|
color="mineshaft"
|
||||||
isDisabled={!isAllowed}
|
isDisabled={!isAllowed}
|
||||||
>
|
>
|
||||||
{!subscription.has_used_trial ? "Start Pro Free Trial" : "Upgrade Plan"}
|
{getUpgradePlanLabel()}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</OrgPermissionCan>
|
</OrgPermissionCan>
|
||||||
@@ -133,22 +155,24 @@ export const PreviewSection = () => {
|
|||||||
subscription.status === "trialing" ? "(Trial)" : ""
|
subscription.status === "trialing" ? "(Trial)" : ""
|
||||||
}`}
|
}`}
|
||||||
</p>
|
</p>
|
||||||
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Billing}>
|
{isInfisicalCloud() && (
|
||||||
{(isAllowed) => (
|
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Billing}>
|
||||||
<button
|
{(isAllowed) => (
|
||||||
type="button"
|
<button
|
||||||
onClick={async () => {
|
type="button"
|
||||||
if (!currentOrg?.id) return;
|
onClick={async () => {
|
||||||
const { url } = await createCustomerPortalSession.mutateAsync(currentOrg.id);
|
if (!currentOrg?.id) return;
|
||||||
window.location.href = url;
|
const { url } = await createCustomerPortalSession.mutateAsync(currentOrg.id);
|
||||||
}}
|
window.location.href = url;
|
||||||
disabled={!isAllowed}
|
}}
|
||||||
className="text-primary"
|
disabled={!isAllowed}
|
||||||
>
|
className="text-primary"
|
||||||
Manage plan →
|
>
|
||||||
</button>
|
Manage plan →
|
||||||
)}
|
</button>
|
||||||
</OrgPermissionCan>
|
)}
|
||||||
|
</OrgPermissionCan>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="mr-4 flex-1 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
<div className="mr-4 flex-1 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
<p className="mb-2 text-gray-400">Price</p>
|
<p className="mb-2 text-gray-400">Price</p>
|
||||||
@@ -161,7 +185,7 @@ export const PreviewSection = () => {
|
|||||||
<div className="flex-1 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
<div className="flex-1 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
<p className="mb-2 text-gray-400">Subscription renews on</p>
|
<p className="mb-2 text-gray-400">Subscription renews on</p>
|
||||||
<p className="mb-8 text-2xl font-semibold text-mineshaft-50">
|
<p className="mb-8 text-2xl font-semibold text-mineshaft-50">
|
||||||
{formatDate(data.currentPeriodEnd)}
|
{data.currentPeriodEnd ? formatDate(data.currentPeriodEnd) : "-"}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+19
-10
@@ -1,5 +1,6 @@
|
|||||||
import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
|
import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
|
||||||
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
|
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
|
||||||
|
import { isInfisicalCloud } from "@app/helpers/platform";
|
||||||
import { withPermission } from "@app/hoc";
|
import { withPermission } from "@app/hoc";
|
||||||
|
|
||||||
import { BillingCloudTab } from "../BillingCloudTab";
|
import { BillingCloudTab } from "../BillingCloudTab";
|
||||||
@@ -16,25 +17,33 @@ const tabs = [
|
|||||||
|
|
||||||
export const BillingTabGroup = withPermission(
|
export const BillingTabGroup = withPermission(
|
||||||
() => {
|
() => {
|
||||||
|
const tabsFiltered = isInfisicalCloud()
|
||||||
|
? tabs
|
||||||
|
: [{ name: "Infisical Self-Hosted", key: "tab-infisical-cloud" }];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tabs defaultValue={tabs[0].key}>
|
<Tabs defaultValue={tabs[0].key}>
|
||||||
<TabList>
|
<TabList>
|
||||||
{tabs.map((tab) => (
|
{tabsFiltered.map((tab) => (
|
||||||
<Tab value={tab.key}>{tab.name}</Tab>
|
<Tab value={tab.key}>{tab.name}</Tab>
|
||||||
))}
|
))}
|
||||||
</TabList>
|
</TabList>
|
||||||
<TabPanel value={tabs[0].key}>
|
<TabPanel value={tabs[0].key}>
|
||||||
<BillingCloudTab />
|
<BillingCloudTab />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel value={tabs[1].key}>
|
{isInfisicalCloud() && (
|
||||||
<BillingSelfHostedTab />
|
<>
|
||||||
</TabPanel>
|
<TabPanel value={tabs[1].key}>
|
||||||
<TabPanel value={tabs[2].key}>
|
<BillingSelfHostedTab />
|
||||||
<BillingReceiptsTab />
|
</TabPanel>
|
||||||
</TabPanel>
|
<TabPanel value={tabs[2].key}>
|
||||||
<TabPanel value={tabs[3].key}>
|
<BillingReceiptsTab />
|
||||||
<BillingDetailsTab />
|
</TabPanel>
|
||||||
</TabPanel>
|
<TabPanel value={tabs[3].key}>
|
||||||
|
<BillingDetailsTab />
|
||||||
|
</TabPanel>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
+10
-8
@@ -152,7 +152,7 @@ export const AccessApprovalRequest = ({
|
|||||||
const isAccepted = request.isApproved;
|
const isAccepted = request.isApproved;
|
||||||
const isSoftEnforcement = request.policy.enforcementLevel === EnforcementLevel.Soft;
|
const isSoftEnforcement = request.policy.enforcementLevel === EnforcementLevel.Soft;
|
||||||
const isRequestedByCurrentUser = request.requestedByUserId === user.id;
|
const isRequestedByCurrentUser = request.requestedByUserId === user.id;
|
||||||
|
const isSelfApproveAllowed = request.policy.allowedSelfApprovals;
|
||||||
const userReviewStatus = request.reviewers.find(({ member }) => member === user.id)?.status;
|
const userReviewStatus = request.reviewers.find(({ member }) => member === user.id)?.status;
|
||||||
|
|
||||||
let displayData: { label: string; type: "primary" | "danger" | "success" } = {
|
let displayData: { label: string; type: "primary" | "danger" | "success" } = {
|
||||||
@@ -189,7 +189,8 @@ export const AccessApprovalRequest = ({
|
|||||||
userReviewStatus,
|
userReviewStatus,
|
||||||
isAccepted,
|
isAccepted,
|
||||||
isSoftEnforcement,
|
isSoftEnforcement,
|
||||||
isRequestedByCurrentUser
|
isRequestedByCurrentUser,
|
||||||
|
isSelfApproveAllowed
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -342,15 +343,16 @@ export const AccessApprovalRequest = ({
|
|||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (
|
if (
|
||||||
(!details.isApprover ||
|
((!details.isApprover ||
|
||||||
details.isReviewedByUser ||
|
details.isReviewedByUser ||
|
||||||
details.isRejectedByAnyone ||
|
details.isRejectedByAnyone ||
|
||||||
details.isAccepted) &&
|
details.isAccepted) &&
|
||||||
!(
|
!(
|
||||||
details.isSoftEnforcement &&
|
details.isSoftEnforcement &&
|
||||||
details.isRequestedByCurrentUser &&
|
details.isRequestedByCurrentUser &&
|
||||||
!details.isAccepted
|
!details.isAccepted
|
||||||
)
|
)) ||
|
||||||
|
(request.requestedByUserId === user.id && !details.isSelfApproveAllowed)
|
||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
if (membersGroupById?.[request.requestedByUserId].user) {
|
if (membersGroupById?.[request.requestedByUserId].user) {
|
||||||
|
|||||||
+27
-3
@@ -12,7 +12,8 @@ import {
|
|||||||
Modal,
|
Modal,
|
||||||
ModalContent,
|
ModalContent,
|
||||||
Select,
|
Select,
|
||||||
SelectItem
|
SelectItem,
|
||||||
|
Switch
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { useWorkspace } from "@app/context";
|
import { useWorkspace } from "@app/context";
|
||||||
import { getMemberLabel } from "@app/helpers/members";
|
import { getMemberLabel } from "@app/helpers/members";
|
||||||
@@ -54,7 +55,8 @@ const formSchema = z
|
|||||||
.array()
|
.array()
|
||||||
.default([]),
|
.default([]),
|
||||||
policyType: z.nativeEnum(PolicyType),
|
policyType: z.nativeEnum(PolicyType),
|
||||||
enforcementLevel: z.nativeEnum(EnforcementLevel)
|
enforcementLevel: z.nativeEnum(EnforcementLevel),
|
||||||
|
allowedSelfApprovals: z.boolean().default(true)
|
||||||
})
|
})
|
||||||
.superRefine((data, ctx) => {
|
.superRefine((data, ctx) => {
|
||||||
if (!(data.groupApprovers.length || data.userApprovers.length)) {
|
if (!(data.groupApprovers.length || data.userApprovers.length)) {
|
||||||
@@ -101,7 +103,8 @@ export const AccessPolicyForm = ({
|
|||||||
editValues?.approvers
|
editValues?.approvers
|
||||||
?.filter((approver) => approver.type === ApproverType.Group)
|
?.filter((approver) => approver.type === ApproverType.Group)
|
||||||
.map(({ id, type }) => ({ id, type: type as ApproverType.Group })) || [],
|
.map(({ id, type }) => ({ id, type: type as ApproverType.Group })) || [],
|
||||||
approvals: editValues?.approvals
|
approvals: editValues?.approvals,
|
||||||
|
allowedSelfApprovals: editValues?.allowedSelfApprovals
|
||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
});
|
});
|
||||||
@@ -441,6 +444,27 @@ export const AccessPolicyForm = ({
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="allowedSelfApprovals"
|
||||||
|
defaultValue
|
||||||
|
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Self Approvals"
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Switch
|
||||||
|
id="self-approvals"
|
||||||
|
thumbClassName="bg-mineshaft-800"
|
||||||
|
isChecked={value}
|
||||||
|
onCheckedChange={onChange}
|
||||||
|
>
|
||||||
|
Allow approvers to review their own requests
|
||||||
|
</Switch>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
<div className="mt-8 flex items-center space-x-4">
|
<div className="mt-8 flex items-center space-x-4">
|
||||||
<Button type="submit" isLoading={isSubmitting} isDisabled={isSubmitting}>
|
<Button type="submit" isLoading={isSubmitting} isDisabled={isSubmitting}>
|
||||||
Save
|
Save
|
||||||
|
|||||||
+152
-143
@@ -127,7 +127,9 @@ export const SecretApprovalRequestChanges = ({
|
|||||||
} = useForm<TReviewFormSchema>({
|
} = useForm<TReviewFormSchema>({
|
||||||
resolver: zodResolver(reviewFormSchema)
|
resolver: zodResolver(reviewFormSchema)
|
||||||
});
|
});
|
||||||
|
const shouldBlockSelfReview =
|
||||||
|
secretApprovalRequestDetails?.policy?.allowedSelfApprovals === false &&
|
||||||
|
secretApprovalRequestDetails?.committerUserId === userSession.id;
|
||||||
const isApproving = variables?.status === ApprovalStatus.APPROVED && isUpdatingRequestStatus;
|
const isApproving = variables?.status === ApprovalStatus.APPROVED && isUpdatingRequestStatus;
|
||||||
const isRejecting = variables?.status === ApprovalStatus.REJECTED && isUpdatingRequestStatus;
|
const isRejecting = variables?.status === ApprovalStatus.REJECTED && isUpdatingRequestStatus;
|
||||||
|
|
||||||
@@ -245,117 +247,119 @@ export const SecretApprovalRequestChanges = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{!hasMerged && secretApprovalRequestDetails.status === "open" && (
|
{!hasMerged &&
|
||||||
<DropdownMenu
|
secretApprovalRequestDetails.status === "open" &&
|
||||||
open={popUp.reviewChanges.isOpen}
|
!shouldBlockSelfReview && (
|
||||||
onOpenChange={(isOpen) => handlePopUpToggle("reviewChanges", isOpen)}
|
<DropdownMenu
|
||||||
>
|
open={popUp.reviewChanges.isOpen}
|
||||||
<DropdownMenuTrigger asChild>
|
onOpenChange={(isOpen) => handlePopUpToggle("reviewChanges", isOpen)}
|
||||||
<Button
|
>
|
||||||
variant="outline_bg"
|
<DropdownMenuTrigger asChild>
|
||||||
rightIcon={<FontAwesomeIcon className="ml-2" icon={faAngleDown} />}
|
<Button
|
||||||
>
|
variant="outline_bg"
|
||||||
Review
|
rightIcon={<FontAwesomeIcon className="ml-2" icon={faAngleDown} />}
|
||||||
</Button>
|
>
|
||||||
</DropdownMenuTrigger>
|
Review
|
||||||
<DropdownMenuContent align="end" asChild className="mt-3">
|
</Button>
|
||||||
<form onSubmit={handleSubmit(handleSubmitReview)}>
|
</DropdownMenuTrigger>
|
||||||
<div className="flex w-[400px] flex-col space-y-2 p-5">
|
<DropdownMenuContent align="end" asChild className="mt-3">
|
||||||
<div className="text-lg font-medium">Finish your review</div>
|
<form onSubmit={handleSubmit(handleSubmitReview)}>
|
||||||
<Controller
|
<div className="flex w-[400px] flex-col space-y-2 p-5">
|
||||||
control={control}
|
<div className="text-lg font-medium">Finish your review</div>
|
||||||
name="comment"
|
<Controller
|
||||||
render={({ field, fieldState: { error } }) => (
|
control={control}
|
||||||
<FormControl errorText={error?.message} isError={Boolean(error)}>
|
name="comment"
|
||||||
<TextArea
|
render={({ field, fieldState: { error } }) => (
|
||||||
{...field}
|
<FormControl errorText={error?.message} isError={Boolean(error)}>
|
||||||
placeholder="Leave a comment..."
|
<TextArea
|
||||||
reSize="none"
|
{...field}
|
||||||
className="text-md mt-2 h-48 border border-mineshaft-600 bg-bunker-800"
|
placeholder="Leave a comment..."
|
||||||
/>
|
reSize="none"
|
||||||
</FormControl>
|
className="text-md mt-2 h-48 border border-mineshaft-600 bg-bunker-800"
|
||||||
)}
|
/>
|
||||||
/>
|
</FormControl>
|
||||||
<Controller
|
)}
|
||||||
control={control}
|
/>
|
||||||
name="status"
|
<Controller
|
||||||
defaultValue={ApprovalStatus.APPROVED}
|
control={control}
|
||||||
render={({ field, fieldState: { error } }) => (
|
name="status"
|
||||||
<FormControl errorText={error?.message} isError={Boolean(error)}>
|
defaultValue={ApprovalStatus.APPROVED}
|
||||||
<RadioGroup
|
render={({ field, fieldState: { error } }) => (
|
||||||
value={field.value}
|
<FormControl errorText={error?.message} isError={Boolean(error)}>
|
||||||
onValueChange={field.onChange}
|
<RadioGroup
|
||||||
className="mb-4 space-y-2"
|
value={field.value}
|
||||||
aria-label="Status"
|
onValueChange={field.onChange}
|
||||||
>
|
className="mb-4 space-y-2"
|
||||||
<div className="flex items-center gap-2">
|
aria-label="Status"
|
||||||
<RadioGroupItem
|
>
|
||||||
id="approve"
|
<div className="flex items-center gap-2">
|
||||||
className="h-4 w-4 rounded-full border border-gray-300 text-primary focus:ring-2 focus:ring-mineshaft-500"
|
<RadioGroupItem
|
||||||
value={ApprovalStatus.APPROVED}
|
id="approve"
|
||||||
aria-labelledby="approve-label"
|
className="h-4 w-4 rounded-full border border-gray-300 text-primary focus:ring-2 focus:ring-mineshaft-500"
|
||||||
>
|
value={ApprovalStatus.APPROVED}
|
||||||
<RadioGroupIndicator className="flex h-full w-full items-center justify-center after:h-2 after:w-2 after:rounded-full after:bg-current" />
|
aria-labelledby="approve-label"
|
||||||
</RadioGroupItem>
|
>
|
||||||
<span
|
<RadioGroupIndicator className="flex h-full w-full items-center justify-center after:h-2 after:w-2 after:rounded-full after:bg-current" />
|
||||||
id="approve-label"
|
</RadioGroupItem>
|
||||||
className="cursor-pointer"
|
<span
|
||||||
onClick={() => field.onChange(ApprovalStatus.APPROVED)}
|
id="approve-label"
|
||||||
onKeyDown={(e) => {
|
className="cursor-pointer"
|
||||||
if (e.key === "Enter" || e.key === " ") {
|
onClick={() => field.onChange(ApprovalStatus.APPROVED)}
|
||||||
e.preventDefault();
|
onKeyDown={(e) => {
|
||||||
field.onChange(ApprovalStatus.APPROVED);
|
if (e.key === "Enter" || e.key === " ") {
|
||||||
}
|
e.preventDefault();
|
||||||
}}
|
field.onChange(ApprovalStatus.APPROVED);
|
||||||
tabIndex={0}
|
}
|
||||||
role="button"
|
}}
|
||||||
>
|
tabIndex={0}
|
||||||
Approve
|
role="button"
|
||||||
</span>
|
>
|
||||||
</div>
|
Approve
|
||||||
<div className="flex items-center gap-2">
|
</span>
|
||||||
<RadioGroupItem
|
</div>
|
||||||
id="reject"
|
<div className="flex items-center gap-2">
|
||||||
className="h-4 w-4 rounded-full border border-gray-300 text-primary focus:ring-2 focus:ring-mineshaft-500"
|
<RadioGroupItem
|
||||||
value={ApprovalStatus.REJECTED}
|
id="reject"
|
||||||
aria-labelledby="reject-label"
|
className="h-4 w-4 rounded-full border border-gray-300 text-primary focus:ring-2 focus:ring-mineshaft-500"
|
||||||
>
|
value={ApprovalStatus.REJECTED}
|
||||||
<RadioGroupIndicator className="flex h-full w-full items-center justify-center after:h-2 after:w-2 after:rounded-full after:bg-current" />
|
aria-labelledby="reject-label"
|
||||||
</RadioGroupItem>
|
>
|
||||||
<span
|
<RadioGroupIndicator className="flex h-full w-full items-center justify-center after:h-2 after:w-2 after:rounded-full after:bg-current" />
|
||||||
id="reject-label"
|
</RadioGroupItem>
|
||||||
className="cursor-pointer"
|
<span
|
||||||
onClick={() => field.onChange(ApprovalStatus.REJECTED)}
|
id="reject-label"
|
||||||
onKeyDown={(e) => {
|
className="cursor-pointer"
|
||||||
if (e.key === "Enter" || e.key === " ") {
|
onClick={() => field.onChange(ApprovalStatus.REJECTED)}
|
||||||
e.preventDefault();
|
onKeyDown={(e) => {
|
||||||
field.onChange(ApprovalStatus.REJECTED);
|
if (e.key === "Enter" || e.key === " ") {
|
||||||
}
|
e.preventDefault();
|
||||||
}}
|
field.onChange(ApprovalStatus.REJECTED);
|
||||||
tabIndex={0}
|
}
|
||||||
role="button"
|
}}
|
||||||
>
|
tabIndex={0}
|
||||||
Reject
|
role="button"
|
||||||
</span>
|
>
|
||||||
</div>
|
Reject
|
||||||
</RadioGroup>
|
</span>
|
||||||
</FormControl>
|
</div>
|
||||||
)}
|
</RadioGroup>
|
||||||
/>
|
</FormControl>
|
||||||
<div className="flex justify-end">
|
)}
|
||||||
<Button
|
/>
|
||||||
type="submit"
|
<div className="flex justify-end">
|
||||||
isLoading={isApproving || isRejecting || isSubmitting}
|
<Button
|
||||||
variant="outline_bg"
|
type="submit"
|
||||||
>
|
isLoading={isApproving || isRejecting || isSubmitting}
|
||||||
Submit Review
|
variant="outline_bg"
|
||||||
</Button>
|
>
|
||||||
|
Submit Review
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</form>
|
||||||
</form>
|
</DropdownMenuContent>
|
||||||
</DropdownMenuContent>
|
</DropdownMenu>
|
||||||
</DropdownMenu>
|
)}
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col space-y-4">
|
<div className="flex flex-col space-y-4">
|
||||||
{secretApprovalRequestDetails.commits.map(
|
{secretApprovalRequestDetails.commits.map(
|
||||||
@@ -422,40 +426,45 @@ export const SecretApprovalRequestChanges = ({
|
|||||||
<div className="sticky top-0 w-1/5 pt-4" style={{ minWidth: "240px" }}>
|
<div className="sticky top-0 w-1/5 pt-4" style={{ minWidth: "240px" }}>
|
||||||
<div className="text-sm text-bunker-300">Reviewers</div>
|
<div className="text-sm text-bunker-300">Reviewers</div>
|
||||||
<div className="mt-2 flex flex-col space-y-2 text-sm">
|
<div className="mt-2 flex flex-col space-y-2 text-sm">
|
||||||
{secretApprovalRequestDetails?.policy?.approvers.map((requiredApprover) => {
|
{secretApprovalRequestDetails?.policy?.approvers
|
||||||
const reviewer = reviewedUsers?.[requiredApprover.userId];
|
.filter(
|
||||||
return (
|
(requiredApprover) =>
|
||||||
<div
|
!(shouldBlockSelfReview && requiredApprover.userId === userSession.id)
|
||||||
className="flex flex-nowrap items-center space-x-2 rounded bg-mineshaft-800 px-2 py-1"
|
)
|
||||||
key={`required-approver-${requiredApprover.userId}`}
|
.map((requiredApprover) => {
|
||||||
>
|
const reviewer = reviewedUsers?.[requiredApprover.userId];
|
||||||
<div className="flex-grow text-sm">
|
return (
|
||||||
<Tooltip
|
<div
|
||||||
content={`${requiredApprover.firstName || ""} ${
|
className="flex flex-nowrap items-center space-x-2 rounded bg-mineshaft-800 px-2 py-1"
|
||||||
requiredApprover.lastName || ""
|
key={`required-approver-${requiredApprover.userId}`}
|
||||||
}`}
|
>
|
||||||
>
|
<div className="flex-grow text-sm">
|
||||||
<span>{requiredApprover?.email} </span>
|
<Tooltip
|
||||||
</Tooltip>
|
content={`${requiredApprover.firstName || ""} ${
|
||||||
<span className="text-red">*</span>
|
requiredApprover.lastName || ""
|
||||||
</div>
|
}`}
|
||||||
<div>
|
>
|
||||||
{reviewer?.comment && (
|
<span>{requiredApprover?.email} </span>
|
||||||
<Tooltip content={reviewer.comment}>
|
|
||||||
<FontAwesomeIcon
|
|
||||||
icon={faComment}
|
|
||||||
size="xs"
|
|
||||||
className="mr-1 text-mineshaft-300"
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
<span className="text-red">*</span>
|
||||||
<Tooltip content={reviewer?.status || ApprovalStatus.PENDING}>
|
</div>
|
||||||
{getReviewedStatusSymbol(reviewer?.status)}
|
<div>
|
||||||
</Tooltip>
|
{reviewer?.comment && (
|
||||||
|
<Tooltip content={reviewer.comment}>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faComment}
|
||||||
|
size="xs"
|
||||||
|
className="mr-1 text-mineshaft-300"
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
<Tooltip content={reviewer?.status || ApprovalStatus.PENDING}>
|
||||||
|
{getReviewedStatusSymbol(reviewer?.status)}
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
);
|
})}
|
||||||
})}
|
|
||||||
{secretApprovalRequestDetails?.reviewers
|
{secretApprovalRequestDetails?.reviewers
|
||||||
.filter(
|
.filter(
|
||||||
(reviewer) =>
|
(reviewer) =>
|
||||||
|
|||||||
@@ -504,5 +504,4 @@ status:
|
|||||||
plural: ""
|
plural: ""
|
||||||
conditions: []
|
conditions: []
|
||||||
storedVersions: []
|
storedVersions: []
|
||||||
|
|
||||||
{{- end }}
|
{{- end }}
|
||||||
@@ -53,6 +53,15 @@ rules:
|
|||||||
- list
|
- list
|
||||||
- update
|
- update
|
||||||
- watch
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- apps
|
||||||
|
resources:
|
||||||
|
- deployments
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- update
|
||||||
|
- watch
|
||||||
- apiGroups:
|
- apiGroups:
|
||||||
- secrets.infisical.com
|
- secrets.infisical.com
|
||||||
resources:
|
resources:
|
||||||
|
|||||||
@@ -13,4 +13,5 @@ rules:
|
|||||||
- /metrics
|
- /metrics
|
||||||
verbs:
|
verbs:
|
||||||
- get
|
- get
|
||||||
|
|
||||||
{{- end }}
|
{{- end }}
|
||||||
@@ -14,4 +14,4 @@ spec:
|
|||||||
control-plane: controller-manager
|
control-plane: controller-manager
|
||||||
{{- include "secrets-operator.selectorLabels" . | nindent 4 }}
|
{{- include "secrets-operator.selectorLabels" . | nindent 4 }}
|
||||||
ports:
|
ports:
|
||||||
{{- .Values.metricsService.ports | toYaml | nindent 2 }}
|
{{- .Values.metricsService.ports | toYaml | nindent 2 }}
|
||||||
|
|||||||
@@ -39,4 +39,5 @@ subjects:
|
|||||||
- kind: ServiceAccount
|
- kind: ServiceAccount
|
||||||
name: '{{ include "secrets-operator.fullname" . }}-controller-manager'
|
name: '{{ include "secrets-operator.fullname" . }}-controller-manager'
|
||||||
namespace: '{{ .Release.Namespace }}'
|
namespace: '{{ .Release.Namespace }}'
|
||||||
|
|
||||||
{{- end }}
|
{{- end }}
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
controllerManager:
|
controllerManager:
|
||||||
kubeRbacProxy:
|
kubeRbacProxy:
|
||||||
args:
|
args:
|
||||||
- --secure-listen-address=0.0.0.0:8443
|
- --secure-listen-address=0.0.0.0:8443
|
||||||
- --upstream=http://127.0.0.1:8080/
|
- --upstream=http://127.0.0.1:8080/
|
||||||
- --logtostderr=true
|
- --logtostderr=true
|
||||||
- --v=0
|
- --v=0
|
||||||
containerSecurityContext:
|
containerSecurityContext:
|
||||||
allowPrivilegeEscalation: false
|
allowPrivilegeEscalation: false
|
||||||
capabilities:
|
capabilities:
|
||||||
drop:
|
drop:
|
||||||
- ALL
|
- ALL
|
||||||
image:
|
image:
|
||||||
repository: gcr.io/kubebuilder/kube-rbac-proxy
|
repository: gcr.io/kubebuilder/kube-rbac-proxy
|
||||||
tag: v0.15.0
|
tag: v0.15.0
|
||||||
@@ -22,17 +22,17 @@ controllerManager:
|
|||||||
memory: 64Mi
|
memory: 64Mi
|
||||||
manager:
|
manager:
|
||||||
args:
|
args:
|
||||||
- --health-probe-bind-address=:8081
|
- --health-probe-bind-address=:8081
|
||||||
- --metrics-bind-address=127.0.0.1:8080
|
- --metrics-bind-address=127.0.0.1:8080
|
||||||
- --leader-elect
|
- --leader-elect
|
||||||
containerSecurityContext:
|
containerSecurityContext:
|
||||||
allowPrivilegeEscalation: false
|
allowPrivilegeEscalation: false
|
||||||
capabilities:
|
capabilities:
|
||||||
drop:
|
drop:
|
||||||
- ALL
|
- ALL
|
||||||
image:
|
image:
|
||||||
repository: infisical/kubernetes-operator
|
repository: infisical/kubernetes-operator
|
||||||
tag: v0.8.15
|
tag: <helm-pr-will-update-this-automatically>
|
||||||
resources:
|
resources:
|
||||||
limits:
|
limits:
|
||||||
cpu: 500m
|
cpu: 500m
|
||||||
@@ -45,14 +45,14 @@ controllerManager:
|
|||||||
annotations: {}
|
annotations: {}
|
||||||
nodeSelector: {}
|
nodeSelector: {}
|
||||||
tolerations: []
|
tolerations: []
|
||||||
|
metricsService:
|
||||||
|
ports:
|
||||||
|
- name: https
|
||||||
|
port: 8443
|
||||||
|
protocol: TCP
|
||||||
|
targetPort: https
|
||||||
|
type: ClusterIP
|
||||||
kubernetesClusterDomain: cluster.local
|
kubernetesClusterDomain: cluster.local
|
||||||
scopedNamespace: ""
|
scopedNamespace: ""
|
||||||
scopedRBAC: false
|
scopedRBAC: false
|
||||||
installCRDs: true
|
installCRDs: true
|
||||||
metricsService:
|
|
||||||
ports:
|
|
||||||
- name: https
|
|
||||||
port: 8443
|
|
||||||
protocol: TCP
|
|
||||||
targetPort: https
|
|
||||||
type: ClusterIP
|
|
||||||
|
|||||||
@@ -48,9 +48,12 @@ helmify: $(HELMIFY) ## Download helmify locally if necessary.
|
|||||||
$(HELMIFY): $(LOCALBIN)
|
$(HELMIFY): $(LOCALBIN)
|
||||||
test -s $(LOCALBIN)/helmify || GOBIN=$(LOCALBIN) go install github.com/arttor/helmify/cmd/helmify@latest
|
test -s $(LOCALBIN)/helmify || GOBIN=$(LOCALBIN) go install github.com/arttor/helmify/cmd/helmify@latest
|
||||||
|
|
||||||
helm: manifests kustomize helmify
|
legacy-helm: manifests kustomize helmify
|
||||||
$(KUSTOMIZE) build config/default | $(HELMIFY) ../helm-charts/secrets-operator
|
$(KUSTOMIZE) build config/default | $(HELMIFY) ../helm-charts/secrets-operator
|
||||||
|
|
||||||
|
helm: manifests kustomize helmify
|
||||||
|
./scripts/generate-helm.sh
|
||||||
|
|
||||||
## Yaml for Kubectl
|
## Yaml for Kubectl
|
||||||
kubectl-install: manifests kustomize
|
kubectl-install: manifests kustomize
|
||||||
mkdir -p kubectl-install
|
mkdir -p kubectl-install
|
||||||
|
|||||||
Executable
+332
@@ -0,0 +1,332 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd)
|
||||||
|
PROJECT_ROOT=$(cd "${SCRIPT_DIR}/.." && pwd)
|
||||||
|
HELM_DIR="${PROJECT_ROOT}/../helm-charts/secrets-operator"
|
||||||
|
LOCALBIN="${PROJECT_ROOT}/bin"
|
||||||
|
KUSTOMIZE="${LOCALBIN}/kustomize"
|
||||||
|
HELMIFY="${LOCALBIN}/helmify"
|
||||||
|
|
||||||
|
|
||||||
|
cd "${PROJECT_ROOT}"
|
||||||
|
# first run the regular helm target to generate base templates
|
||||||
|
"${KUSTOMIZE}" build config/default | "${HELMIFY}" "${HELM_DIR}"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ? NOTE: Processes all files that end with crd.yaml (so only actual CRDs)
|
||||||
|
for crd_file in "${HELM_DIR}"/templates/*crd.yaml; do
|
||||||
|
# skip if file doesn't exist (pattern doesn't match)
|
||||||
|
[ -e "$crd_file" ] || continue
|
||||||
|
|
||||||
|
echo "Processing CRD file: ${crd_file}"
|
||||||
|
|
||||||
|
cp "$crd_file" "$crd_file.bkp"
|
||||||
|
|
||||||
|
# if we ever need to run conditional logic based on the CRD kind, we can use this
|
||||||
|
# CRD_KIND=$(grep -E "kind: [a-zA-Z]+" "$crd_file" | head -n1 | awk '{print $2}')
|
||||||
|
# echo "Found CRD kind: ${CRD_KIND}"
|
||||||
|
|
||||||
|
# create a new file with the conditional statement, then append the entire original content
|
||||||
|
echo "{{- if .Values.installCRDs }}" > "$crd_file.new"
|
||||||
|
cat "$crd_file.bkp" >> "$crd_file.new"
|
||||||
|
|
||||||
|
# make sure the file ends with a newline before adding the end tag (otherwise it might get messed up and end up on the same line as the last line)
|
||||||
|
# check if file already ends with a newline
|
||||||
|
if [ "$(tail -c1 "$crd_file.new" | wc -l)" -eq 0 ]; then
|
||||||
|
# File doesn't end with a newline, add one
|
||||||
|
echo "" >> "$crd_file.new"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# add the end tag on a new line
|
||||||
|
echo "{{- end }}" >> "$crd_file.new"
|
||||||
|
|
||||||
|
# replace the original file with the new one
|
||||||
|
mv "$crd_file.new" "$crd_file"
|
||||||
|
|
||||||
|
# clean up backup
|
||||||
|
rm "$crd_file.bkp"
|
||||||
|
|
||||||
|
echo "Completed processing for: ${crd_file}"
|
||||||
|
done
|
||||||
|
|
||||||
|
# ? NOTE: Processes only the manager-rbac.yaml file
|
||||||
|
if [ -f "${HELM_DIR}/templates/manager-rbac.yaml" ]; then
|
||||||
|
echo "Processing manager-rbac.yaml file specifically"
|
||||||
|
|
||||||
|
|
||||||
|
cp "${HELM_DIR}/templates/manager-rbac.yaml" "${HELM_DIR}/templates/manager-rbac.yaml.bkp"
|
||||||
|
|
||||||
|
# extract the rules section from the original file
|
||||||
|
rules_section=$(sed -n '/^rules:/,/^---/p' "${HELM_DIR}/templates/manager-rbac.yaml.bkp" | sed '$d')
|
||||||
|
# extract the original label lines
|
||||||
|
original_labels=$(sed -n '/^ labels:/,/^roleRef:/p' "${HELM_DIR}/templates/manager-rbac.yaml.bkp" | grep "app.kubernetes.io")
|
||||||
|
|
||||||
|
# create a new file from scratch with exactly what we want
|
||||||
|
{
|
||||||
|
# first section: Role/ClusterRole
|
||||||
|
echo "apiVersion: rbac.authorization.k8s.io/v1"
|
||||||
|
echo "{{- if and .Values.scopedNamespace .Values.scopedRBAC }}"
|
||||||
|
echo "kind: Role"
|
||||||
|
echo "{{- else }}"
|
||||||
|
echo "kind: ClusterRole"
|
||||||
|
echo "{{- end }}"
|
||||||
|
echo "metadata:"
|
||||||
|
echo " name: {{ include \"secrets-operator.fullname\" . }}-manager-role"
|
||||||
|
echo " {{- if and .Values.scopedNamespace .Values.scopedRBAC }}"
|
||||||
|
echo " namespace: {{ .Values.scopedNamespace | quote }}"
|
||||||
|
echo " {{- end }}"
|
||||||
|
echo " labels:"
|
||||||
|
echo " {{- include \"secrets-operator.labels\" . | nindent 4 }}"
|
||||||
|
|
||||||
|
# add the existing rules section from helm-generated file
|
||||||
|
echo "$rules_section"
|
||||||
|
|
||||||
|
# second section: RoleBinding/ClusterRoleBinding
|
||||||
|
echo "---"
|
||||||
|
echo "apiVersion: rbac.authorization.k8s.io/v1"
|
||||||
|
echo "{{- if and .Values.scopedNamespace .Values.scopedRBAC }}"
|
||||||
|
echo "kind: RoleBinding"
|
||||||
|
echo "{{- else }}"
|
||||||
|
echo "kind: ClusterRoleBinding"
|
||||||
|
echo "{{- end }}"
|
||||||
|
echo "metadata:"
|
||||||
|
echo " name: {{ include \"secrets-operator.fullname\" . }}-manager-rolebinding"
|
||||||
|
echo " {{- if and .Values.scopedNamespace .Values.scopedRBAC }}"
|
||||||
|
echo " namespace: {{ .Values.scopedNamespace | quote }}"
|
||||||
|
echo " {{- end }}"
|
||||||
|
echo " labels:"
|
||||||
|
echo "$original_labels"
|
||||||
|
echo " {{- include \"secrets-operator.labels\" . | nindent 4 }}"
|
||||||
|
|
||||||
|
# add the roleRef section with custom logic
|
||||||
|
echo "roleRef:"
|
||||||
|
echo " apiGroup: rbac.authorization.k8s.io"
|
||||||
|
echo " {{- if and .Values.scopedNamespace .Values.scopedRBAC }}"
|
||||||
|
echo " kind: Role"
|
||||||
|
echo " {{- else }}"
|
||||||
|
echo " kind: ClusterRole"
|
||||||
|
echo " {{- end }}"
|
||||||
|
echo " name: '{{ include \"secrets-operator.fullname\" . }}-manager-role'"
|
||||||
|
|
||||||
|
# add the subjects section
|
||||||
|
sed -n '/^subjects:/,$ p' "${HELM_DIR}/templates/manager-rbac.yaml.bkp"
|
||||||
|
} > "${HELM_DIR}/templates/manager-rbac.yaml.new"
|
||||||
|
|
||||||
|
mv "${HELM_DIR}/templates/manager-rbac.yaml.new" "${HELM_DIR}/templates/manager-rbac.yaml"
|
||||||
|
rm "${HELM_DIR}/templates/manager-rbac.yaml.bkp"
|
||||||
|
|
||||||
|
echo "Completed processing for manager-rbac.yaml with both role conditions and metadata applied"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ? NOTE(Daniel): Processes proxy-rbac.yaml and metrics-reader-rbac.yaml
|
||||||
|
for rbac_file in "${HELM_DIR}/templates/proxy-rbac.yaml" "${HELM_DIR}/templates/metrics-reader-rbac.yaml"; do
|
||||||
|
if [ -f "$rbac_file" ]; then
|
||||||
|
echo "Adding scopedNamespace condition to $(basename "$rbac_file")"
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "{{- if not .Values.scopedNamespace }}"
|
||||||
|
cat "$rbac_file"
|
||||||
|
echo ""
|
||||||
|
echo "{{- end }}"
|
||||||
|
} > "$rbac_file.new"
|
||||||
|
|
||||||
|
mv "$rbac_file.new" "$rbac_file"
|
||||||
|
|
||||||
|
echo "Completed processing for $(basename "$rbac_file")"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
|
||||||
|
# ? NOTE(Daniel): Processes metrics-service.yaml
|
||||||
|
if [ -f "${HELM_DIR}/templates/metrics-service.yaml" ]; then
|
||||||
|
echo "Processing metrics-service.yaml file specifically"
|
||||||
|
|
||||||
|
metrics_file="${HELM_DIR}/templates/metrics-service.yaml"
|
||||||
|
touch "${metrics_file}.new"
|
||||||
|
|
||||||
|
while IFS= read -r line; do
|
||||||
|
if [[ "$line" == *"{{- include \"secrets-operator.selectorLabels\" . | nindent 4 }}"* ]]; then
|
||||||
|
# keep original indentation for the selector labels line
|
||||||
|
echo " {{- include \"secrets-operator.selectorLabels\" . | nindent 4 }}" >> "${metrics_file}.new"
|
||||||
|
elif [[ "$line" == *"{{- .Values.metricsService.ports | toYaml | nindent 2 }}"* ]]; then
|
||||||
|
# fix indentation for the ports line - use less indentation here
|
||||||
|
echo " {{- .Values.metricsService.ports | toYaml | nindent 2 }}" >> "${metrics_file}.new"
|
||||||
|
else
|
||||||
|
echo "$line" >> "${metrics_file}.new"
|
||||||
|
fi
|
||||||
|
done < "${metrics_file}"
|
||||||
|
|
||||||
|
mv "${metrics_file}.new" "${metrics_file}"
|
||||||
|
echo "Completed processing for metrics_service.yaml"
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ? NOTE(Daniel): Processes deployment.yaml
|
||||||
|
if [ -f "${HELM_DIR}/templates/deployment.yaml" ]; then
|
||||||
|
echo "Processing deployment.yaml file"
|
||||||
|
|
||||||
|
touch "${HELM_DIR}/templates/deployment.yaml.new"
|
||||||
|
|
||||||
|
securityContext_replaced=0
|
||||||
|
in_first_securityContext=0
|
||||||
|
first_securityContext_found=0
|
||||||
|
|
||||||
|
# process the file line by line
|
||||||
|
while IFS= read -r line; do
|
||||||
|
# check if this is the first securityContext line (for kube-rbac-proxy)
|
||||||
|
if [[ "$line" =~ securityContext.*Values.controllerManager.kubeRbacProxy ]] && [ "$first_securityContext_found" -eq 0 ]; then
|
||||||
|
echo "$line" >> "${HELM_DIR}/templates/deployment.yaml.new"
|
||||||
|
first_securityContext_found=1
|
||||||
|
in_first_securityContext=1
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# check if this is the args line after the first securityContext
|
||||||
|
if [ "$in_first_securityContext" -eq 1 ] && [[ "$line" =~ args: ]]; then
|
||||||
|
# Add our custom args section with conditional logic
|
||||||
|
echo " - args:" >> "${HELM_DIR}/templates/deployment.yaml.new"
|
||||||
|
echo " {{- toYaml .Values.controllerManager.manager.args | nindent 8 }}" >> "${HELM_DIR}/templates/deployment.yaml.new"
|
||||||
|
echo " {{- if and .Values.scopedNamespace .Values.scopedRBAC }}" >> "${HELM_DIR}/templates/deployment.yaml.new"
|
||||||
|
echo " - --namespace={{ .Values.scopedNamespace }}" >> "${HELM_DIR}/templates/deployment.yaml.new"
|
||||||
|
echo " {{- end }}" >> "${HELM_DIR}/templates/deployment.yaml.new"
|
||||||
|
in_first_securityContext=0
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# check if this is the problematic pod securityContext line
|
||||||
|
if [[ "$line" =~ securityContext.*Values.controllerManager.podSecurityContext ]] && [ "$securityContext_replaced" -eq 0 ]; then
|
||||||
|
# Replace with our custom securityContext
|
||||||
|
echo " securityContext:" >> "${HELM_DIR}/templates/deployment.yaml.new"
|
||||||
|
echo " runAsNonRoot: true" >> "${HELM_DIR}/templates/deployment.yaml.new"
|
||||||
|
securityContext_replaced=1
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# skip the line if it's just the trailing part of the replacement
|
||||||
|
if [[ "$securityContext_replaced" -eq 1 ]] && [[ "$line" =~ ^[[:space:]]*[0-9]+[[:space:]]*\}\} ]]; then
|
||||||
|
# this is the trailing part of the template expression, skip it
|
||||||
|
securityContext_replaced=0
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# skip the simplified args line that replaced our custom one
|
||||||
|
if [[ "$line" =~ args:.*Values.controllerManager.manager.args ]]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$line" >> "${HELM_DIR}/templates/deployment.yaml.new"
|
||||||
|
done < "${HELM_DIR}/templates/deployment.yaml"
|
||||||
|
|
||||||
|
echo " nodeSelector: {{ toYaml .Values.controllerManager.nodeSelector | nindent 8 }}" >> "${HELM_DIR}/templates/deployment.yaml.new"
|
||||||
|
echo " tolerations: {{ toYaml .Values.controllerManager.tolerations | nindent 8 }}" >> "${HELM_DIR}/templates/deployment.yaml.new"
|
||||||
|
|
||||||
|
mv "${HELM_DIR}/templates/deployment.yaml.new" "${HELM_DIR}/templates/deployment.yaml"
|
||||||
|
echo "Completed processing for deployment.yaml"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ? NOTE(Daniel): Processes values.yaml
|
||||||
|
if [ -f "${HELM_DIR}/values.yaml" ]; then
|
||||||
|
echo "Processing values.yaml file"
|
||||||
|
|
||||||
|
# Create a temporary file
|
||||||
|
touch "${HELM_DIR}/values.yaml.new"
|
||||||
|
|
||||||
|
# Flag to track sections
|
||||||
|
in_resources_section=0
|
||||||
|
in_service_account=0
|
||||||
|
|
||||||
|
previous_line=""
|
||||||
|
# Process the file line by line
|
||||||
|
while IFS= read -r line; do
|
||||||
|
|
||||||
|
# Check if previous line includes infisical/kubernetes-operator and this line includes tag:
|
||||||
|
if [[ "$previous_line" =~ infisical/kubernetes-operator ]] && [[ "$line" =~ ^[[:space:]]*tag: ]]; then
|
||||||
|
# Get the indentation
|
||||||
|
indent=$(echo "$line" | sed 's/\(^[[:space:]]*\).*/\1/')
|
||||||
|
# Replace with our custom tag
|
||||||
|
echo "${indent}tag: <helm-pr-will-update-this-automatically>" >> "${HELM_DIR}/values.yaml.new"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
if [[ "$line" =~ resources: ]]; then
|
||||||
|
in_resources_section=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$line" =~ podSecurityContext: ]]; then
|
||||||
|
# skip this line and continue to the next line
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$line" =~ runAsNonRoot: ]] && [ "$in_resources_section" -eq 1 ]; then
|
||||||
|
# also skip this line and continue to the next line
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$line" =~ ^[[:space:]]*serviceAccount: ]]; then
|
||||||
|
# set the flag to 1 so we can continue to print the associated lines later
|
||||||
|
in_service_account=1
|
||||||
|
# print the current line
|
||||||
|
echo "$line" >> "${HELM_DIR}/values.yaml.new"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# process annotations under serviceAccount (only if in_service_account is true)
|
||||||
|
if [ "$in_service_account" -eq 1 ]; then
|
||||||
|
# Print the current line (annotations)
|
||||||
|
echo "$line" >> "${HELM_DIR}/values.yaml.new"
|
||||||
|
|
||||||
|
# if we've processed the annotations, add our new fields
|
||||||
|
if [[ "$line" =~ annotations: ]]; then
|
||||||
|
# get the base indentation level (of serviceAccount:)
|
||||||
|
base_indent=$(echo "$line" | sed 's/\(^[[:space:]]*\).*/\1/')
|
||||||
|
base_indent=${base_indent%??} # Remove two spaces to get to parent level
|
||||||
|
|
||||||
|
# add nodeSelector and tolerations at the same level as serviceAccount
|
||||||
|
echo "${base_indent}nodeSelector: {}" >> "${HELM_DIR}/values.yaml.new"
|
||||||
|
echo "${base_indent}tolerations: []" >> "${HELM_DIR}/values.yaml.new"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# exit serviceAccount section when we hit the next top-level item
|
||||||
|
if [[ "$line" =~ ^[[:space:]]{2}[a-zA-Z] ]] && ! [[ "$line" =~ annotations: ]]; then
|
||||||
|
in_service_account=0
|
||||||
|
fi
|
||||||
|
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# if we reach this point, we'll exit the resources section, this is the next top-level item
|
||||||
|
if [ "$in_resources_section" -eq 1 ] && [[ "$line" =~ ^[[:space:]]{2}[a-zA-Z] ]]; then
|
||||||
|
in_resources_section=0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# output the line unchanged
|
||||||
|
echo "$line" >> "${HELM_DIR}/values.yaml.new"
|
||||||
|
previous_line="$line"
|
||||||
|
done < "${HELM_DIR}/values.yaml"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# hacky, just append the kubernetesClusterDomain fields at the end of the file
|
||||||
|
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||||
|
# macOS version
|
||||||
|
sed -i '' '/kubernetesClusterDomain: /d' "${HELM_DIR}/values.yaml.new"
|
||||||
|
else
|
||||||
|
# Linux version
|
||||||
|
sed -i '/kubernetesClusterDomain: /d' "${HELM_DIR}/values.yaml.new"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "kubernetesClusterDomain: cluster.local" >> "${HELM_DIR}/values.yaml.new"
|
||||||
|
echo "scopedNamespace: \"\"" >> "${HELM_DIR}/values.yaml.new"
|
||||||
|
echo "scopedRBAC: false" >> "${HELM_DIR}/values.yaml.new"
|
||||||
|
echo "installCRDs: true" >> "${HELM_DIR}/values.yaml.new"
|
||||||
|
|
||||||
|
# replace the original file with the new one
|
||||||
|
mv "${HELM_DIR}/values.yaml.new" "${HELM_DIR}/values.yaml"
|
||||||
|
|
||||||
|
echo "Completed processing for values.yaml"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Helm chart generation complete with custom templating applied."
|
||||||
Executable
+37
@@ -0,0 +1,37 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd)
|
||||||
|
PATH_TO_HELM_CHART="${SCRIPT_DIR}/../../helm-charts/secrets-operator"
|
||||||
|
|
||||||
|
VERSION=$1
|
||||||
|
VERSION_WITHOUT_V=$(echo "$VERSION" | sed 's/^v//') # needed to validate semver
|
||||||
|
|
||||||
|
|
||||||
|
if [ -z "$VERSION" ]; then
|
||||||
|
echo "Usage: $0 <version>"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
if ! [[ "$VERSION_WITHOUT_V" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||||
|
echo "Error: Version must follow semantic versioning (e.g. 0.0.1)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! [[ "$VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||||
|
echo "Error: Version must start with 'v' (e.g. v0.0.1)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Linux vs macOS sed compatibility
|
||||||
|
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||||
|
# macOS version
|
||||||
|
sed -i '' -e '/repository: infisical\/kubernetes-operator/{n;s/tag: .*/tag: '"$VERSION"'/;}' "${PATH_TO_HELM_CHART}/values.yaml"
|
||||||
|
sed -i '' 's/appVersion: .*/appVersion: "'"$VERSION"'"/g' "${PATH_TO_HELM_CHART}/Chart.yaml"
|
||||||
|
sed -i '' 's/version: .*/version: '"$VERSION"'/g' "${PATH_TO_HELM_CHART}/Chart.yaml"
|
||||||
|
else
|
||||||
|
# Linux version
|
||||||
|
sed -i -e '/repository: infisical\/kubernetes-operator/{n;s/tag: .*/tag: '"$VERSION"'/;}' "${PATH_TO_HELM_CHART}/values.yaml"
|
||||||
|
sed -i 's/appVersion: .*/appVersion: "'"$VERSION"'"/g' "${PATH_TO_HELM_CHART}/Chart.yaml"
|
||||||
|
sed -i 's/version: .*/version: '"$VERSION"'/g' "${PATH_TO_HELM_CHART}/Chart.yaml"
|
||||||
|
fi
|
||||||
Reference in New Issue
Block a user