1
0
mirror of https://github.com/Infisical/infisical.git synced 2025-04-17 19:37:38 +00:00

Compare commits

..

2 Commits

Author SHA1 Message Date
c498178923 Update scim-service.ts 2025-04-10 18:10:58 +04:00
4b9f409ea5 fix: scim improvements and ui fixes 2025-03-25 07:12:56 +04:00
71 changed files with 507 additions and 1230 deletions
.github/workflows
backend/src
cli/packages/util
docs/documentation/platform
frontend/src
helpers
hooks
api
accessApproval
secretApproval
secretImports
utils
layouts/OrganizationLayout
ProductsSideBar
components/MinimizedOrgSidebar
pages
organization
AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection
BillingPage/components
BillingCloudTab
BillingTabGroup
project/AccessControlPage/components/MembersTab/components
secret-manager
OverviewPage
OverviewPage.tsx
components
SecretOverviewImportListView
SecretOverviewTableRow
SecretApprovalsPage/components
AccessApprovalRequest
ApprovalPolicyList/components
SecretApprovalRequest/components
helm-charts/secrets-operator
k8-operator

@ -1,27 +0,0 @@
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,103 +1,52 @@
name: Release K8 Operator Docker Image
name: Release image + Helm chart K8s Operator
on:
push:
tags:
- "infisical-k8-operator/v*.*.*"
push:
tags:
- "infisical-k8-operator/v*.*.*"
jobs:
release-image:
name: Generate Helm Chart PR
runs-on: ubuntu-latest
outputs:
pr_number: ${{ steps.create-pr.outputs.pull-request-number }}
steps:
- name: Extract version from tag
id: extract_version
run: echo "::set-output name=version::${GITHUB_REF_NAME#infisical-k8-operator/}"
release:
runs-on: ubuntu-latest
steps:
- name: Extract version from tag
id: extract_version
run: echo "::set-output name=version::${GITHUB_REF_NAME#infisical-k8-operator/}"
- uses: actions/checkout@v2
- name: Checkout code
uses: actions/checkout@v2
- name: 🔧 Set up QEMU
uses: docker/setup-qemu-action@v1
# Dependency for helm generation
- name: Install Helm
uses: azure/setup-helm@v3
with:
version: v3.10.0
- name: 🔧 Set up Docker Buildx
uses: docker/setup-buildx-action@v1
# Dependency for helm generation
- name: Install Go
uses: actions/setup-go@v4
with:
go-version: 1.21
- name: 🐋 Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
# Install binaries for helm generation
- name: Install dependencies
working-directory: k8-operator
run: |
make helmify
make kustomize
make controller-gen
- 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 }}
- name: Generate Helm Chart
working-directory: k8-operator
run: make helm
- name: Update Helm Chart Version
run: ./k8-operator/scripts/update-version.sh ${{ steps.extract_version.outputs.version }}
- name: Debug - Check file changes
run: |
echo "Current git status:"
git status
echo ""
echo "Modified files:"
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 <noreply@github.com>
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 }}
- 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,29 +0,0 @@
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,8 +16,7 @@ export const AccessApprovalPoliciesSchema = z.object({
createdAt: z.date(),
updatedAt: z.date(),
enforcementLevel: z.string().default("hard"),
deletedAt: z.date().nullable().optional(),
allowedSelfApprovals: z.boolean().default(true)
deletedAt: z.date().nullable().optional()
});
export type TAccessApprovalPolicies = z.infer<typeof AccessApprovalPoliciesSchema>;

@ -16,8 +16,7 @@ export const SecretApprovalPoliciesSchema = z.object({
createdAt: z.date(),
updatedAt: z.date(),
enforcementLevel: z.string().default("hard"),
deletedAt: z.date().nullable().optional(),
allowedSelfApprovals: z.boolean().default(true)
deletedAt: z.date().nullable().optional()
});
export type TSecretApprovalPolicies = z.infer<typeof SecretApprovalPoliciesSchema>;

@ -29,8 +29,7 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
.array()
.min(1, { message: "At least one approver should be provided" }),
approvals: z.number().min(1).default(1),
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard),
allowedSelfApprovals: z.boolean().default(true)
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard)
}),
response: {
200: z.object({
@ -148,8 +147,7 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
.array()
.min(1, { message: "At least one approver should be provided" }),
approvals: z.number().min(1).optional(),
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard),
allowedSelfApprovals: z.boolean().default(true)
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard)
}),
response: {
200: z.object({

@ -110,8 +110,7 @@ export const registerAccessApprovalRequestRouter = async (server: FastifyZodProv
secretPath: z.string().nullish(),
envId: z.string(),
enforcementLevel: z.string(),
deletedAt: z.date().nullish(),
allowedSelfApprovals: z.boolean()
deletedAt: z.date().nullish()
}),
reviewers: z
.object({

@ -35,8 +35,7 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
.array()
.min(1, { message: "At least one approver should be provided" }),
approvals: z.number().min(1).default(1),
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard),
allowedSelfApprovals: z.boolean().default(true)
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard)
}),
response: {
200: z.object({
@ -86,8 +85,7 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
.nullable()
.transform((val) => (val ? removeTrailingSlash(val) : val))
.transform((val) => (val === "" ? "/" : val)),
enforcementLevel: z.nativeEnum(EnforcementLevel).optional(),
allowedSelfApprovals: z.boolean().default(true)
enforcementLevel: z.nativeEnum(EnforcementLevel).optional()
}),
response: {
200: z.object({

@ -49,8 +49,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
.array(),
secretPath: z.string().optional().nullable(),
enforcementLevel: z.string(),
deletedAt: z.date().nullish(),
allowedSelfApprovals: z.boolean()
deletedAt: z.date().nullish()
}),
committerUser: approvalRequestUser,
commits: z.object({ op: z.string(), secretId: z.string().nullable().optional() }).array(),
@ -268,8 +267,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
approvers: approvalRequestUser.array(),
secretPath: z.string().optional().nullable(),
enforcementLevel: z.string(),
deletedAt: z.date().nullish(),
allowedSelfApprovals: z.boolean()
deletedAt: z.date().nullish()
}),
environment: z.string(),
statusChangedByUser: approvalRequestUser.optional(),

@ -5,11 +5,9 @@ import { SshCertType } from "@app/ee/services/ssh/ssh-certificate-authority-type
import { SSH_CERTIFICATE_AUTHORITIES } from "@app/lib/api-docs";
import { ms } from "@app/lib/ms";
import { writeLimit } from "@app/server/config/rateLimiter";
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { CertKeyAlgorithm } from "@app/services/certificate/certificate-types";
import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";
export const registerSshCertRouter = async (server: FastifyZodProvider) => {
server.route({
@ -75,16 +73,6 @@ export const registerSshCertRouter = async (server: FastifyZodProvider) => {
}
});
await server.services.telemetry.sendPostHogEvents({
event: PostHogEventTypes.SignSshKey,
distinctId: getTelemetryDistinctId(req),
properties: {
certificateTemplateId: req.body.certificateTemplateId,
principals: req.body.principals,
...req.auditLogInfo
}
});
return {
serialNumber,
signedKey: signedPublicKey
@ -164,16 +152,6 @@ export const registerSshCertRouter = async (server: FastifyZodProvider) => {
}
});
await server.services.telemetry.sendPostHogEvents({
event: PostHogEventTypes.IssueSshCreds,
distinctId: getTelemetryDistinctId(req),
properties: {
certificateTemplateId: req.body.certificateTemplateId,
principals: req.body.principals,
...req.auditLogInfo
}
});
return {
serialNumber,
signedKey: signedPublicKey,

@ -65,8 +65,7 @@ export const accessApprovalPolicyServiceFactory = ({
approvers,
projectSlug,
environment,
enforcementLevel,
allowedSelfApprovals
enforcementLevel
}: TCreateAccessApprovalPolicy) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
@ -154,8 +153,7 @@ export const accessApprovalPolicyServiceFactory = ({
approvals,
secretPath,
name,
enforcementLevel,
allowedSelfApprovals
enforcementLevel
},
tx
);
@ -218,8 +216,7 @@ export const accessApprovalPolicyServiceFactory = ({
actorOrgId,
actorAuthMethod,
approvals,
enforcementLevel,
allowedSelfApprovals
enforcementLevel
}: TUpdateAccessApprovalPolicy) => {
const groupApprovers = approvers
.filter((approver) => approver.type === ApproverType.Group)
@ -265,8 +262,7 @@ export const accessApprovalPolicyServiceFactory = ({
approvals,
secretPath,
name,
enforcementLevel,
allowedSelfApprovals
enforcementLevel
},
tx
);

@ -26,7 +26,6 @@ export type TCreateAccessApprovalPolicy = {
projectSlug: string;
name: string;
enforcementLevel: EnforcementLevel;
allowedSelfApprovals: boolean;
} & Omit<TProjectPermission, "projectId">;
export type TUpdateAccessApprovalPolicy = {
@ -36,7 +35,6 @@ export type TUpdateAccessApprovalPolicy = {
secretPath?: string;
name?: string;
enforcementLevel?: EnforcementLevel;
allowedSelfApprovals: boolean;
} & Omit<TProjectPermission, "projectId">;
export type TDeleteAccessApprovalPolicy = {

@ -61,7 +61,6 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
db.ref("approvals").withSchema(TableName.AccessApprovalPolicy).as("policyApprovals"),
db.ref("secretPath").withSchema(TableName.AccessApprovalPolicy).as("policySecretPath"),
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("deletedAt").withSchema(TableName.AccessApprovalPolicy).as("policyDeletedAt")
)
@ -120,7 +119,6 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
approvals: doc.policyApprovals,
secretPath: doc.policySecretPath,
enforcementLevel: doc.policyEnforcementLevel,
allowedSelfApprovals: doc.policyAllowedSelfApprovals,
envId: doc.policyEnvId,
deletedAt: doc.policyDeletedAt
},
@ -256,7 +254,6 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
tx.ref("slug").withSchema(TableName.Environment).as("environment"),
tx.ref("secretPath").withSchema(TableName.AccessApprovalPolicy).as("policySecretPath"),
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("deletedAt").withSchema(TableName.AccessApprovalPolicy).as("policyDeletedAt")
);
@ -278,7 +275,6 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
approvals: el.policyApprovals,
secretPath: el.policySecretPath,
enforcementLevel: el.policyEnforcementLevel,
allowedSelfApprovals: el.policyAllowedSelfApprovals,
deletedAt: el.policyDeletedAt
},
requestedByUser: {

@ -320,11 +320,6 @@ export const accessApprovalRequestServiceFactory = ({
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({
actor,

@ -21,12 +21,7 @@ const generateUsername = () => {
export const CassandraProvider = (): TDynamicProviderFns => {
const validateProviderInputs = async (inputs: unknown) => {
const providerInputs = await DynamicSecretCassandraSchema.parseAsync(inputs);
const hostIps = await Promise.all(
providerInputs.host
.split(",")
.filter(Boolean)
.map((el) => verifyHostInputValidity(el).then((ip) => ip[0]))
);
const [hostIp] = await verifyHostInputValidity(providerInputs.host);
validateHandlebarTemplate("Cassandra creation", providerInputs.creationStatement, {
allowedExpressions: (val) => ["username", "password", "expiration", "keyspace"].includes(val)
});
@ -39,10 +34,10 @@ export const CassandraProvider = (): TDynamicProviderFns => {
allowedExpressions: (val) => ["username"].includes(val)
});
return { ...providerInputs, hostIps };
return { ...providerInputs, host: hostIp };
};
const $getClient = async (providerInputs: z.infer<typeof DynamicSecretCassandraSchema> & { hostIps: string[] }) => {
const $getClient = async (providerInputs: z.infer<typeof DynamicSecretCassandraSchema>) => {
const sslOptions = providerInputs.ca ? { rejectUnauthorized: false, ca: providerInputs.ca } : undefined;
const client = new cassandra.Client({
sslOptions,
@ -55,7 +50,7 @@ export const CassandraProvider = (): TDynamicProviderFns => {
},
keyspace: providerInputs.keyspace,
localDataCenter: providerInputs?.localDataCenter,
contactPoints: providerInputs.hostIps
contactPoints: providerInputs.host.split(",").filter(Boolean)
});
return client;
};

@ -20,13 +20,13 @@ export const ElasticSearchProvider = (): TDynamicProviderFns => {
const validateProviderInputs = async (inputs: unknown) => {
const providerInputs = await DynamicSecretElasticSearchSchema.parseAsync(inputs);
const [hostIp] = await verifyHostInputValidity(providerInputs.host);
return { ...providerInputs, hostIp };
return { ...providerInputs, host: hostIp };
};
const $getClient = async (providerInputs: z.infer<typeof DynamicSecretElasticSearchSchema> & { hostIp: string }) => {
const $getClient = async (providerInputs: z.infer<typeof DynamicSecretElasticSearchSchema>) => {
const connection = new ElasticSearchClient({
node: {
url: new URL(`${providerInputs.hostIp}:${providerInputs.port}`),
url: new URL(`${providerInputs.host}:${providerInputs.port}`),
...(providerInputs.ca && {
ssl: {
rejectUnauthorized: false,

@ -20,14 +20,14 @@ export const MongoDBProvider = (): TDynamicProviderFns => {
const validateProviderInputs = async (inputs: unknown) => {
const providerInputs = await DynamicSecretMongoDBSchema.parseAsync(inputs);
const [hostIp] = await verifyHostInputValidity(providerInputs.host);
return { ...providerInputs, hostIp };
return { ...providerInputs, host: hostIp };
};
const $getClient = async (providerInputs: z.infer<typeof DynamicSecretMongoDBSchema> & { hostIp: string }) => {
const $getClient = async (providerInputs: z.infer<typeof DynamicSecretMongoDBSchema>) => {
const isSrv = !providerInputs.port;
const uri = isSrv
? `mongodb+srv://${providerInputs.hostIp}`
: `mongodb://${providerInputs.hostIp}:${providerInputs.port}`;
? `mongodb+srv://${providerInputs.host}`
: `mongodb://${providerInputs.host}:${providerInputs.port}`;
const client = new MongoClient(uri, {
auth: {

@ -3,6 +3,7 @@ import https from "https";
import { customAlphabet } from "nanoid";
import { z } from "zod";
import { removeTrailingSlash } from "@app/lib/fn";
import { logger } from "@app/lib/logger";
import { alphaNumericNanoId } from "@app/lib/nanoid";
@ -79,12 +80,12 @@ export const RabbitMqProvider = (): TDynamicProviderFns => {
const validateProviderInputs = async (inputs: unknown) => {
const providerInputs = await DynamicSecretRabbitMqSchema.parseAsync(inputs);
const [hostIp] = await verifyHostInputValidity(providerInputs.host);
return { ...providerInputs, hostIp };
return { ...providerInputs, host: hostIp };
};
const $getClient = async (providerInputs: z.infer<typeof DynamicSecretRabbitMqSchema> & { hostIp: string }) => {
const $getClient = async (providerInputs: z.infer<typeof DynamicSecretRabbitMqSchema>) => {
const axiosInstance = axios.create({
baseURL: `${providerInputs.hostIp}:${providerInputs.port}/api`,
baseURL: `${removeTrailingSlash(providerInputs.host)}:${providerInputs.port}/api`,
auth: {
username: providerInputs.username,
password: providerInputs.password

@ -65,15 +65,15 @@ export const RedisDatabaseProvider = (): TDynamicProviderFns => {
allowedExpressions: (val) => ["username"].includes(val)
});
return { ...providerInputs, hostIp };
return { ...providerInputs, host: hostIp };
};
const $getClient = async (providerInputs: z.infer<typeof DynamicSecretRedisDBSchema> & { hostIp: string }) => {
const $getClient = async (providerInputs: z.infer<typeof DynamicSecretRedisDBSchema>) => {
let connection: Redis | null = null;
try {
connection = new Redis({
username: providerInputs.username,
host: providerInputs.hostIp,
host: providerInputs.host,
port: providerInputs.port,
password: providerInputs.password,
...(providerInputs.ca && {

@ -37,16 +37,13 @@ export const SapAseProvider = (): TDynamicProviderFns => {
allowedExpressions: (val) => ["username"].includes(val)
});
}
return { ...providerInputs, hostIp };
return { ...providerInputs, host: hostIp };
};
const $getClient = async (
providerInputs: z.infer<typeof DynamicSecretSapAseSchema> & { hostIp: string },
useMaster?: boolean
) => {
const $getClient = async (providerInputs: z.infer<typeof DynamicSecretSapAseSchema>, useMaster?: boolean) => {
const connectionString =
`DRIVER={FreeTDS};` +
`SERVER=${providerInputs.hostIp};` +
`SERVER=${providerInputs.host};` +
`PORT=${providerInputs.port};` +
`DATABASE=${useMaster ? "master" : providerInputs.database};` +
`UID=${providerInputs.username};` +

@ -41,12 +41,12 @@ export const SapHanaProvider = (): TDynamicProviderFns => {
validateHandlebarTemplate("SAP Hana revoke", providerInputs.revocationStatement, {
allowedExpressions: (val) => ["username"].includes(val)
});
return { ...providerInputs, hostIp };
return { ...providerInputs, host: hostIp };
};
const $getClient = async (providerInputs: z.infer<typeof DynamicSecretSapHanaSchema> & { hostIp: string }) => {
const $getClient = async (providerInputs: z.infer<typeof DynamicSecretSapHanaSchema>) => {
const client = hdb.createClient({
host: providerInputs.hostIp,
host: providerInputs.host,
port: providerInputs.port,
user: providerInputs.username,
password: providerInputs.password,

@ -132,7 +132,7 @@ export const SqlDatabaseProvider = ({ gatewayService }: TSqlDatabaseProviderDTO)
allowedExpressions: (val) => ["username", "database"].includes(val)
});
return { ...providerInputs, hostIp };
return { ...providerInputs, host: hostIp };
};
const $getClient = async (providerInputs: z.infer<typeof DynamicSecretSqlDBSchema>) => {
@ -193,7 +193,7 @@ export const SqlDatabaseProvider = ({ gatewayService }: TSqlDatabaseProviderDTO)
const validateConnection = async (inputs: unknown) => {
const providerInputs = await validateProviderInputs(inputs);
let isConnected = false;
const gatewayCallback = async (host = providerInputs.hostIp, port = providerInputs.port) => {
const gatewayCallback = async (host = providerInputs.host, port = providerInputs.port) => {
const db = await $getClient({ ...providerInputs, port, host });
// oracle needs from keyword
const testStatement = providerInputs.client === SqlProviders.Oracle ? "SELECT 1 FROM DUAL" : "SELECT 1";

@ -1,24 +0,0 @@
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,13 +12,10 @@ import { getConfig } from "@app/lib/config/env";
import { verifyOfflineLicense } from "@app/lib/crypto";
import { NotFoundError } from "@app/lib/errors";
import { logger } from "@app/lib/logger";
import { TIdentityOrgDALFactory } from "@app/services/identity/identity-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 { TPermissionServiceFactory } from "../permission/permission-service";
import { BillingPlanRows, BillingPlanTableHead } from "./licence-enums";
import { TLicenseDALFactory } from "./license-dal";
import { getDefaultOnPremFeatures, setupLicenseRequestWithStore } from "./license-fns";
import {
@ -31,7 +28,6 @@ import {
TFeatureSet,
TGetOrgBillInfoDTO,
TGetOrgTaxIdDTO,
TOfflineLicense,
TOfflineLicenseContents,
TOrgInvoiceDTO,
TOrgLicensesDTO,
@ -43,12 +39,10 @@ import {
} from "./license-types";
type TLicenseServiceFactoryDep = {
orgDAL: Pick<TOrgDALFactory, "findOrgById" | "countAllOrgMembers">;
orgDAL: Pick<TOrgDALFactory, "findOrgById">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
licenseDAL: TLicenseDALFactory;
keyStore: Pick<TKeyStoreFactory, "setItemWithExpiry" | "getItem" | "deleteItem">;
identityOrgMembershipDAL: TIdentityOrgDALFactory;
projectDAL: TProjectDALFactory;
};
export type TLicenseServiceFactory = ReturnType<typeof licenseServiceFactory>;
@ -63,14 +57,11 @@ export const licenseServiceFactory = ({
orgDAL,
permissionService,
licenseDAL,
keyStore,
identityOrgMembershipDAL,
projectDAL
keyStore
}: TLicenseServiceFactoryDep) => {
let isValidLicense = false;
let instanceType = InstanceType.OnPrem;
let onPremFeatures: TFeatureSet = getDefaultOnPremFeatures();
let selfHostedLicense: TOfflineLicense | null = null;
const appCfg = getConfig();
const licenseServerCloudApi = setupLicenseRequestWithStore(
@ -134,7 +125,6 @@ export const licenseServiceFactory = ({
instanceType = InstanceType.EnterpriseOnPremOffline;
logger.info(`Instance type: ${InstanceType.EnterpriseOnPremOffline}`);
isValidLicense = true;
selfHostedLicense = contents.license;
return;
}
}
@ -358,21 +348,10 @@ export const licenseServiceFactory = ({
message: `Organization with ID '${orgId}' not found`
});
}
if (instanceType !== InstanceType.OnPrem && instanceType !== InstanceType.EnterpriseOnPremOffline) {
const { data } = await licenseServerCloudApi.request.get(
`/api/license-server/v1/customers/${organization.customerId}/cloud-plan/billing`
);
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
};
const { data } = await licenseServerCloudApi.request.get(
`/api/license-server/v1/customers/${organization.customerId}/cloud-plan/billing`
);
return data;
};
// returns org current plan feature table
@ -386,41 +365,10 @@ export const licenseServiceFactory = ({
message: `Organization with ID '${orgId}' not found`
});
}
if (instanceType !== InstanceType.OnPrem && instanceType !== InstanceType.EnterpriseOnPremOffline) {
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
};
})
const { data } = await licenseServerCloudApi.request.get(
`/api/license-server/v1/customers/${organization.customerId}/cloud-plan/table`
);
return {
head: Object.values(BillingPlanTableHead),
rows: mappedRows
};
return data;
};
const getOrgBillingDetails = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgBillInfoDTO) => {

@ -594,6 +594,7 @@ export const scimServiceFactory = ({
},
tx
);
await orgMembershipDAL.updateById(
membership.id,
{

@ -62,8 +62,7 @@ export const secretApprovalPolicyServiceFactory = ({
projectId,
secretPath,
environment,
enforcementLevel,
allowedSelfApprovals
enforcementLevel
}: TCreateSapDTO) => {
const groupApprovers = approvers
?.filter((approver) => approver.type === ApproverType.Group)
@ -114,8 +113,7 @@ export const secretApprovalPolicyServiceFactory = ({
approvals,
secretPath,
name,
enforcementLevel,
allowedSelfApprovals
enforcementLevel
},
tx
);
@ -174,8 +172,7 @@ export const secretApprovalPolicyServiceFactory = ({
actorAuthMethod,
approvals,
secretPolicyId,
enforcementLevel,
allowedSelfApprovals
enforcementLevel
}: TUpdateSapDTO) => {
const groupApprovers = approvers
?.filter((approver) => approver.type === ApproverType.Group)
@ -221,8 +218,7 @@ export const secretApprovalPolicyServiceFactory = ({
approvals,
secretPath,
name,
enforcementLevel,
allowedSelfApprovals
enforcementLevel
},
tx
);

@ -10,7 +10,6 @@ export type TCreateSapDTO = {
projectId: string;
name: string;
enforcementLevel: EnforcementLevel;
allowedSelfApprovals: boolean;
} & Omit<TProjectPermission, "projectId">;
export type TUpdateSapDTO = {
@ -20,7 +19,6 @@ export type TUpdateSapDTO = {
approvers: ({ type: ApproverType.Group; id: string } | { type: ApproverType.User; id?: string; name?: string })[];
name?: string;
enforcementLevel?: EnforcementLevel;
allowedSelfApprovals?: boolean;
} & Omit<TProjectPermission, "projectId">;
export type TDeleteSapDTO = {

@ -112,7 +112,6 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
tx.ref("secretPath").withSchema(TableName.SecretApprovalPolicy).as("policySecretPath"),
tx.ref("envId").withSchema(TableName.SecretApprovalPolicy).as("policyEnvId"),
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("deletedAt").withSchema(TableName.SecretApprovalPolicy).as("policyDeletedAt")
);
@ -151,8 +150,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
secretPath: el.policySecretPath,
enforcementLevel: el.policyEnforcementLevel,
envId: el.policyEnvId,
deletedAt: el.policyDeletedAt,
allowedSelfApprovals: el.policyAllowedSelfApprovals
deletedAt: el.policyDeletedAt
}
}),
childrenMapper: [
@ -338,7 +336,6 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
),
db.ref("secretPath").withSchema(TableName.SecretApprovalPolicy).as("policySecretPath"),
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("approverUserId").withSchema(TableName.SecretApprovalPolicyApprover),
db.ref("userId").withSchema(TableName.UserGroupMembership).as("approverGroupUserId"),
@ -367,8 +364,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
name: el.policyName,
approvals: el.policyApprovals,
secretPath: el.policySecretPath,
enforcementLevel: el.policyEnforcementLevel,
allowedSelfApprovals: el.policyAllowedSelfApprovals
enforcementLevel: el.policyEnforcementLevel
},
committerUser: {
userId: el.committerUserId,
@ -486,7 +482,6 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
`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("allowedSelfApprovals").withSchema(TableName.SecretApprovalPolicy).as("policyAllowedSelfApprovals"),
db.ref("approvals").withSchema(TableName.SecretApprovalPolicy).as("policyApprovals"),
db.ref("enforcementLevel").withSchema(TableName.SecretApprovalPolicy).as("policyEnforcementLevel"),
db.ref("approverUserId").withSchema(TableName.SecretApprovalPolicyApprover),
@ -516,8 +511,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
name: el.policyName,
approvals: el.policyApprovals,
secretPath: el.policySecretPath,
enforcementLevel: el.policyEnforcementLevel,
allowedSelfApprovals: el.policyAllowedSelfApprovals
enforcementLevel: el.policyEnforcementLevel
},
committerUser: {
userId: el.committerUserId,

@ -352,11 +352,6 @@ export const secretApprovalRequestServiceFactory = ({
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({
actor: ActorType.USER,

@ -413,14 +413,7 @@ export const registerRoutes = async (
serviceTokenDAL,
projectDAL
});
const licenseService = licenseServiceFactory({
permissionService,
orgDAL,
licenseDAL,
keyStore,
identityOrgMembershipDAL,
projectDAL
});
const licenseService = licenseServiceFactory({ permissionService, orgDAL, licenseDAL, keyStore });
const hsmService = hsmServiceFactory({
hsmModule,

@ -6,7 +6,6 @@ import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { CERTIFICATE_AUTHORITIES } from "@app/lib/api-docs";
import { ms } from "@app/lib/ms";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { CertExtendedKeyUsage, CertKeyAlgorithm, CertKeyUsage } from "@app/services/certificate/certificate-types";
@ -15,7 +14,6 @@ import {
validateAltNamesField,
validateCaDateField
} from "@app/services/certificate-authority/certificate-authority-validators";
import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";
export const registerCaRouter = async (server: FastifyZodProvider) => {
server.route({
@ -651,16 +649,6 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
}
});
await server.services.telemetry.sendPostHogEvents({
event: PostHogEventTypes.IssueCert,
distinctId: getTelemetryDistinctId(req),
properties: {
caId: ca.id,
commonName: req.body.commonName,
...req.auditLogInfo
}
});
return {
certificate,
certificateChain,
@ -719,7 +707,7 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
}
},
handler: async (req) => {
const { certificate, certificateChain, issuingCaCertificate, serialNumber, ca, commonName } =
const { certificate, certificateChain, issuingCaCertificate, serialNumber, ca } =
await server.services.certificateAuthority.signCertFromCa({
isInternal: false,
caId: req.params.caId,
@ -743,16 +731,6 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
}
});
await server.services.telemetry.sendPostHogEvents({
event: PostHogEventTypes.SignCert,
distinctId: getTelemetryDistinctId(req),
properties: {
caId: ca.id,
commonName,
...req.auditLogInfo
}
});
return {
certificate: certificate.toString("pem"),
certificateChain,

@ -5,7 +5,6 @@ import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { CERTIFICATE_AUTHORITIES, CERTIFICATES } from "@app/lib/api-docs";
import { ms } from "@app/lib/ms";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { CertExtendedKeyUsage, CertKeyUsage, CrlReason } from "@app/services/certificate/certificate-types";
@ -13,7 +12,6 @@ import {
validateAltNamesField,
validateCaDateField
} from "@app/services/certificate-authority/certificate-authority-validators";
import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";
export const registerCertRouter = async (server: FastifyZodProvider) => {
server.route({
@ -152,17 +150,6 @@ export const registerCertRouter = async (server: FastifyZodProvider) => {
}
});
await server.services.telemetry.sendPostHogEvents({
event: PostHogEventTypes.IssueCert,
distinctId: getTelemetryDistinctId(req),
properties: {
caId: req.body.caId,
certificateTemplateId: req.body.certificateTemplateId,
commonName: req.body.commonName,
...req.auditLogInfo
}
});
return {
certificate,
certificateChain,
@ -241,7 +228,7 @@ export const registerCertRouter = async (server: FastifyZodProvider) => {
}
},
handler: async (req) => {
const { certificate, certificateChain, issuingCaCertificate, serialNumber, ca, commonName } =
const { certificate, certificateChain, issuingCaCertificate, serialNumber, ca } =
await server.services.certificateAuthority.signCertFromCa({
isInternal: false,
actor: req.permission.type,
@ -264,17 +251,6 @@ export const registerCertRouter = async (server: FastifyZodProvider) => {
}
});
await server.services.telemetry.sendPostHogEvents({
event: PostHogEventTypes.SignCert,
distinctId: getTelemetryDistinctId(req),
properties: {
caId: req.body.caId,
certificateTemplateId: req.body.certificateTemplateId,
commonName,
...req.auditLogInfo
}
});
return {
certificate: certificate.toString("pem"),
certificateChain,

@ -1819,8 +1819,7 @@ export const certificateAuthorityServiceFactory = ({
certificateChain: `${issuingCaCertificate}\n${caCertChain}`.trim(),
issuingCaCertificate,
serialNumber,
ca,
commonName: cn
ca
};
};

@ -64,11 +64,9 @@ export const identityUaServiceFactory = ({
ipAddress: ip,
trustedIps: identityUa.clientSecretTrustedIps as TIp[]
});
const clientSecretPrefix = clientSecret.slice(0, 4);
const clientSecrtInfo = await identityUaClientSecretDAL.find({
identityUAId: identityUa.id,
isClientSecretRevoked: false,
clientSecretPrefix
isClientSecretRevoked: false
});
let validClientSecretInfo: (typeof clientSecrtInfo)[0] | null = null;
@ -253,17 +251,14 @@ export const identityUaServiceFactory = ({
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
const uaIdentityAuth = await identityUaDAL.findOne({ identityId });
if (!uaIdentityAuth) {
throw new NotFoundError({ message: `Failed to find universal auth for identity with ID ${identityId}` });
}
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.UNIVERSAL_AUTH)) {
throw new BadRequestError({
message: "The identity does not have universal auth"
});
}
const uaIdentityAuth = await identityUaDAL.findOne({ identityId });
if (
(accessTokenMaxTTL || uaIdentityAuth.accessTokenMaxTTL) > 0 &&
(accessTokenTTL || uaIdentityAuth.accessTokenMaxTTL) > (accessTokenMaxTTL || uaIdentityAuth.accessTokenMaxTTL)
@ -332,17 +327,14 @@ export const identityUaServiceFactory = ({
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
const uaIdentityAuth = await identityUaDAL.findOne({ identityId });
if (!uaIdentityAuth) {
throw new NotFoundError({ message: `Failed to find universal auth for identity with ID ${identityId}` });
}
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.UNIVERSAL_AUTH)) {
throw new BadRequestError({
message: "The identity does not have universal auth"
});
}
const uaIdentityAuth = await identityUaDAL.findOne({ identityId });
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,

@ -15,11 +15,7 @@ export enum PostHogEventTypes {
UserOrgInvitation = "User Org Invitation",
TelemetryInstanceStats = "Self Hosted Instance Stats",
SecretRequestCreated = "Secret Request Created",
SecretRequestDeleted = "Secret Request Deleted",
SignSshKey = "Sign SSH Key",
IssueSshCreds = "Issue SSH Credentials",
SignCert = "Sign PKI Certificate",
IssueCert = "Issue PKI Certificate"
SecretRequestDeleted = "Secret Request Deleted"
}
export type TSecretModifiedEvent = {
@ -143,44 +139,6 @@ export type TSecretRequestDeletedEvent = {
};
};
export type TSignSshKeyEvent = {
event: PostHogEventTypes.SignSshKey;
properties: {
certificateTemplateId: string;
principals: string[];
userAgent?: string;
};
};
export type TIssueSshCredsEvent = {
event: PostHogEventTypes.IssueSshCreds;
properties: {
certificateTemplateId: string;
principals: string[];
userAgent?: string;
};
};
export type TSignCertificateEvent = {
event: PostHogEventTypes.SignCert;
properties: {
caId?: string;
certificateTemplateId?: string;
commonName: string;
userAgent?: string;
};
};
export type TIssueCertificateEvent = {
event: PostHogEventTypes.IssueCert;
properties: {
caId?: string;
certificateTemplateId?: string;
commonName: string;
userAgent?: string;
};
};
export type TPostHogEvent = { distinctId: string } & (
| TSecretModifiedEvent
| TAdminInitEvent
@ -193,8 +151,4 @@ export type TPostHogEvent = { distinctId: string } & (
| TTelemetryInstanceStatsEvent
| TSecretRequestCreatedEvent
| TSecretRequestDeletedEvent
| TSignSshKeyEvent
| TIssueSshCredsEvent
| TSignCertificateEvent
| TIssueCertificateEvent
);

@ -56,7 +56,6 @@ func WriteInitalConfig(userCredentials *models.UserCredentials) error {
LoggedInUsers: existingConfigFile.LoggedInUsers,
VaultBackendType: existingConfigFile.VaultBackendType,
VaultBackendPassphrase: existingConfigFile.VaultBackendPassphrase,
Domains: existingConfigFile.Domains,
}
configFileMarshalled, err := json.Marshal(configFile)

@ -1,6 +1,6 @@
---
title: "Overview"
description: "Track all actions performed within Infisical"
description: "Track evert event action performed within Infisical projects."
---
<Info>

@ -1,5 +1,4 @@
export const isInfisicalCloud = () =>
window.location.origin.includes("https://app.infisical.com") ||
window.location.origin.includes("https://us.infisical.com") ||
window.location.origin.includes("https://eu.infisical.com") ||
window.location.origin.includes("https://gamma.infisical.com");
window.location.origin.includes("https://eu.infisical.com");

@ -23,8 +23,7 @@ export const useCreateAccessApprovalPolicy = () => {
approvers,
name,
secretPath,
enforcementLevel,
allowedSelfApprovals
enforcementLevel
}) => {
const { data } = await apiRequest.post("/api/v1/access-approvals/policies", {
environment,
@ -33,8 +32,7 @@ export const useCreateAccessApprovalPolicy = () => {
approvers,
secretPath,
name,
enforcementLevel,
allowedSelfApprovals
enforcementLevel
});
return data;
},
@ -50,22 +48,13 @@ export const useUpdateAccessApprovalPolicy = () => {
const queryClient = useQueryClient();
return useMutation<object, object, TUpdateAccessPolicyDTO>({
mutationFn: async ({
id,
approvers,
approvals,
name,
secretPath,
enforcementLevel,
allowedSelfApprovals
}) => {
mutationFn: async ({ id, approvers, approvals, name, secretPath, enforcementLevel }) => {
const { data } = await apiRequest.patch(`/api/v1/access-approvals/policies/${id}`, {
approvals,
approvers,
secretPath,
name,
enforcementLevel,
allowedSelfApprovals
enforcementLevel
});
return data;
},

@ -16,7 +16,6 @@ export type TAccessApprovalPolicy = {
enforcementLevel: EnforcementLevel;
updatedAt: Date;
approvers?: Approver[];
allowedSelfApprovals: boolean;
};
export enum ApproverType {
@ -72,7 +71,6 @@ export type TAccessApprovalRequest = {
envId: string;
enforcementLevel: EnforcementLevel;
deletedAt: Date | null;
allowedSelfApprovals: boolean;
};
reviewers: {
@ -146,7 +144,6 @@ export type TCreateAccessPolicyDTO = {
approvals?: number;
secretPath?: string;
enforcementLevel?: EnforcementLevel;
allowedSelfApprovals: boolean;
};
export type TUpdateAccessPolicyDTO = {
@ -157,7 +154,6 @@ export type TUpdateAccessPolicyDTO = {
environment?: string;
approvals?: number;
enforcementLevel?: EnforcementLevel;
allowedSelfApprovals: boolean;
// for invalidating list
projectSlug: string;
};

@ -16,8 +16,7 @@ export const useCreateSecretApprovalPolicy = () => {
approvers,
secretPath,
name,
enforcementLevel,
allowedSelfApprovals
enforcementLevel
}) => {
const { data } = await apiRequest.post("/api/v1/secret-approvals", {
environment,
@ -26,8 +25,7 @@ export const useCreateSecretApprovalPolicy = () => {
approvers,
secretPath,
name,
enforcementLevel,
allowedSelfApprovals
enforcementLevel
});
return data;
},
@ -43,22 +41,13 @@ export const useUpdateSecretApprovalPolicy = () => {
const queryClient = useQueryClient();
return useMutation<object, object, TUpdateSecretPolicyDTO>({
mutationFn: async ({
id,
approvers,
approvals,
secretPath,
name,
enforcementLevel,
allowedSelfApprovals
}) => {
mutationFn: async ({ id, approvers, approvals, secretPath, name, enforcementLevel }) => {
const { data } = await apiRequest.patch(`/api/v1/secret-approvals/${id}`, {
approvals,
approvers,
secretPath,
name,
enforcementLevel,
allowedSelfApprovals
enforcementLevel
});
return data;
},

@ -12,7 +12,6 @@ export type TSecretApprovalPolicy = {
approvers: Approver[];
updatedAt: Date;
enforcementLevel: EnforcementLevel;
allowedSelfApprovals: boolean;
};
export enum ApproverType {
@ -43,7 +42,6 @@ export type TCreateSecretPolicyDTO = {
approvers?: Approver[];
approvals?: number;
enforcementLevel: EnforcementLevel;
allowedSelfApprovals: boolean;
};
export type TUpdateSecretPolicyDTO = {
@ -52,7 +50,6 @@ export type TUpdateSecretPolicyDTO = {
approvers?: Approver[];
secretPath?: string | null;
approvals?: number;
allowedSelfApprovals?: boolean;
enforcementLevel?: EnforcementLevel;
// for invalidating list
workspaceId: string;

@ -182,8 +182,7 @@ export const useGetImportedSecretsAllEnvs = ({
comment: encSecret.secretComment,
createdAt: encSecret.createdAt,
updatedAt: encSecret.updatedAt,
version: encSecret.version,
sourceEnv: env
version: encSecret.version
};
})
})),

@ -86,5 +86,13 @@ export const useSecretOverview = (secrets: DashboardProjectSecretsOverview["secr
[secrets]
);
return { secKeys, getEnvSecretKeyCount };
const getSecretByKey = useCallback(
(env: string, key: string) => {
const sec = secrets?.find((s) => s.env === env && s.key === key);
return sec;
},
[secrets]
);
return { secKeys, getSecretByKey, getEnvSecretKeyCount };
};

@ -12,13 +12,17 @@ export const DefaultSideBar = () => (
</MenuItem>
)}
</Link>
<Link to="/organization/billing">
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="spinning-coin">
Usage & Billing
</MenuItem>
)}
</Link>
{(window.location.origin.includes("https://app.infisical.com") ||
window.location.origin.includes("https://eu.infisical.com") ||
window.location.origin.includes("https://gamma.infisical.com")) && (
<Link to="/organization/billing">
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="spinning-coin">
Usage & Billing
</MenuItem>
)}
</Link>
)}
</MenuGroup>
<MenuGroup title="Other">
<Link to="/organization/access-management">

@ -370,11 +370,17 @@ export const MinimizedOrgSidebar = () => {
Gateways
</DropdownMenuItem>
</Link>
<Link to="/organization/billing">
<DropdownMenuItem icon={<FontAwesomeIcon className="w-3" icon={faMoneyBill} />}>
Usage & Billing
</DropdownMenuItem>
</Link>
{(window.location.origin.includes("https://app.infisical.com") ||
window.location.origin.includes("https://eu.infisical.com") ||
window.location.origin.includes("https://gamma.infisical.com")) && (
<Link to="/organization/billing">
<DropdownMenuItem
icon={<FontAwesomeIcon className="w-3" icon={faMoneyBill} />}
>
Usage & Billing
</DropdownMenuItem>
</Link>
)}
<Link to="/organization/audit-logs">
<DropdownMenuItem icon={<FontAwesomeIcon className="w-3" icon={faBook} />}>
Audit Logs

@ -258,7 +258,7 @@ export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLinks }: Pro
</Th>
<Th className="w-1/3">
<div className="flex items-center">
Email
Username
<IconButton
variant="plain"
className={`ml-2 ${orderBy === OrgMembersOrderBy.Email ? "" : "opacity-30"}`}
@ -478,7 +478,7 @@ export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLinks }: Pro
onClick={(e) => {
e.stopPropagation();
if (currentOrg?.scimEnabled) {
if (currentOrg?.scimEnabled && isActive) {
createNotification({
text: "You cannot manage users from Infisical when org-level auth is enforced for your organization",
type: "error"

@ -9,7 +9,6 @@ import {
useOrganization,
useSubscription
} from "@app/context";
import { isInfisicalCloud } from "@app/helpers/platform";
import {
useCreateCustomerPortalSession,
useGetOrgPlanBillingInfo,
@ -48,9 +47,6 @@ export const PreviewSection = () => {
};
function formatPlanSlug(slug: string) {
if (!slug) {
return "-";
}
return slug.replace(/(\b[a-z])/g, (match) => match.toUpperCase()).replace(/-/g, " ");
}
@ -58,11 +54,6 @@ export const PreviewSection = () => {
try {
if (!subscription || !currentOrg) return;
if (!isInfisicalCloud()) {
window.open("https://infisical.com/pricing", "_blank");
return;
}
if (!subscription.has_used_trial) {
// direct user to start pro trial
const url = await getOrgTrialUrl.mutateAsync({
@ -80,19 +71,6 @@ 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 (
<div>
{subscription &&
@ -119,7 +97,7 @@ export const PreviewSection = () => {
color="mineshaft"
isDisabled={!isAllowed}
>
{getUpgradePlanLabel()}
{!subscription.has_used_trial ? "Start Pro Free Trial" : "Upgrade Plan"}
</Button>
)}
</OrgPermissionCan>
@ -155,24 +133,22 @@ export const PreviewSection = () => {
subscription.status === "trialing" ? "(Trial)" : ""
}`}
</p>
{isInfisicalCloud() && (
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Billing}>
{(isAllowed) => (
<button
type="button"
onClick={async () => {
if (!currentOrg?.id) return;
const { url } = await createCustomerPortalSession.mutateAsync(currentOrg.id);
window.location.href = url;
}}
disabled={!isAllowed}
className="text-primary"
>
Manage plan &rarr;
</button>
)}
</OrgPermissionCan>
)}
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Billing}>
{(isAllowed) => (
<button
type="button"
onClick={async () => {
if (!currentOrg?.id) return;
const { url } = await createCustomerPortalSession.mutateAsync(currentOrg.id);
window.location.href = url;
}}
disabled={!isAllowed}
className="text-primary"
>
Manage plan &rarr;
</button>
)}
</OrgPermissionCan>
</div>
<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>
@ -185,7 +161,7 @@ export const PreviewSection = () => {
<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-8 text-2xl font-semibold text-mineshaft-50">
{data.currentPeriodEnd ? formatDate(data.currentPeriodEnd) : "-"}
{formatDate(data.currentPeriodEnd)}
</p>
</div>
</div>

@ -1,6 +1,5 @@
import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
import { isInfisicalCloud } from "@app/helpers/platform";
import { withPermission } from "@app/hoc";
import { BillingCloudTab } from "../BillingCloudTab";
@ -17,33 +16,25 @@ const tabs = [
export const BillingTabGroup = withPermission(
() => {
const tabsFiltered = isInfisicalCloud()
? tabs
: [{ name: "Infisical Self-Hosted", key: "tab-infisical-cloud" }];
return (
<Tabs defaultValue={tabs[0].key}>
<TabList>
{tabsFiltered.map((tab) => (
{tabs.map((tab) => (
<Tab value={tab.key}>{tab.name}</Tab>
))}
</TabList>
<TabPanel value={tabs[0].key}>
<BillingCloudTab />
</TabPanel>
{isInfisicalCloud() && (
<>
<TabPanel value={tabs[1].key}>
<BillingSelfHostedTab />
</TabPanel>
<TabPanel value={tabs[2].key}>
<BillingReceiptsTab />
</TabPanel>
<TabPanel value={tabs[3].key}>
<BillingDetailsTab />
</TabPanel>
</>
)}
<TabPanel value={tabs[1].key}>
<BillingSelfHostedTab />
</TabPanel>
<TabPanel value={tabs[2].key}>
<BillingReceiptsTab />
</TabPanel>
<TabPanel value={tabs[3].key}>
<BillingDetailsTab />
</TabPanel>
</Tabs>
);
},

@ -94,8 +94,29 @@ export const AddMemberModal = ({ popUp, handlePopUpToggle }: Props) => {
});
} else {
const inviteeEmails = selectedMembers
.map((member) => member?.user.username as string)
.filter(Boolean);
.map((member) => {
if (!member) return null;
if (member.user.email) {
return member.user.email;
}
if (member.user.username) {
return member.user.username;
}
return null;
})
.filter(Boolean) as string[];
if (inviteeEmails.length !== selectedMembers.length) {
createNotification({
text: "Failed to add users to project. One or more users were invalid.",
type: "error"
});
return;
}
if (inviteeEmails.length || newInvitees.length) {
await addMembersToProject({
inviteeEmails: [...inviteeEmails, ...newInvitees],

@ -81,6 +81,7 @@ import { CreateSecretForm } from "./components/CreateSecretForm";
import { FolderBreadCrumbs } from "./components/FolderBreadCrumbs";
import { SecretOverviewDynamicSecretRow } from "./components/SecretOverviewDynamicSecretRow";
import { SecretOverviewFolderRow } from "./components/SecretOverviewFolderRow";
import { SecretOverviewImportListView } from "./components/SecretOverviewImportListView";
import {
SecretNoAccessOverviewTableRow,
SecretOverviewTableRow
@ -202,16 +203,12 @@ export const OverviewPage = () => {
setVisibleEnvs(userAvailableEnvs);
}, [userAvailableEnvs]);
const {
secretImports,
isImportedSecretPresentInEnv,
getImportedSecretByKey,
getEnvImportedSecretKeyCount
} = useGetImportedSecretsAllEnvs({
projectId: workspaceId,
path: secretPath,
environments: (userAvailableEnvs || []).map(({ slug }) => slug)
});
const { isImportedSecretPresentInEnv, getImportedSecretByKey, getEnvImportedSecretKeyCount } =
useGetImportedSecretsAllEnvs({
projectId: workspaceId,
path: secretPath,
environments: (userAvailableEnvs || []).map(({ slug }) => slug)
});
const { isPending: isOverviewLoading, data: overview } = useGetProjectSecretsOverview(
{
@ -235,6 +232,7 @@ export const OverviewPage = () => {
secrets,
folders,
dynamicSecrets,
imports,
totalFolderCount,
totalSecretCount,
totalDynamicSecretCount,
@ -246,20 +244,16 @@ export const OverviewPage = () => {
totalUniqueDynamicSecretsInPage
} = overview ?? {};
const secretImportsShaped = secretImports
?.flatMap(({ data }) => data)
.filter(Boolean)
.flatMap((item) => item?.secrets || []);
const handleIsImportedSecretPresentInEnv = (envSlug: string, secretName: string) => {
if (secrets?.some((s) => s.key === secretName && s.env === envSlug)) {
return false;
}
if (secretImportsShaped.some((s) => s.key === secretName && s.sourceEnv === envSlug)) {
return true;
}
return isImportedSecretPresentInEnv(envSlug, secretName);
};
const importsShaped = imports
?.filter((el) => !el.isReserved)
?.map(({ importPath, importEnv }) => ({ importPath, importEnv }))
.filter(
(el, index, self) =>
index ===
self.findIndex(
(item) => item.importPath === el.importPath && item.importEnv.slug === el.importEnv.slug
)
);
useResetPageHelper({
totalCount,
@ -273,18 +267,7 @@ export const OverviewPage = () => {
const { dynamicSecretNames, isDynamicSecretPresentInEnv } =
useDynamicSecretOverview(dynamicSecrets);
const { secKeys, getEnvSecretKeyCount } = useSecretOverview(
secrets?.concat(secretImportsShaped) || []
);
const getSecretByKey = useCallback(
(env: string, key: string) => {
const sec = secrets?.find((s) => s.env === env && s.key === key);
return sec;
},
[secrets]
);
const { secKeys, getSecretByKey, getEnvSecretKeyCount } = useSecretOverview(secrets);
const { data: tags } = useGetWsTags(
permission.can(ProjectPermissionActions.Read, ProjectPermissionSub.Tags) ? workspaceId : ""
);
@ -1141,13 +1124,24 @@ export const OverviewPage = () => {
key={`overview-${dynamicSecretName}-${index + 1}`}
/>
))}
{filter.import &&
importsShaped &&
importsShaped?.length > 0 &&
importsShaped?.map((item, index) => (
<SecretOverviewImportListView
secretImport={item}
environments={visibleEnvs}
key={`overview-secret-input-${index + 1}`}
allSecretImports={imports}
/>
))}
{secKeys.map((key, index) => (
<SecretOverviewTableRow
isSelected={Boolean(selectedEntries.secret[key])}
onToggleSecretSelect={() => toggleSelectedEntry(EntryType.SECRET, key)}
secretPath={secretPath}
getImportedSecretByKey={getImportedSecretByKey}
isImportedSecretPresentInEnv={handleIsImportedSecretPresentInEnv}
isImportedSecretPresentInEnv={isImportedSecretPresentInEnv}
onSecretCreate={handleSecretCreate}
onSecretDelete={handleSecretDelete}
onSecretUpdate={handleSecretUpdate}

@ -0,0 +1,85 @@
import { faCheck, faFileImport, faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { twMerge } from "tailwind-merge";
import { Td, Tr } from "@app/components/v2";
import { TSecretImport, WorkspaceEnv } from "@app/hooks/api/types";
import { EnvFolderIcon } from "@app/pages/secret-manager/SecretDashboardPage/components/SecretImportListView/SecretImportItem";
type Props = {
secretImport: { importPath: string; importEnv: WorkspaceEnv };
environments: { name: string; slug: string }[];
allSecretImports?: TSecretImport[];
};
export const SecretOverviewImportListView = ({
secretImport,
environments = [],
allSecretImports = []
}: Props) => {
const isSecretPresentInEnv = (envSlug: string) => {
return allSecretImports.some((item) => {
if (item.isReplication) {
if (
item.importPath === secretImport.importPath &&
item.importEnv.slug === secretImport.importEnv.slug
) {
const reservedItem = allSecretImports.find((element) =>
element.importPath.includes(`__reserve_replication_${item.id}`)
);
// If the reserved item exists, check if the envSlug matches
if (reservedItem) {
return reservedItem.environment === envSlug;
}
}
} else {
// If the item is not replication, check if the envSlug matches directly
return (
item.environment === envSlug &&
item.importPath === secretImport.importPath &&
item.importEnv.slug === secretImport.importEnv.slug
);
}
return false;
});
};
return (
<Tr className="group">
<Td className="sticky left-0 z-10 border-r border-mineshaft-600 bg-mineshaft-800 bg-clip-padding px-0 py-0 group-hover:bg-mineshaft-700">
<div className="group flex cursor-pointer">
<div className="flex w-11 items-center py-2 pl-5 text-green-700">
<FontAwesomeIcon icon={faFileImport} />
</div>
<div className="flex flex-grow items-center py-2 pl-4 pr-2">
<EnvFolderIcon
env={secretImport.importEnv.slug || ""}
secretPath={secretImport.importPath || ""}
/>
</div>
</div>
</Td>
{environments.map(({ slug }, i) => {
const isPresent = isSecretPresentInEnv(slug);
return (
<Td
key={`sec-overview-${slug}-${i + 1}-value`}
className={twMerge(
"px-0 py-0 group-hover:bg-mineshaft-700",
isPresent ? "text-green-600" : "text-red-600"
)}
>
<div className="h-full w-full border-r border-mineshaft-600 px-5 py-[0.85rem]">
<div className="flex justify-center">
<FontAwesomeIcon
// eslint-disable-next-line no-nested-ternary
icon={isSecretPresentInEnv(slug) ? faCheck : faXmark}
/>
</div>
</div>
</Td>
);
})}
</Tr>
);
};

@ -0,0 +1 @@
export { SecretOverviewImportListView } from "./SecretOverviewImportListView";

@ -162,7 +162,7 @@ function SecretRenameRow({ environments, getSecretByKey, secretKey, secretPath }
render={({ field, fieldState: { error } }) => (
<Input
autoComplete="off"
isReadOnly={isReadOnly || secrets.filter(Boolean).length === 0}
isReadOnly={isReadOnly}
autoCapitalization={currentWorkspace?.autoCapitalization}
variant="plain"
isDisabled={isOverriden}

@ -152,7 +152,7 @@ export const AccessApprovalRequest = ({
const isAccepted = request.isApproved;
const isSoftEnforcement = request.policy.enforcementLevel === EnforcementLevel.Soft;
const isRequestedByCurrentUser = request.requestedByUserId === user.id;
const isSelfApproveAllowed = request.policy.allowedSelfApprovals;
const userReviewStatus = request.reviewers.find(({ member }) => member === user.id)?.status;
let displayData: { label: string; type: "primary" | "danger" | "success" } = {
@ -189,8 +189,7 @@ export const AccessApprovalRequest = ({
userReviewStatus,
isAccepted,
isSoftEnforcement,
isRequestedByCurrentUser,
isSelfApproveAllowed
isRequestedByCurrentUser
};
};
@ -343,16 +342,15 @@ export const AccessApprovalRequest = ({
tabIndex={0}
onClick={() => {
if (
((!details.isApprover ||
(!details.isApprover ||
details.isReviewedByUser ||
details.isRejectedByAnyone ||
details.isAccepted) &&
!(
details.isSoftEnforcement &&
details.isRequestedByCurrentUser &&
!details.isAccepted
)) ||
(request.requestedByUserId === user.id && !details.isSelfApproveAllowed)
!(
details.isSoftEnforcement &&
details.isRequestedByCurrentUser &&
!details.isAccepted
)
)
return;
if (membersGroupById?.[request.requestedByUserId].user) {

@ -12,8 +12,7 @@ import {
Modal,
ModalContent,
Select,
SelectItem,
Switch
SelectItem
} from "@app/components/v2";
import { useWorkspace } from "@app/context";
import { getMemberLabel } from "@app/helpers/members";
@ -55,8 +54,7 @@ const formSchema = z
.array()
.default([]),
policyType: z.nativeEnum(PolicyType),
enforcementLevel: z.nativeEnum(EnforcementLevel),
allowedSelfApprovals: z.boolean().default(true)
enforcementLevel: z.nativeEnum(EnforcementLevel)
})
.superRefine((data, ctx) => {
if (!(data.groupApprovers.length || data.userApprovers.length)) {
@ -103,8 +101,7 @@ export const AccessPolicyForm = ({
editValues?.approvers
?.filter((approver) => approver.type === ApproverType.Group)
.map(({ id, type }) => ({ id, type: type as ApproverType.Group })) || [],
approvals: editValues?.approvals,
allowedSelfApprovals: editValues?.allowedSelfApprovals
approvals: editValues?.approvals
}
: undefined
});
@ -444,27 +441,6 @@ export const AccessPolicyForm = ({
</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">
<Button type="submit" isLoading={isSubmitting} isDisabled={isSubmitting}>
Save

@ -127,9 +127,7 @@ export const SecretApprovalRequestChanges = ({
} = useForm<TReviewFormSchema>({
resolver: zodResolver(reviewFormSchema)
});
const shouldBlockSelfReview =
secretApprovalRequestDetails?.policy?.allowedSelfApprovals === false &&
secretApprovalRequestDetails?.committerUserId === userSession.id;
const isApproving = variables?.status === ApprovalStatus.APPROVED && isUpdatingRequestStatus;
const isRejecting = variables?.status === ApprovalStatus.REJECTED && isUpdatingRequestStatus;
@ -247,119 +245,117 @@ export const SecretApprovalRequestChanges = ({
</div>
</div>
</div>
{!hasMerged &&
secretApprovalRequestDetails.status === "open" &&
!shouldBlockSelfReview && (
<DropdownMenu
open={popUp.reviewChanges.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("reviewChanges", isOpen)}
>
<DropdownMenuTrigger asChild>
<Button
variant="outline_bg"
rightIcon={<FontAwesomeIcon className="ml-2" icon={faAngleDown} />}
>
Review
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" asChild className="mt-3">
<form onSubmit={handleSubmit(handleSubmitReview)}>
<div className="flex w-[400px] flex-col space-y-2 p-5">
<div className="text-lg font-medium">Finish your review</div>
<Controller
control={control}
name="comment"
render={({ field, fieldState: { error } }) => (
<FormControl errorText={error?.message} isError={Boolean(error)}>
<TextArea
{...field}
placeholder="Leave a comment..."
reSize="none"
className="text-md mt-2 h-48 border border-mineshaft-600 bg-bunker-800"
/>
</FormControl>
)}
/>
<Controller
control={control}
name="status"
defaultValue={ApprovalStatus.APPROVED}
render={({ field, fieldState: { error } }) => (
<FormControl errorText={error?.message} isError={Boolean(error)}>
<RadioGroup
value={field.value}
onValueChange={field.onChange}
className="mb-4 space-y-2"
aria-label="Status"
>
<div className="flex items-center gap-2">
<RadioGroupItem
id="approve"
className="h-4 w-4 rounded-full border border-gray-300 text-primary focus:ring-2 focus:ring-mineshaft-500"
value={ApprovalStatus.APPROVED}
aria-labelledby="approve-label"
>
<RadioGroupIndicator className="flex h-full w-full items-center justify-center after:h-2 after:w-2 after:rounded-full after:bg-current" />
</RadioGroupItem>
<span
id="approve-label"
className="cursor-pointer"
onClick={() => field.onChange(ApprovalStatus.APPROVED)}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
field.onChange(ApprovalStatus.APPROVED);
}
}}
tabIndex={0}
role="button"
>
Approve
</span>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem
id="reject"
className="h-4 w-4 rounded-full border border-gray-300 text-primary focus:ring-2 focus:ring-mineshaft-500"
value={ApprovalStatus.REJECTED}
aria-labelledby="reject-label"
>
<RadioGroupIndicator className="flex h-full w-full items-center justify-center after:h-2 after:w-2 after:rounded-full after:bg-current" />
</RadioGroupItem>
<span
id="reject-label"
className="cursor-pointer"
onClick={() => field.onChange(ApprovalStatus.REJECTED)}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
field.onChange(ApprovalStatus.REJECTED);
}
}}
tabIndex={0}
role="button"
>
Reject
</span>
</div>
</RadioGroup>
</FormControl>
)}
/>
<div className="flex justify-end">
<Button
type="submit"
isLoading={isApproving || isRejecting || isSubmitting}
variant="outline_bg"
>
Submit Review
</Button>
</div>
{!hasMerged && secretApprovalRequestDetails.status === "open" && (
<DropdownMenu
open={popUp.reviewChanges.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("reviewChanges", isOpen)}
>
<DropdownMenuTrigger asChild>
<Button
variant="outline_bg"
rightIcon={<FontAwesomeIcon className="ml-2" icon={faAngleDown} />}
>
Review
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" asChild className="mt-3">
<form onSubmit={handleSubmit(handleSubmitReview)}>
<div className="flex w-[400px] flex-col space-y-2 p-5">
<div className="text-lg font-medium">Finish your review</div>
<Controller
control={control}
name="comment"
render={({ field, fieldState: { error } }) => (
<FormControl errorText={error?.message} isError={Boolean(error)}>
<TextArea
{...field}
placeholder="Leave a comment..."
reSize="none"
className="text-md mt-2 h-48 border border-mineshaft-600 bg-bunker-800"
/>
</FormControl>
)}
/>
<Controller
control={control}
name="status"
defaultValue={ApprovalStatus.APPROVED}
render={({ field, fieldState: { error } }) => (
<FormControl errorText={error?.message} isError={Boolean(error)}>
<RadioGroup
value={field.value}
onValueChange={field.onChange}
className="mb-4 space-y-2"
aria-label="Status"
>
<div className="flex items-center gap-2">
<RadioGroupItem
id="approve"
className="h-4 w-4 rounded-full border border-gray-300 text-primary focus:ring-2 focus:ring-mineshaft-500"
value={ApprovalStatus.APPROVED}
aria-labelledby="approve-label"
>
<RadioGroupIndicator className="flex h-full w-full items-center justify-center after:h-2 after:w-2 after:rounded-full after:bg-current" />
</RadioGroupItem>
<span
id="approve-label"
className="cursor-pointer"
onClick={() => field.onChange(ApprovalStatus.APPROVED)}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
field.onChange(ApprovalStatus.APPROVED);
}
}}
tabIndex={0}
role="button"
>
Approve
</span>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem
id="reject"
className="h-4 w-4 rounded-full border border-gray-300 text-primary focus:ring-2 focus:ring-mineshaft-500"
value={ApprovalStatus.REJECTED}
aria-labelledby="reject-label"
>
<RadioGroupIndicator className="flex h-full w-full items-center justify-center after:h-2 after:w-2 after:rounded-full after:bg-current" />
</RadioGroupItem>
<span
id="reject-label"
className="cursor-pointer"
onClick={() => field.onChange(ApprovalStatus.REJECTED)}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
field.onChange(ApprovalStatus.REJECTED);
}
}}
tabIndex={0}
role="button"
>
Reject
</span>
</div>
</RadioGroup>
</FormControl>
)}
/>
<div className="flex justify-end">
<Button
type="submit"
isLoading={isApproving || isRejecting || isSubmitting}
variant="outline_bg"
>
Submit Review
</Button>
</div>
</form>
</DropdownMenuContent>
</DropdownMenu>
)}
</div>
</form>
</DropdownMenuContent>
</DropdownMenu>
)}
</div>
<div className="flex flex-col space-y-4">
{secretApprovalRequestDetails.commits.map(
@ -426,45 +422,40 @@ export const SecretApprovalRequestChanges = ({
<div className="sticky top-0 w-1/5 pt-4" style={{ minWidth: "240px" }}>
<div className="text-sm text-bunker-300">Reviewers</div>
<div className="mt-2 flex flex-col space-y-2 text-sm">
{secretApprovalRequestDetails?.policy?.approvers
.filter(
(requiredApprover) =>
!(shouldBlockSelfReview && requiredApprover.userId === userSession.id)
)
.map((requiredApprover) => {
const reviewer = reviewedUsers?.[requiredApprover.userId];
return (
<div
className="flex flex-nowrap items-center space-x-2 rounded bg-mineshaft-800 px-2 py-1"
key={`required-approver-${requiredApprover.userId}`}
>
<div className="flex-grow text-sm">
<Tooltip
content={`${requiredApprover.firstName || ""} ${
requiredApprover.lastName || ""
}`}
>
<span>{requiredApprover?.email} </span>
</Tooltip>
<span className="text-red">*</span>
</div>
<div>
{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>
{secretApprovalRequestDetails?.policy?.approvers.map((requiredApprover) => {
const reviewer = reviewedUsers?.[requiredApprover.userId];
return (
<div
className="flex flex-nowrap items-center space-x-2 rounded bg-mineshaft-800 px-2 py-1"
key={`required-approver-${requiredApprover.userId}`}
>
<div className="flex-grow text-sm">
<Tooltip
content={`${requiredApprover.firstName || ""} ${
requiredApprover.lastName || ""
}`}
>
<span>{requiredApprover?.email} </span>
</Tooltip>
<span className="text-red">*</span>
</div>
);
})}
<div>
{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>
);
})}
{secretApprovalRequestDetails?.reviewers
.filter(
(reviewer) =>

@ -88,4 +88,4 @@ spec:
serviceAccountName: {{ include "secrets-operator.fullname" . }}-controller-manager
terminationGracePeriodSeconds: 10
nodeSelector: {{ toYaml .Values.controllerManager.nodeSelector | nindent 8 }}
tolerations: {{ toYaml .Values.controllerManager.tolerations | nindent 8 }}
tolerations: {{ toYaml .Values.controllerManager.tolerations | nindent 8 }}

@ -309,4 +309,4 @@ status:
plural: ""
conditions: []
storedVersions: []
{{- end }}
{{- end }}

@ -266,4 +266,4 @@ status:
plural: ""
conditions: []
storedVersions: []
{{- end }}
{{- end }}

@ -504,4 +504,5 @@ status:
plural: ""
conditions: []
storedVersions: []
{{- end }}
{{- end }}

@ -56,4 +56,4 @@ roleRef:
subjects:
- kind: ServiceAccount
name: '{{ include "secrets-operator.fullname" . }}-controller-manager'
namespace: '{{ .Release.Namespace }}'
namespace: '{{ .Release.Namespace }}'

@ -53,15 +53,6 @@ rules:
- list
- update
- watch
- apiGroups:
- apps
resources:
- deployments
verbs:
- get
- list
- update
- watch
- apiGroups:
- secrets.infisical.com
resources:
@ -168,4 +159,4 @@ roleRef:
subjects:
- kind: ServiceAccount
name: '{{ include "secrets-operator.fullname" . }}-controller-manager'
namespace: '{{ .Release.Namespace }}'
namespace: '{{ .Release.Namespace }}'

@ -13,5 +13,4 @@ rules:
- /metrics
verbs:
- get
{{- end }}
{{- end }}

@ -14,4 +14,4 @@ spec:
control-plane: controller-manager
{{- include "secrets-operator.selectorLabels" . | nindent 4 }}
ports:
{{- .Values.metricsService.ports | toYaml | nindent 2 }}
{{- .Values.metricsService.ports | toYaml | nindent 2 }}

@ -39,5 +39,4 @@ subjects:
- kind: ServiceAccount
name: '{{ include "secrets-operator.fullname" . }}-controller-manager'
namespace: '{{ .Release.Namespace }}'
{{- end }}
{{- end }}

@ -8,4 +8,4 @@ metadata:
app.kubernetes.io/part-of: k8-operator
{{- include "secrets-operator.labels" . | nindent 4 }}
annotations:
{{- toYaml .Values.controllerManager.serviceAccount.annotations | nindent 4 }}
{{- toYaml .Values.controllerManager.serviceAccount.annotations | nindent 4 }}

@ -1,15 +1,15 @@
controllerManager:
kubeRbacProxy:
args:
- --secure-listen-address=0.0.0.0:8443
- --upstream=http://127.0.0.1:8080/
- --logtostderr=true
- --v=0
- --secure-listen-address=0.0.0.0:8443
- --upstream=http://127.0.0.1:8080/
- --logtostderr=true
- --v=0
containerSecurityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
- ALL
image:
repository: gcr.io/kubebuilder/kube-rbac-proxy
tag: v0.15.0
@ -22,17 +22,17 @@ controllerManager:
memory: 64Mi
manager:
args:
- --health-probe-bind-address=:8081
- --metrics-bind-address=127.0.0.1:8080
- --leader-elect
- --health-probe-bind-address=:8081
- --metrics-bind-address=127.0.0.1:8080
- --leader-elect
containerSecurityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
- ALL
image:
repository: infisical/kubernetes-operator
tag: <helm-pr-will-update-this-automatically>
tag: v0.8.15
resources:
limits:
cpu: 500m
@ -45,14 +45,14 @@ controllerManager:
annotations: {}
nodeSelector: {}
tolerations: []
metricsService:
ports:
- name: https
port: 8443
protocol: TCP
targetPort: https
type: ClusterIP
kubernetesClusterDomain: cluster.local
scopedNamespace: ""
scopedRBAC: false
installCRDs: true
metricsService:
ports:
- name: https
port: 8443
protocol: TCP
targetPort: https
type: ClusterIP

@ -48,11 +48,8 @@ helmify: $(HELMIFY) ## Download helmify locally if necessary.
$(HELMIFY): $(LOCALBIN)
test -s $(LOCALBIN)/helmify || GOBIN=$(LOCALBIN) go install github.com/arttor/helmify/cmd/helmify@latest
legacy-helm: manifests kustomize helmify
$(KUSTOMIZE) build config/default | $(HELMIFY) ../helm-charts/secrets-operator
helm: manifests kustomize helmify
./scripts/generate-helm.sh
$(KUSTOMIZE) build config/default | $(HELMIFY) ../helm-charts/secrets-operator
## Yaml for Kubectl
kubectl-install: manifests kustomize

@ -1,332 +0,0 @@
#!/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."

@ -1,37 +0,0 @@
#!/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