mirror of
https://github.com/Infisical/infisical.git
synced 2025-07-02 16:55:02 +00:00
Compare commits
50 Commits
misc/add-e
...
fix/keepDo
Author | SHA1 | Date | |
---|---|---|---|
d4728e31c1 | |||
f9a5b46365 | |||
d65deab0af | |||
61591742e4 | |||
54b13a9daa | |||
4adf0aa1e2 | |||
3d3ee746cf | |||
07e4358d00 | |||
962dd5d919 | |||
52bd1afb0a | |||
d918dd8967 | |||
e2e0f6a346 | |||
326cb99732 | |||
341b63c61c | |||
81b026865c | |||
f50c72c033 | |||
e1046e2d56 | |||
ed3fa8add1 | |||
d123283849 | |||
d7fd44b845 | |||
3ffee049ee | |||
524462d7bc | |||
351e573fea | |||
f1bc26e2e5 | |||
8aeb607f6e | |||
e530b7a788 | |||
bf61090b5a | |||
106b068a51 | |||
6f0a97a2fa | |||
5d604be091 | |||
905cf47d90 | |||
2c40d316f4 | |||
32521523c1 | |||
3a2e8939b1 | |||
a6d9c74054 | |||
82520a7f0a | |||
af236ba892 | |||
c4b7d4618d | |||
003f2b003d | |||
747b5ec68d | |||
ed0dc324a3 | |||
1c13ed54af | |||
8abfea0409 | |||
ce4adccc80 | |||
dcd3b5df56 | |||
f6425480ca | |||
a3e9392a2f | |||
633a2ae985 | |||
e67a8f9c05 | |||
ad110f490c |
1
backend/src/@types/fastify.d.ts
vendored
1
backend/src/@types/fastify.d.ts
vendored
@ -106,6 +106,7 @@ declare module "@fastify/request-context" {
|
|||||||
claims: Record<string, string>;
|
claims: Record<string, string>;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
identityPermissionMetadata?: Record<string, unknown>; // filled by permission service
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const doesParentColumExist = await knex.schema.hasColumn(TableName.SecretFolder, "parentId");
|
||||||
|
const doesNameColumnExist = await knex.schema.hasColumn(TableName.SecretFolder, "name");
|
||||||
|
if (doesParentColumExist && doesNameColumnExist) {
|
||||||
|
await knex.schema.alterTable(TableName.SecretFolder, (t) => {
|
||||||
|
t.index(["parentId", "name"]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const doesParentColumExist = await knex.schema.hasColumn(TableName.SecretFolder, "parentId");
|
||||||
|
const doesNameColumnExist = await knex.schema.hasColumn(TableName.SecretFolder, "name");
|
||||||
|
if (doesParentColumExist && doesNameColumnExist) {
|
||||||
|
await knex.schema.alterTable(TableName.SecretFolder, (t) => {
|
||||||
|
t.dropIndex(["parentId", "name"]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const hasReviewerJwtCol = await knex.schema.hasColumn(
|
||||||
|
TableName.IdentityKubernetesAuth,
|
||||||
|
"encryptedKubernetesTokenReviewerJwt"
|
||||||
|
);
|
||||||
|
if (hasReviewerJwtCol) {
|
||||||
|
await knex.schema.alterTable(TableName.IdentityKubernetesAuth, (t) => {
|
||||||
|
t.binary("encryptedKubernetesTokenReviewerJwt").nullable().alter();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(): Promise<void> {
|
||||||
|
// we can't make it back to non nullable, it will fail
|
||||||
|
}
|
@ -28,7 +28,7 @@ export const IdentityKubernetesAuthsSchema = z.object({
|
|||||||
allowedNamespaces: z.string(),
|
allowedNamespaces: z.string(),
|
||||||
allowedNames: z.string(),
|
allowedNames: z.string(),
|
||||||
allowedAudience: z.string(),
|
allowedAudience: z.string(),
|
||||||
encryptedKubernetesTokenReviewerJwt: zodBuffer,
|
encryptedKubernetesTokenReviewerJwt: zodBuffer.nullable().optional(),
|
||||||
encryptedKubernetesCaCertificate: zodBuffer.nullable().optional()
|
encryptedKubernetesCaCertificate: zodBuffer.nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
import { requestContext } from "@fastify/request-context";
|
||||||
|
|
||||||
import { ActionProjectType } from "@app/db/schemas";
|
import { ActionProjectType } from "@app/db/schemas";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
import { ActorType } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
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";
|
||||||
@ -81,8 +83,12 @@ export const auditLogServiceFactory = ({
|
|||||||
if (!data.projectId && !data.orgId)
|
if (!data.projectId && !data.orgId)
|
||||||
throw new BadRequestError({ message: "Must specify either project id or org id" });
|
throw new BadRequestError({ message: "Must specify either project id or org id" });
|
||||||
}
|
}
|
||||||
|
const el = { ...data };
|
||||||
return auditLogQueue.pushToLog(data);
|
if (el.actor.type === ActorType.USER || el.actor.type === ActorType.IDENTITY) {
|
||||||
|
const permissionMetadata = requestContext.get("identityPermissionMetadata");
|
||||||
|
el.actor.metadata.permission = permissionMetadata;
|
||||||
|
}
|
||||||
|
return auditLogQueue.pushToLog(el);
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -290,6 +290,7 @@ interface UserActorMetadata {
|
|||||||
userId: string;
|
userId: string;
|
||||||
email?: string | null;
|
email?: string | null;
|
||||||
username: string;
|
username: string;
|
||||||
|
permission?: Record<string, unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ServiceActorMetadata {
|
interface ServiceActorMetadata {
|
||||||
@ -300,6 +301,7 @@ interface ServiceActorMetadata {
|
|||||||
interface IdentityActorMetadata {
|
interface IdentityActorMetadata {
|
||||||
identityId: string;
|
identityId: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
permission?: Record<string, unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ScimClientActorMetadata {}
|
interface ScimClientActorMetadata {}
|
||||||
|
@ -244,22 +244,20 @@ export const permissionServiceFactory = ({
|
|||||||
|
|
||||||
const rules = buildProjectPermissionRules(rolePermissions.concat(additionalPrivileges));
|
const rules = buildProjectPermissionRules(rolePermissions.concat(additionalPrivileges));
|
||||||
const templatedRules = handlebars.compile(JSON.stringify(rules), { data: false });
|
const templatedRules = handlebars.compile(JSON.stringify(rules), { data: false });
|
||||||
const metadataKeyValuePair = escapeHandlebarsMissingDict(
|
const unescapedMetadata = objectify(
|
||||||
objectify(
|
|
||||||
userProjectPermission.metadata,
|
userProjectPermission.metadata,
|
||||||
(i) => i.key,
|
(i) => i.key,
|
||||||
(i) => i.value
|
(i) => i.value
|
||||||
),
|
|
||||||
"identity.metadata"
|
|
||||||
);
|
);
|
||||||
const templateValue = {
|
const metadataKeyValuePair = escapeHandlebarsMissingDict(unescapedMetadata, "identity.metadata");
|
||||||
|
requestContext.set("identityPermissionMetadata", { metadata: unescapedMetadata });
|
||||||
|
const interpolateRules = templatedRules(
|
||||||
|
{
|
||||||
|
identity: {
|
||||||
id: userProjectPermission.userId,
|
id: userProjectPermission.userId,
|
||||||
username: userProjectPermission.username,
|
username: userProjectPermission.username,
|
||||||
metadata: metadataKeyValuePair
|
metadata: metadataKeyValuePair
|
||||||
};
|
}
|
||||||
const interpolateRules = templatedRules(
|
|
||||||
{
|
|
||||||
identity: templateValue
|
|
||||||
},
|
},
|
||||||
{ data: false }
|
{ data: false }
|
||||||
);
|
);
|
||||||
@ -331,15 +329,16 @@ export const permissionServiceFactory = ({
|
|||||||
? escapeHandlebarsMissingDict(unescapedIdentityAuthInfo as never, "identity.auth")
|
? escapeHandlebarsMissingDict(unescapedIdentityAuthInfo as never, "identity.auth")
|
||||||
: {};
|
: {};
|
||||||
const metadataKeyValuePair = escapeHandlebarsMissingDict(unescapedMetadata, "identity.metadata");
|
const metadataKeyValuePair = escapeHandlebarsMissingDict(unescapedMetadata, "identity.metadata");
|
||||||
const templateValue = {
|
|
||||||
|
requestContext.set("identityPermissionMetadata", { metadata: unescapedMetadata, auth: unescapedIdentityAuthInfo });
|
||||||
|
const interpolateRules = templatedRules(
|
||||||
|
{
|
||||||
|
identity: {
|
||||||
id: identityProjectPermission.identityId,
|
id: identityProjectPermission.identityId,
|
||||||
username: identityProjectPermission.username,
|
username: identityProjectPermission.username,
|
||||||
metadata: metadataKeyValuePair,
|
metadata: metadataKeyValuePair,
|
||||||
auth: identityAuthInfo
|
auth: identityAuthInfo
|
||||||
};
|
}
|
||||||
const interpolateRules = templatedRules(
|
|
||||||
{
|
|
||||||
identity: templateValue
|
|
||||||
},
|
},
|
||||||
{ data: false }
|
{ data: false }
|
||||||
);
|
);
|
||||||
@ -440,14 +439,13 @@ export const permissionServiceFactory = ({
|
|||||||
),
|
),
|
||||||
"identity.metadata"
|
"identity.metadata"
|
||||||
);
|
);
|
||||||
const templateValue = {
|
const interpolateRules = templatedRules(
|
||||||
|
{
|
||||||
|
identity: {
|
||||||
id: userProjectPermission.userId,
|
id: userProjectPermission.userId,
|
||||||
username: userProjectPermission.username,
|
username: userProjectPermission.username,
|
||||||
metadata: metadataKeyValuePair
|
metadata: metadataKeyValuePair
|
||||||
};
|
}
|
||||||
const interpolateRules = templatedRules(
|
|
||||||
{
|
|
||||||
identity: templateValue
|
|
||||||
},
|
},
|
||||||
{ data: false }
|
{ data: false }
|
||||||
);
|
);
|
||||||
@ -487,14 +485,13 @@ export const permissionServiceFactory = ({
|
|||||||
),
|
),
|
||||||
"identity.metadata"
|
"identity.metadata"
|
||||||
);
|
);
|
||||||
const templateValue = {
|
const interpolateRules = templatedRules(
|
||||||
|
{
|
||||||
|
identity: {
|
||||||
id: identityProjectPermission.identityId,
|
id: identityProjectPermission.identityId,
|
||||||
username: identityProjectPermission.username,
|
username: identityProjectPermission.username,
|
||||||
metadata: metadataKeyValuePair
|
metadata: metadataKeyValuePair
|
||||||
};
|
}
|
||||||
const interpolateRules = templatedRules(
|
|
||||||
{
|
|
||||||
identity: templateValue
|
|
||||||
},
|
},
|
||||||
{ data: false }
|
{ data: false }
|
||||||
);
|
);
|
||||||
|
@ -244,7 +244,7 @@ export const KUBERNETES_AUTH = {
|
|||||||
kubernetesHost: "The host string, host:port pair, or URL to the base of the Kubernetes API server.",
|
kubernetesHost: "The host string, host:port pair, or URL to the base of the Kubernetes API server.",
|
||||||
caCert: "The PEM-encoded CA cert for the Kubernetes API server.",
|
caCert: "The PEM-encoded CA cert for the Kubernetes API server.",
|
||||||
tokenReviewerJwt:
|
tokenReviewerJwt:
|
||||||
"The long-lived service account JWT token for Infisical to access the TokenReview API to validate other service account JWT tokens submitted by applications/pods.",
|
"Optional JWT token for accessing Kubernetes TokenReview API. If provided, this long-lived token will be used to validate service account tokens during authentication. If omitted, the client's own JWT will be used instead, which requires the client to have the system:auth-delegator ClusterRole binding.",
|
||||||
allowedNamespaces:
|
allowedNamespaces:
|
||||||
"The comma-separated list of trusted namespaces that service accounts must belong to authenticate with Infisical.",
|
"The comma-separated list of trusted namespaces that service accounts must belong to authenticate with Infisical.",
|
||||||
allowedNames: "The comma-separated list of trusted service account names that can authenticate with Infisical.",
|
allowedNames: "The comma-separated list of trusted service account names that can authenticate with Infisical.",
|
||||||
@ -260,7 +260,7 @@ export const KUBERNETES_AUTH = {
|
|||||||
kubernetesHost: "The new host string, host:port pair, or URL to the base of the Kubernetes API server.",
|
kubernetesHost: "The new host string, host:port pair, or URL to the base of the Kubernetes API server.",
|
||||||
caCert: "The new PEM-encoded CA cert for the Kubernetes API server.",
|
caCert: "The new PEM-encoded CA cert for the Kubernetes API server.",
|
||||||
tokenReviewerJwt:
|
tokenReviewerJwt:
|
||||||
"The new long-lived service account JWT token for Infisical to access the TokenReview API to validate other service account JWT tokens submitted by applications/pods.",
|
"Optional JWT token for accessing Kubernetes TokenReview API. If provided, this long-lived token will be used to validate service account tokens during authentication. If omitted, the client's own JWT will be used instead, which requires the client to have the system:auth-delegator ClusterRole binding.",
|
||||||
allowedNamespaces:
|
allowedNamespaces:
|
||||||
"The new comma-separated list of trusted namespaces that service accounts must belong to authenticate with Infisical.",
|
"The new comma-separated list of trusted namespaces that service accounts must belong to authenticate with Infisical.",
|
||||||
allowedNames: "The new comma-separated list of trusted service account names that can authenticate with Infisical.",
|
allowedNames: "The new comma-separated list of trusted service account names that can authenticate with Infisical.",
|
||||||
@ -631,7 +631,8 @@ export const FOLDERS = {
|
|||||||
workspaceId: "The ID of the project to list folders from.",
|
workspaceId: "The ID of the project to list folders from.",
|
||||||
environment: "The slug of the environment to list folders from.",
|
environment: "The slug of the environment to list folders from.",
|
||||||
path: "The path to list folders from.",
|
path: "The path to list folders from.",
|
||||||
directory: "The directory to list folders from. (Deprecated in favor of path)"
|
directory: "The directory to list folders from. (Deprecated in favor of path)",
|
||||||
|
recursive: "Whether or not to fetch all folders from the specified base path, and all of its subdirectories."
|
||||||
},
|
},
|
||||||
GET_BY_ID: {
|
GET_BY_ID: {
|
||||||
folderId: "The ID of the folder to get details."
|
folderId: "The ID of the folder to get details."
|
||||||
@ -815,7 +816,8 @@ export const DASHBOARD = {
|
|||||||
search: "The text string to filter secret keys and folder names by.",
|
search: "The text string to filter secret keys and folder names by.",
|
||||||
includeSecrets: "Whether to include project secrets in the response.",
|
includeSecrets: "Whether to include project secrets in the response.",
|
||||||
includeFolders: "Whether to include project folders in the response.",
|
includeFolders: "Whether to include project folders in the response.",
|
||||||
includeDynamicSecrets: "Whether to include dynamic project secrets in the response."
|
includeDynamicSecrets: "Whether to include dynamic project secrets in the response.",
|
||||||
|
includeImports: "Whether to include project secret imports in the response."
|
||||||
},
|
},
|
||||||
SECRET_DETAILS_LIST: {
|
SECRET_DETAILS_LIST: {
|
||||||
projectId: "The ID of the project to list secrets/folders from.",
|
projectId: "The ID of the project to list secrets/folders from.",
|
||||||
|
@ -56,6 +56,7 @@ const envSchema = z
|
|||||||
// TODO(akhilmhdh): will be changed to one
|
// TODO(akhilmhdh): will be changed to one
|
||||||
ENCRYPTION_KEY: zpStr(z.string().optional()),
|
ENCRYPTION_KEY: zpStr(z.string().optional()),
|
||||||
ROOT_ENCRYPTION_KEY: zpStr(z.string().optional()),
|
ROOT_ENCRYPTION_KEY: zpStr(z.string().optional()),
|
||||||
|
QUEUE_WORKERS_ENABLED: zodStrBool.default("true"),
|
||||||
HTTPS_ENABLED: zodStrBool,
|
HTTPS_ENABLED: zodStrBool,
|
||||||
// smtp options
|
// smtp options
|
||||||
SMTP_HOST: zpStr(z.string().optional()),
|
SMTP_HOST: zpStr(z.string().optional()),
|
||||||
|
@ -93,6 +93,7 @@ export const pingGatewayAndVerify = async ({
|
|||||||
let lastError: Error | null = null;
|
let lastError: Error | null = null;
|
||||||
const quicClient = await createQuicConnection(relayHost, relayPort, tlsOptions, identityId, orgId).catch((err) => {
|
const quicClient = await createQuicConnection(relayHost, relayPort, tlsOptions, identityId, orgId).catch((err) => {
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
|
message: (err as Error)?.message,
|
||||||
error: err as Error
|
error: err as Error
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -272,10 +272,13 @@ export const queueServiceFactory = (
|
|||||||
connection
|
connection
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const appCfg = getConfig();
|
||||||
|
if (appCfg.QUEUE_WORKERS_ENABLED) {
|
||||||
workerContainer[name] = new Worker<TQueueJobTypes[T]["payload"], void, TQueueJobTypes[T]["name"]>(name, jobFn, {
|
workerContainer[name] = new Worker<TQueueJobTypes[T]["payload"], void, TQueueJobTypes[T]["name"]>(name, jobFn, {
|
||||||
...queueSettings,
|
...queueSettings,
|
||||||
connection
|
connection
|
||||||
});
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const startPg = async <T extends QueueName>(
|
const startPg = async <T extends QueueName>(
|
||||||
@ -307,6 +310,11 @@ export const queueServiceFactory = (
|
|||||||
event: U,
|
event: U,
|
||||||
listener: WorkerListener<TQueueJobTypes[T]["payload"], void, TQueueJobTypes[T]["name"]>[U]
|
listener: WorkerListener<TQueueJobTypes[T]["payload"], void, TQueueJobTypes[T]["name"]>[U]
|
||||||
) => {
|
) => {
|
||||||
|
const appCfg = getConfig();
|
||||||
|
if (!appCfg.QUEUE_WORKERS_ENABLED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const worker = workerContainer[name];
|
const worker = workerContainer[name];
|
||||||
worker.on(event, listener);
|
worker.on(event, listener);
|
||||||
};
|
};
|
||||||
|
@ -65,7 +65,7 @@ export const registerSecretScannerGhApp = async (server: FastifyZodProvider) =>
|
|||||||
payload: JSON.stringify(req.body),
|
payload: JSON.stringify(req.body),
|
||||||
signature: signatureSHA256
|
signature: signatureSHA256
|
||||||
});
|
});
|
||||||
void res.send("ok");
|
return res.send("ok");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@ export const registerServeUI = async (
|
|||||||
TELEMETRY_CAPTURING_ENABLED: appCfg.TELEMETRY_ENABLED
|
TELEMETRY_CAPTURING_ENABLED: appCfg.TELEMETRY_ENABLED
|
||||||
};
|
};
|
||||||
const js = `window.__INFISICAL_RUNTIME_ENV__ = Object.freeze(${JSON.stringify(config)});`;
|
const js = `window.__INFISICAL_RUNTIME_ENV__ = Object.freeze(${JSON.stringify(config)});`;
|
||||||
void res.send(js);
|
return res.send(js);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -57,7 +57,7 @@ export const registerServeUI = async (
|
|||||||
reply.callNotFound();
|
reply.callNotFound();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
void reply.sendFile("index.html");
|
return reply.sendFile("index.html");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -70,6 +70,19 @@ export const DefaultResponseErrorsSchema = {
|
|||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const booleanSchema = z
|
||||||
|
.union([z.boolean(), z.string().trim()])
|
||||||
|
.transform((value) => {
|
||||||
|
if (typeof value === "string") {
|
||||||
|
// ie if not empty, 0 or false, return true
|
||||||
|
return Boolean(value) && Number(value) !== 0 && value.toLowerCase() !== "false";
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
})
|
||||||
|
.optional()
|
||||||
|
.default(true);
|
||||||
|
|
||||||
export const sapPubSchema = SecretApprovalPoliciesSchema.merge(
|
export const sapPubSchema = SecretApprovalPoliciesSchema.merge(
|
||||||
z.object({
|
z.object({
|
||||||
environment: z.object({
|
environment: z.object({
|
||||||
|
@ -16,7 +16,12 @@ import { secretsLimit } from "@app/server/config/rateLimiter";
|
|||||||
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
||||||
import { getUserAgentType } from "@app/server/plugins/audit-log";
|
import { getUserAgentType } from "@app/server/plugins/audit-log";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { SanitizedDynamicSecretSchema, SanitizedTagSchema, secretRawSchema } from "@app/server/routes/sanitizedSchemas";
|
import {
|
||||||
|
booleanSchema,
|
||||||
|
SanitizedDynamicSecretSchema,
|
||||||
|
SanitizedTagSchema,
|
||||||
|
secretRawSchema
|
||||||
|
} from "@app/server/routes/sanitizedSchemas";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
import { ResourceMetadataSchema } from "@app/services/resource-metadata/resource-metadata-schema";
|
import { ResourceMetadataSchema } from "@app/services/resource-metadata/resource-metadata-schema";
|
||||||
import { SecretsOrderBy } from "@app/services/secret/secret-types";
|
import { SecretsOrderBy } from "@app/services/secret/secret-types";
|
||||||
@ -24,20 +29,6 @@ import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";
|
|||||||
|
|
||||||
const MAX_DEEP_SEARCH_LIMIT = 500; // arbitrary limit to prevent excessive results
|
const MAX_DEEP_SEARCH_LIMIT = 500; // arbitrary limit to prevent excessive results
|
||||||
|
|
||||||
// handle querystring boolean values
|
|
||||||
const booleanSchema = z
|
|
||||||
.union([z.boolean(), z.string().trim()])
|
|
||||||
.transform((value) => {
|
|
||||||
if (typeof value === "string") {
|
|
||||||
// ie if not empty, 0 or false, return true
|
|
||||||
return Boolean(value) && Number(value) !== 0 && value.toLowerCase() !== "false";
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
})
|
|
||||||
.optional()
|
|
||||||
.default(true);
|
|
||||||
|
|
||||||
const parseSecretPathSearch = (search?: string) => {
|
const parseSecretPathSearch = (search?: string) => {
|
||||||
if (!search)
|
if (!search)
|
||||||
return {
|
return {
|
||||||
@ -109,6 +100,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
search: z.string().trim().describe(DASHBOARD.SECRET_OVERVIEW_LIST.search).optional(),
|
search: z.string().trim().describe(DASHBOARD.SECRET_OVERVIEW_LIST.search).optional(),
|
||||||
includeSecrets: booleanSchema.describe(DASHBOARD.SECRET_OVERVIEW_LIST.includeSecrets),
|
includeSecrets: booleanSchema.describe(DASHBOARD.SECRET_OVERVIEW_LIST.includeSecrets),
|
||||||
includeFolders: booleanSchema.describe(DASHBOARD.SECRET_OVERVIEW_LIST.includeFolders),
|
includeFolders: booleanSchema.describe(DASHBOARD.SECRET_OVERVIEW_LIST.includeFolders),
|
||||||
|
includeImports: booleanSchema.describe(DASHBOARD.SECRET_OVERVIEW_LIST.includeImports),
|
||||||
includeDynamicSecrets: booleanSchema.describe(DASHBOARD.SECRET_OVERVIEW_LIST.includeDynamicSecrets)
|
includeDynamicSecrets: booleanSchema.describe(DASHBOARD.SECRET_OVERVIEW_LIST.includeDynamicSecrets)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
@ -124,9 +116,17 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
})
|
})
|
||||||
.array()
|
.array()
|
||||||
.optional(),
|
.optional(),
|
||||||
|
imports: SecretImportsSchema.omit({ importEnv: true })
|
||||||
|
.extend({
|
||||||
|
importEnv: z.object({ name: z.string(), slug: z.string(), id: z.string() }),
|
||||||
|
environment: z.string()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
.optional(),
|
||||||
totalFolderCount: z.number().optional(),
|
totalFolderCount: z.number().optional(),
|
||||||
totalDynamicSecretCount: z.number().optional(),
|
totalDynamicSecretCount: z.number().optional(),
|
||||||
totalSecretCount: z.number().optional(),
|
totalSecretCount: z.number().optional(),
|
||||||
|
totalImportCount: z.number().optional(),
|
||||||
totalCount: z.number()
|
totalCount: z.number()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -143,6 +143,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
orderDirection,
|
orderDirection,
|
||||||
includeFolders,
|
includeFolders,
|
||||||
includeSecrets,
|
includeSecrets,
|
||||||
|
includeImports,
|
||||||
includeDynamicSecrets
|
includeDynamicSecrets
|
||||||
} = req.query;
|
} = req.query;
|
||||||
|
|
||||||
@ -159,6 +160,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
let remainingLimit = limit;
|
let remainingLimit = limit;
|
||||||
let adjustedOffset = offset;
|
let adjustedOffset = offset;
|
||||||
|
|
||||||
|
let imports: Awaited<ReturnType<typeof server.services.secretImport.getImportsMultiEnv>> | undefined;
|
||||||
let folders: Awaited<ReturnType<typeof server.services.folder.getFoldersMultiEnv>> | undefined;
|
let folders: Awaited<ReturnType<typeof server.services.folder.getFoldersMultiEnv>> | undefined;
|
||||||
let secrets: Awaited<ReturnType<typeof server.services.secret.getSecretsRawMultiEnv>> | undefined;
|
let secrets: Awaited<ReturnType<typeof server.services.secret.getSecretsRawMultiEnv>> | undefined;
|
||||||
let dynamicSecrets:
|
let dynamicSecrets:
|
||||||
@ -168,6 +170,53 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
let totalFolderCount: number | undefined;
|
let totalFolderCount: number | undefined;
|
||||||
let totalDynamicSecretCount: number | undefined;
|
let totalDynamicSecretCount: number | undefined;
|
||||||
let totalSecretCount: number | undefined;
|
let totalSecretCount: number | undefined;
|
||||||
|
let totalImportCount: number | undefined;
|
||||||
|
|
||||||
|
if (includeImports) {
|
||||||
|
totalImportCount = await server.services.secretImport.getProjectImportMultiEnvCount({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
projectId,
|
||||||
|
environments,
|
||||||
|
path: secretPath,
|
||||||
|
search
|
||||||
|
});
|
||||||
|
|
||||||
|
if (remainingLimit > 0 && totalImportCount > adjustedOffset) {
|
||||||
|
imports = await server.services.secretImport.getImportsMultiEnv({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
projectId,
|
||||||
|
environments,
|
||||||
|
path: secretPath,
|
||||||
|
search,
|
||||||
|
limit: remainingLimit,
|
||||||
|
offset: adjustedOffset
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
projectId: req.query.projectId,
|
||||||
|
event: {
|
||||||
|
type: EventType.GET_SECRET_IMPORTS,
|
||||||
|
metadata: {
|
||||||
|
environment: environments.join(","),
|
||||||
|
folderId: imports?.[0]?.folderId,
|
||||||
|
numberOfImports: imports.length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
remainingLimit -= imports.length;
|
||||||
|
adjustedOffset = 0;
|
||||||
|
} else {
|
||||||
|
adjustedOffset = Math.max(0, adjustedOffset - totalImportCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (includeFolders) {
|
if (includeFolders) {
|
||||||
// this is the unique count, ie duplicate folders across envs only count as 1
|
// this is the unique count, ie duplicate folders across envs only count as 1
|
||||||
@ -345,10 +394,13 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
folders,
|
folders,
|
||||||
dynamicSecrets,
|
dynamicSecrets,
|
||||||
secrets,
|
secrets,
|
||||||
|
imports,
|
||||||
totalFolderCount,
|
totalFolderCount,
|
||||||
totalDynamicSecretCount,
|
totalDynamicSecretCount,
|
||||||
|
totalImportCount,
|
||||||
totalSecretCount,
|
totalSecretCount,
|
||||||
totalCount: (totalFolderCount ?? 0) + (totalDynamicSecretCount ?? 0) + (totalSecretCount ?? 0)
|
totalCount:
|
||||||
|
(totalFolderCount ?? 0) + (totalDynamicSecretCount ?? 0) + (totalSecretCount ?? 0) + (totalImportCount ?? 0)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -24,7 +24,7 @@ const IdentityKubernetesAuthResponseSchema = IdentityKubernetesAuthsSchema.pick(
|
|||||||
allowedAudience: true
|
allowedAudience: true
|
||||||
}).extend({
|
}).extend({
|
||||||
caCert: z.string(),
|
caCert: z.string(),
|
||||||
tokenReviewerJwt: z.string()
|
tokenReviewerJwt: z.string().optional().nullable()
|
||||||
});
|
});
|
||||||
|
|
||||||
export const registerIdentityKubernetesRouter = async (server: FastifyZodProvider) => {
|
export const registerIdentityKubernetesRouter = async (server: FastifyZodProvider) => {
|
||||||
@ -98,7 +98,7 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide
|
|||||||
.object({
|
.object({
|
||||||
kubernetesHost: z.string().trim().min(1).describe(KUBERNETES_AUTH.ATTACH.kubernetesHost),
|
kubernetesHost: z.string().trim().min(1).describe(KUBERNETES_AUTH.ATTACH.kubernetesHost),
|
||||||
caCert: z.string().trim().default("").describe(KUBERNETES_AUTH.ATTACH.caCert),
|
caCert: z.string().trim().default("").describe(KUBERNETES_AUTH.ATTACH.caCert),
|
||||||
tokenReviewerJwt: z.string().trim().min(1).describe(KUBERNETES_AUTH.ATTACH.tokenReviewerJwt),
|
tokenReviewerJwt: z.string().trim().optional().describe(KUBERNETES_AUTH.ATTACH.tokenReviewerJwt),
|
||||||
allowedNamespaces: z.string().describe(KUBERNETES_AUTH.ATTACH.allowedNamespaces), // TODO: validation
|
allowedNamespaces: z.string().describe(KUBERNETES_AUTH.ATTACH.allowedNamespaces), // TODO: validation
|
||||||
allowedNames: z.string().describe(KUBERNETES_AUTH.ATTACH.allowedNames),
|
allowedNames: z.string().describe(KUBERNETES_AUTH.ATTACH.allowedNames),
|
||||||
allowedAudience: z.string().describe(KUBERNETES_AUTH.ATTACH.allowedAudience),
|
allowedAudience: z.string().describe(KUBERNETES_AUTH.ATTACH.allowedAudience),
|
||||||
@ -195,7 +195,7 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide
|
|||||||
.object({
|
.object({
|
||||||
kubernetesHost: z.string().trim().min(1).optional().describe(KUBERNETES_AUTH.UPDATE.kubernetesHost),
|
kubernetesHost: z.string().trim().min(1).optional().describe(KUBERNETES_AUTH.UPDATE.kubernetesHost),
|
||||||
caCert: z.string().trim().optional().describe(KUBERNETES_AUTH.UPDATE.caCert),
|
caCert: z.string().trim().optional().describe(KUBERNETES_AUTH.UPDATE.caCert),
|
||||||
tokenReviewerJwt: z.string().trim().min(1).optional().describe(KUBERNETES_AUTH.UPDATE.tokenReviewerJwt),
|
tokenReviewerJwt: z.string().trim().nullable().optional().describe(KUBERNETES_AUTH.UPDATE.tokenReviewerJwt),
|
||||||
allowedNamespaces: z.string().optional().describe(KUBERNETES_AUTH.UPDATE.allowedNamespaces), // TODO: validation
|
allowedNamespaces: z.string().optional().describe(KUBERNETES_AUTH.UPDATE.allowedNamespaces), // TODO: validation
|
||||||
allowedNames: z.string().optional().describe(KUBERNETES_AUTH.UPDATE.allowedNames),
|
allowedNames: z.string().optional().describe(KUBERNETES_AUTH.UPDATE.allowedNames),
|
||||||
allowedAudience: z.string().optional().describe(KUBERNETES_AUTH.UPDATE.allowedAudience),
|
allowedAudience: z.string().optional().describe(KUBERNETES_AUTH.UPDATE.allowedAudience),
|
||||||
|
@ -9,6 +9,8 @@ import { readLimit, secretsLimit } from "@app/server/config/rateLimiter";
|
|||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
import { booleanSchema } from "../sanitizedSchemas";
|
||||||
|
|
||||||
export const registerSecretFolderRouter = async (server: FastifyZodProvider) => {
|
export const registerSecretFolderRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
url: "/",
|
url: "/",
|
||||||
@ -347,11 +349,14 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
|
|||||||
.default("/")
|
.default("/")
|
||||||
.transform(prefixWithSlash)
|
.transform(prefixWithSlash)
|
||||||
.transform(removeTrailingSlash)
|
.transform(removeTrailingSlash)
|
||||||
.describe(FOLDERS.LIST.directory)
|
.describe(FOLDERS.LIST.directory),
|
||||||
|
recursive: booleanSchema.default(false).describe(FOLDERS.LIST.recursive)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
folders: SecretFoldersSchema.array()
|
folders: SecretFoldersSchema.extend({
|
||||||
|
relativePath: z.string().optional()
|
||||||
|
}).array()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -84,6 +84,9 @@ export const identityKubernetesAuthServiceFactory = ({
|
|||||||
tokenReviewerJwt = decryptor({
|
tokenReviewerJwt = decryptor({
|
||||||
cipherTextBlob: identityKubernetesAuth.encryptedKubernetesTokenReviewerJwt
|
cipherTextBlob: identityKubernetesAuth.encryptedKubernetesTokenReviewerJwt
|
||||||
}).toString();
|
}).toString();
|
||||||
|
} else {
|
||||||
|
// if no token reviewer is provided means the incoming token has to act as reviewer
|
||||||
|
tokenReviewerJwt = serviceAccountJwt;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data } = await axios
|
const { data } = await axios
|
||||||
@ -291,7 +294,9 @@ export const identityKubernetesAuthServiceFactory = ({
|
|||||||
accessTokenTTL,
|
accessTokenTTL,
|
||||||
accessTokenNumUsesLimit,
|
accessTokenNumUsesLimit,
|
||||||
accessTokenTrustedIps: JSON.stringify(reformattedAccessTokenTrustedIps),
|
accessTokenTrustedIps: JSON.stringify(reformattedAccessTokenTrustedIps),
|
||||||
encryptedKubernetesTokenReviewerJwt: encryptor({ plainText: Buffer.from(tokenReviewerJwt) }).cipherTextBlob,
|
encryptedKubernetesTokenReviewerJwt: tokenReviewerJwt
|
||||||
|
? encryptor({ plainText: Buffer.from(tokenReviewerJwt) }).cipherTextBlob
|
||||||
|
: null,
|
||||||
encryptedKubernetesCaCertificate: encryptor({ plainText: Buffer.from(caCert) }).cipherTextBlob
|
encryptedKubernetesCaCertificate: encryptor({ plainText: Buffer.from(caCert) }).cipherTextBlob
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
@ -387,10 +392,12 @@ export const identityKubernetesAuthServiceFactory = ({
|
|||||||
updateQuery.encryptedKubernetesCaCertificate = encryptor({ plainText: Buffer.from(caCert) }).cipherTextBlob;
|
updateQuery.encryptedKubernetesCaCertificate = encryptor({ plainText: Buffer.from(caCert) }).cipherTextBlob;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tokenReviewerJwt !== undefined) {
|
if (tokenReviewerJwt) {
|
||||||
updateQuery.encryptedKubernetesTokenReviewerJwt = encryptor({
|
updateQuery.encryptedKubernetesTokenReviewerJwt = encryptor({
|
||||||
plainText: Buffer.from(tokenReviewerJwt)
|
plainText: Buffer.from(tokenReviewerJwt)
|
||||||
}).cipherTextBlob;
|
}).cipherTextBlob;
|
||||||
|
} else if (tokenReviewerJwt === null) {
|
||||||
|
updateQuery.encryptedKubernetesTokenReviewerJwt = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedKubernetesAuth = await identityKubernetesAuthDAL.updateById(identityKubernetesAuth.id, updateQuery);
|
const updatedKubernetesAuth = await identityKubernetesAuthDAL.updateById(identityKubernetesAuth.id, updateQuery);
|
||||||
|
@ -9,7 +9,7 @@ export type TAttachKubernetesAuthDTO = {
|
|||||||
identityId: string;
|
identityId: string;
|
||||||
kubernetesHost: string;
|
kubernetesHost: string;
|
||||||
caCert: string;
|
caCert: string;
|
||||||
tokenReviewerJwt: string;
|
tokenReviewerJwt?: string;
|
||||||
allowedNamespaces: string;
|
allowedNamespaces: string;
|
||||||
allowedNames: string;
|
allowedNames: string;
|
||||||
allowedAudience: string;
|
allowedAudience: string;
|
||||||
@ -24,7 +24,7 @@ export type TUpdateKubernetesAuthDTO = {
|
|||||||
identityId: string;
|
identityId: string;
|
||||||
kubernetesHost?: string;
|
kubernetesHost?: string;
|
||||||
caCert?: string;
|
caCert?: string;
|
||||||
tokenReviewerJwt?: string;
|
tokenReviewerJwt?: string | null;
|
||||||
allowedNamespaces?: string;
|
allowedNamespaces?: string;
|
||||||
allowedNames?: string;
|
allowedNames?: string;
|
||||||
allowedAudience?: string;
|
allowedAudience?: string;
|
||||||
|
@ -69,9 +69,15 @@ export const identityUaServiceFactory = ({
|
|||||||
isClientSecretRevoked: false
|
isClientSecretRevoked: false
|
||||||
});
|
});
|
||||||
|
|
||||||
const validClientSecretInfo = clientSecrtInfo.find(({ clientSecretHash }) =>
|
let validClientSecretInfo: (typeof clientSecrtInfo)[0] | null = null;
|
||||||
bcrypt.compareSync(clientSecret, clientSecretHash)
|
for await (const info of clientSecrtInfo) {
|
||||||
);
|
const isMatch = await bcrypt.compare(clientSecret, info.clientSecretHash);
|
||||||
|
if (isMatch) {
|
||||||
|
validClientSecretInfo = info;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!validClientSecretInfo) throw new UnauthorizedError({ message: "Invalid credentials" });
|
if (!validClientSecretInfo) throw new UnauthorizedError({ message: "Invalid credentials" });
|
||||||
|
|
||||||
const { clientSecretTTL, clientSecretNumUses, clientSecretNumUsesLimit } = validClientSecretInfo;
|
const { clientSecretTTL, clientSecretNumUses, clientSecretNumUsesLimit } = validClientSecretInfo;
|
||||||
@ -104,7 +110,7 @@ export const identityUaServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const identityAccessToken = await identityUaDAL.transaction(async (tx) => {
|
const identityAccessToken = await identityUaDAL.transaction(async (tx) => {
|
||||||
const uaClientSecretDoc = await identityUaClientSecretDAL.incrementUsage(validClientSecretInfo.id, tx);
|
const uaClientSecretDoc = await identityUaClientSecretDAL.incrementUsage(validClientSecretInfo!.id, tx);
|
||||||
const newToken = await identityAccessTokenDAL.create(
|
const newToken = await identityAccessTokenDAL.create(
|
||||||
{
|
{
|
||||||
identityId: identityUa.identityId,
|
identityId: identityUa.identityId,
|
||||||
|
@ -923,16 +923,14 @@ const getAppsCodefresh = async ({ accessToken }: { accessToken: string }) => {
|
|||||||
/**
|
/**
|
||||||
* Return list of projects for Windmill integration
|
* Return list of projects for Windmill integration
|
||||||
*/
|
*/
|
||||||
const getAppsWindmill = async ({ accessToken }: { accessToken: string }) => {
|
const getAppsWindmill = async ({ accessToken, url }: { accessToken: string; url?: string | null }) => {
|
||||||
const { data } = await request.get<{ id: string; name: string }[]>(
|
const apiUrl = url ? `${url}/api` : IntegrationUrls.WINDMILL_API_URL;
|
||||||
`${IntegrationUrls.WINDMILL_API_URL}/workspaces/list`,
|
const { data } = await request.get<{ id: string; name: string }[]>(`${apiUrl}/workspaces/list`, {
|
||||||
{
|
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${accessToken}`,
|
Authorization: `Bearer ${accessToken}`,
|
||||||
"Accept-Encoding": "application/json"
|
"Accept-Encoding": "application/json"
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
// check for write access of secrets in windmill workspaces
|
// check for write access of secrets in windmill workspaces
|
||||||
const writeAccessCheck = data.map(async (app) => {
|
const writeAccessCheck = data.map(async (app) => {
|
||||||
@ -941,7 +939,7 @@ const getAppsWindmill = async ({ accessToken }: { accessToken: string }) => {
|
|||||||
const folderPath = "f/folder/variable";
|
const folderPath = "f/folder/variable";
|
||||||
|
|
||||||
const { data: writeUser } = await request.post<object>(
|
const { data: writeUser } = await request.post<object>(
|
||||||
`${IntegrationUrls.WINDMILL_API_URL}/w/${app.id}/variables/create`,
|
`${apiUrl}/w/${app.id}/variables/create`,
|
||||||
{
|
{
|
||||||
path: userPath,
|
path: userPath,
|
||||||
value: "variable",
|
value: "variable",
|
||||||
@ -957,7 +955,7 @@ const getAppsWindmill = async ({ accessToken }: { accessToken: string }) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const { data: writeFolder } = await request.post<object>(
|
const { data: writeFolder } = await request.post<object>(
|
||||||
`${IntegrationUrls.WINDMILL_API_URL}/w/${app.id}/variables/create`,
|
`${apiUrl}/w/${app.id}/variables/create`,
|
||||||
{
|
{
|
||||||
path: folderPath,
|
path: folderPath,
|
||||||
value: "variable",
|
value: "variable",
|
||||||
@ -974,14 +972,14 @@ const getAppsWindmill = async ({ accessToken }: { accessToken: string }) => {
|
|||||||
|
|
||||||
// is write access is allowed then delete the created secrets from workspace
|
// is write access is allowed then delete the created secrets from workspace
|
||||||
if (writeUser && writeFolder) {
|
if (writeUser && writeFolder) {
|
||||||
await request.delete(`${IntegrationUrls.WINDMILL_API_URL}/w/${app.id}/variables/delete/${userPath}`, {
|
await request.delete(`${apiUrl}/w/${app.id}/variables/delete/${userPath}`, {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${accessToken}`,
|
Authorization: `Bearer ${accessToken}`,
|
||||||
"Accept-Encoding": "application/json"
|
"Accept-Encoding": "application/json"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
await request.delete(`${IntegrationUrls.WINDMILL_API_URL}/w/${app.id}/variables/delete/${folderPath}`, {
|
await request.delete(`${apiUrl}/w/${app.id}/variables/delete/${folderPath}`, {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${accessToken}`,
|
Authorization: `Bearer ${accessToken}`,
|
||||||
"Accept-Encoding": "application/json"
|
"Accept-Encoding": "application/json"
|
||||||
@ -1316,7 +1314,8 @@ export const getApps = async ({
|
|||||||
|
|
||||||
case Integrations.WINDMILL:
|
case Integrations.WINDMILL:
|
||||||
return getAppsWindmill({
|
return getAppsWindmill({
|
||||||
accessToken
|
accessToken,
|
||||||
|
url
|
||||||
});
|
});
|
||||||
|
|
||||||
case Integrations.DIGITAL_OCEAN_APP_PLATFORM:
|
case Integrations.DIGITAL_OCEAN_APP_PLATFORM:
|
||||||
|
@ -4127,10 +4127,10 @@ const syncSecretsWindmill = async ({
|
|||||||
is_secret: boolean;
|
is_secret: boolean;
|
||||||
description?: string;
|
description?: string;
|
||||||
}
|
}
|
||||||
|
const apiUrl = integration.url ? `${integration.url}/api` : IntegrationUrls.WINDMILL_API_URL;
|
||||||
// get secrets stored in windmill workspace
|
// get secrets stored in windmill workspace
|
||||||
const res = (
|
const res = (
|
||||||
await request.get<WindmillSecret[]>(`${IntegrationUrls.WINDMILL_API_URL}/w/${integration.appId}/variables/list`, {
|
await request.get<WindmillSecret[]>(`${apiUrl}/w/${integration.appId}/variables/list`, {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${accessToken}`,
|
Authorization: `Bearer ${accessToken}`,
|
||||||
"Accept-Encoding": "application/json"
|
"Accept-Encoding": "application/json"
|
||||||
@ -4146,7 +4146,6 @@ const syncSecretsWindmill = async ({
|
|||||||
|
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
const pattern = new RegExp("^(u/|f/)[a-zA-Z0-9_-]+/([a-zA-Z0-9_-]+/)*[a-zA-Z0-9_-]*[^/]$");
|
const pattern = new RegExp("^(u/|f/)[a-zA-Z0-9_-]+/([a-zA-Z0-9_-]+/)*[a-zA-Z0-9_-]*[^/]$");
|
||||||
|
|
||||||
for await (const key of Object.keys(secrets)) {
|
for await (const key of Object.keys(secrets)) {
|
||||||
if ((key.startsWith("u/") || key.startsWith("f/")) && pattern.test(key)) {
|
if ((key.startsWith("u/") || key.startsWith("f/")) && pattern.test(key)) {
|
||||||
if (!(key in res)) {
|
if (!(key in res)) {
|
||||||
@ -4154,7 +4153,7 @@ const syncSecretsWindmill = async ({
|
|||||||
// -> create secret
|
// -> create secret
|
||||||
|
|
||||||
await request.post(
|
await request.post(
|
||||||
`${IntegrationUrls.WINDMILL_API_URL}/w/${integration.appId}/variables/create`,
|
`${apiUrl}/w/${integration.appId}/variables/create`,
|
||||||
{
|
{
|
||||||
path: key,
|
path: key,
|
||||||
value: secrets[key].value,
|
value: secrets[key].value,
|
||||||
@ -4171,7 +4170,7 @@ const syncSecretsWindmill = async ({
|
|||||||
} else {
|
} else {
|
||||||
// -> update secret
|
// -> update secret
|
||||||
await request.post(
|
await request.post(
|
||||||
`${IntegrationUrls.WINDMILL_API_URL}/w/${integration.appId}/variables/update/${res[key].path}`,
|
`${apiUrl}/w/${integration.appId}/variables/update/${res[key].path}`,
|
||||||
{
|
{
|
||||||
path: key,
|
path: key,
|
||||||
value: secrets[key].value,
|
value: secrets[key].value,
|
||||||
@ -4192,16 +4191,13 @@ const syncSecretsWindmill = async ({
|
|||||||
for await (const key of Object.keys(res)) {
|
for await (const key of Object.keys(res)) {
|
||||||
if (!(key in secrets)) {
|
if (!(key in secrets)) {
|
||||||
// -> delete secret
|
// -> delete secret
|
||||||
await request.delete(
|
await request.delete(`${apiUrl}/w/${integration.appId}/variables/delete/${res[key].path}`, {
|
||||||
`${IntegrationUrls.WINDMILL_API_URL}/w/${integration.appId}/variables/delete/${res[key].path}`,
|
|
||||||
{
|
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${accessToken}`,
|
Authorization: `Bearer ${accessToken}`,
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"Accept-Encoding": "application/json"
|
"Accept-Encoding": "application/json"
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { TDbClient } from "@app/db";
|
import { TDbClient } from "@app/db";
|
||||||
import { TableName, TProjectEnvironments, TSecretFolders, TSecretFoldersUpdate } from "@app/db/schemas";
|
import { TableName, TSecretFolders, TSecretFoldersUpdate } from "@app/db/schemas";
|
||||||
import { BadRequestError, DatabaseError } from "@app/lib/errors";
|
import { BadRequestError, DatabaseError } from "@app/lib/errors";
|
||||||
import { groupBy, removeTrailingSlash } from "@app/lib/fn";
|
import { groupBy, removeTrailingSlash } from "@app/lib/fn";
|
||||||
import { ormify, selectAllTableCols } from "@app/lib/knex";
|
import { ormify, selectAllTableCols } from "@app/lib/knex";
|
||||||
@ -41,12 +41,12 @@ const sqlFindMultipleFolderByEnvPathQuery = (db: Knex, query: Array<{ envId: str
|
|||||||
void baseQb
|
void baseQb
|
||||||
.select({
|
.select({
|
||||||
depth: 1,
|
depth: 1,
|
||||||
// latestFolderVerId: db.raw("NULL::uuid"),
|
|
||||||
path: db.raw("'/'")
|
path: db.raw("'/'")
|
||||||
})
|
})
|
||||||
.from(TableName.SecretFolder)
|
.from(TableName.SecretFolder)
|
||||||
.where({
|
.where({
|
||||||
parentId: null
|
parentId: null,
|
||||||
|
name: "root"
|
||||||
})
|
})
|
||||||
.whereIn(
|
.whereIn(
|
||||||
"envId",
|
"envId",
|
||||||
@ -69,9 +69,7 @@ const sqlFindMultipleFolderByEnvPathQuery = (db: Knex, query: Array<{ envId: str
|
|||||||
.where((wb) =>
|
.where((wb) =>
|
||||||
formatedQuery.map(({ secretPath }) =>
|
formatedQuery.map(({ secretPath }) =>
|
||||||
wb.orWhereRaw(
|
wb.orWhereRaw(
|
||||||
`depth = array_position(ARRAY[${secretPath.map(() => "?").join(",")}]::varchar[], ${
|
`secret_folders.name = (ARRAY[${secretPath.map(() => "?").join(",")}]::varchar[])[depth]`,
|
||||||
TableName.SecretFolder
|
|
||||||
}.name,depth)`,
|
|
||||||
[...secretPath]
|
[...secretPath]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -107,7 +105,6 @@ const sqlFindFolderByPathQuery = (db: Knex, projectId: string, environments: str
|
|||||||
void baseQb
|
void baseQb
|
||||||
.select({
|
.select({
|
||||||
depth: 1,
|
depth: 1,
|
||||||
// latestFolderVerId: db.raw("NULL::uuid"),
|
|
||||||
path: db.raw("'/'")
|
path: db.raw("'/'")
|
||||||
})
|
})
|
||||||
.from(TableName.SecretFolder)
|
.from(TableName.SecretFolder)
|
||||||
@ -117,6 +114,11 @@ const sqlFindFolderByPathQuery = (db: Knex, projectId: string, environments: str
|
|||||||
parentId: null
|
parentId: null
|
||||||
})
|
})
|
||||||
.whereIn(`${TableName.Environment}.slug`, environments)
|
.whereIn(`${TableName.Environment}.slug`, environments)
|
||||||
|
.select(
|
||||||
|
db.ref("slug").withSchema(TableName.Environment).as("envSlug"),
|
||||||
|
db.ref("name").withSchema(TableName.Environment).as("envName"),
|
||||||
|
db.ref("projectId").withSchema(TableName.Environment)
|
||||||
|
)
|
||||||
.select(selectAllTableCols(TableName.SecretFolder))
|
.select(selectAllTableCols(TableName.SecretFolder))
|
||||||
.union(
|
.union(
|
||||||
(qb) =>
|
(qb) =>
|
||||||
@ -128,21 +130,20 @@ const sqlFindFolderByPathQuery = (db: Knex, projectId: string, environments: str
|
|||||||
depth: db.raw("parent.depth + 1"),
|
depth: db.raw("parent.depth + 1"),
|
||||||
path: db.raw(
|
path: db.raw(
|
||||||
"CONCAT((CASE WHEN parent.path = '/' THEN '' ELSE parent.path END),'/', secret_folders.name)"
|
"CONCAT((CASE WHEN parent.path = '/' THEN '' ELSE parent.path END),'/', secret_folders.name)"
|
||||||
)
|
),
|
||||||
|
envSlug: db.ref("envSlug").withSchema("parent"),
|
||||||
|
envName: db.ref("envName").withSchema("parent"),
|
||||||
|
projectId: db.ref("projectId").withSchema("parent")
|
||||||
})
|
})
|
||||||
.select(selectAllTableCols(TableName.SecretFolder))
|
.select(selectAllTableCols(TableName.SecretFolder))
|
||||||
.whereRaw(
|
.whereRaw(`secret_folders.name = (ARRAY[${pathSegments.map(() => "?").join(",")}]::varchar[])[depth]`, [
|
||||||
`depth = array_position(ARRAY[${pathSegments
|
...pathSegments
|
||||||
.map(() => "?")
|
])
|
||||||
.join(",")}]::varchar[], secret_folders.name,depth)`,
|
|
||||||
[...pathSegments]
|
|
||||||
)
|
|
||||||
.from(TableName.SecretFolder)
|
.from(TableName.SecretFolder)
|
||||||
.join("parent", "parent.id", `${TableName.SecretFolder}.parentId`)
|
.join("parent", "parent.id", `${TableName.SecretFolder}.parentId`)
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.from<TSecretFolders & { depth: number; path: string }>("parent")
|
.from<TSecretFolders & { depth: number; path: string }>("parent")
|
||||||
.leftJoin<TProjectEnvironments>(TableName.Environment, `${TableName.Environment}.id`, "parent.envId")
|
|
||||||
.select<
|
.select<
|
||||||
(TSecretFolders & {
|
(TSecretFolders & {
|
||||||
depth: number;
|
depth: number;
|
||||||
@ -152,13 +153,7 @@ const sqlFindFolderByPathQuery = (db: Knex, projectId: string, environments: str
|
|||||||
envName: string;
|
envName: string;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
})[]
|
})[]
|
||||||
>(
|
>(selectAllTableCols("parent" as TableName.SecretFolder));
|
||||||
selectAllTableCols("parent" as TableName.SecretFolder),
|
|
||||||
db.ref("id").withSchema(TableName.Environment).as("envId"),
|
|
||||||
db.ref("slug").withSchema(TableName.Environment).as("envSlug"),
|
|
||||||
db.ref("name").withSchema(TableName.Environment).as("envName"),
|
|
||||||
db.ref("projectId").withSchema(TableName.Environment)
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const sqlFindSecretPathByFolderId = (db: Knex, projectId: string, folderIds: string[]) =>
|
const sqlFindSecretPathByFolderId = (db: Knex, projectId: string, folderIds: string[]) =>
|
||||||
@ -220,19 +215,12 @@ export const secretFolderDALFactory = (db: TDbClient) => {
|
|||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
message: "Invalid secret path. Only alphanumeric characters, dashes, and underscores are allowed."
|
message: "Invalid secret path. Only alphanumeric characters, dashes, and underscores are allowed."
|
||||||
});
|
});
|
||||||
|
const formatedPath = removeTrailingSlash(path);
|
||||||
try {
|
try {
|
||||||
const folder = await sqlFindFolderByPathQuery(
|
const query = sqlFindFolderByPathQuery(tx || db.replicaNode(), projectId, [environment], formatedPath)
|
||||||
tx || db.replicaNode(),
|
.where("path", formatedPath)
|
||||||
projectId,
|
|
||||||
[environment],
|
|
||||||
removeTrailingSlash(path)
|
|
||||||
)
|
|
||||||
.orderBy("depth", "desc")
|
|
||||||
.first();
|
.first();
|
||||||
if (folder && folder.path !== removeTrailingSlash(path)) {
|
const folder = await query;
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!folder) return;
|
if (!folder) return;
|
||||||
const { envId: id, envName: name, envSlug: slug, ...el } = folder;
|
const { envId: id, envName: name, envSlug: slug, ...el } = folder;
|
||||||
return { ...el, envId: id, environment: { id, name, slug } };
|
return { ...el, envId: id, environment: { id, name, slug } };
|
||||||
@ -250,22 +238,13 @@ export const secretFolderDALFactory = (db: TDbClient) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const pathDepth = removeTrailingSlash(path).split("/").filter(Boolean).length + 1;
|
const formatedPath = removeTrailingSlash(path);
|
||||||
|
|
||||||
const folders = await sqlFindFolderByPathQuery(
|
const folders = await sqlFindFolderByPathQuery(
|
||||||
tx || db.replicaNode(),
|
tx || db.replicaNode(),
|
||||||
projectId,
|
projectId,
|
||||||
environments,
|
environments,
|
||||||
removeTrailingSlash(path)
|
formatedPath
|
||||||
)
|
).where("path", removeTrailingSlash(path));
|
||||||
.orderBy("depth", "desc")
|
|
||||||
.where("depth", pathDepth);
|
|
||||||
|
|
||||||
const firstFolder = folders[0];
|
|
||||||
|
|
||||||
if (firstFolder && firstFolder.path !== removeTrailingSlash(path)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return folders.map((folder) => {
|
return folders.map((folder) => {
|
||||||
const { envId: id, envName: name, envSlug: slug, ...el } = folder;
|
const { envId: id, envName: name, envSlug: slug, ...el } = folder;
|
||||||
@ -323,7 +302,6 @@ export const secretFolderDALFactory = (db: TDbClient) => {
|
|||||||
const findSecretPathByFolderIds = async (projectId: string, folderIds: string[], tx?: Knex) => {
|
const findSecretPathByFolderIds = async (projectId: string, folderIds: string[], tx?: Knex) => {
|
||||||
try {
|
try {
|
||||||
const folders = await sqlFindSecretPathByFolderId(tx || db.replicaNode(), projectId, folderIds);
|
const folders = await sqlFindSecretPathByFolderId(tx || db.replicaNode(), projectId, folderIds);
|
||||||
|
|
||||||
// travelling all the way from leaf node to root contains real path
|
// travelling all the way from leaf node to root contains real path
|
||||||
const rootFolders = groupBy(
|
const rootFolders = groupBy(
|
||||||
folders.filter(({ parentId }) => parentId === null),
|
folders.filter(({ parentId }) => parentId === null),
|
||||||
|
@ -401,7 +401,8 @@ export const secretFolderServiceFactory = ({
|
|||||||
orderBy,
|
orderBy,
|
||||||
orderDirection,
|
orderDirection,
|
||||||
limit,
|
limit,
|
||||||
offset
|
offset,
|
||||||
|
recursive
|
||||||
}: TGetFolderDTO) => {
|
}: TGetFolderDTO) => {
|
||||||
// folder list is allowed to be read by anyone
|
// folder list is allowed to be read by anyone
|
||||||
// permission to check does user has access
|
// permission to check does user has access
|
||||||
@ -420,6 +421,17 @@ export const secretFolderServiceFactory = ({
|
|||||||
const parentFolder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
|
const parentFolder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
|
||||||
if (!parentFolder) return [];
|
if (!parentFolder) return [];
|
||||||
|
|
||||||
|
if (recursive) {
|
||||||
|
const recursiveFolders = await folderDAL.findByEnvsDeep({ parentIds: [parentFolder.id] });
|
||||||
|
// remove the parent folder
|
||||||
|
return recursiveFolders
|
||||||
|
.filter((folder) => folder.id !== parentFolder.id)
|
||||||
|
.map((folder) => ({
|
||||||
|
...folder,
|
||||||
|
relativePath: folder.path
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
const folders = await folderDAL.find(
|
const folders = await folderDAL.find(
|
||||||
{
|
{
|
||||||
envId: env.id,
|
envId: env.id,
|
||||||
|
@ -45,6 +45,7 @@ export type TGetFolderDTO = {
|
|||||||
orderDirection?: OrderByDirection;
|
orderDirection?: OrderByDirection;
|
||||||
limit?: number;
|
limit?: number;
|
||||||
offset?: number;
|
offset?: number;
|
||||||
|
recursive?: boolean;
|
||||||
} & TProjectPermission;
|
} & TProjectPermission;
|
||||||
|
|
||||||
export type TGetFolderByIdDTO = {
|
export type TGetFolderByIdDTO = {
|
||||||
|
@ -469,6 +469,58 @@ export const secretImportServiceFactory = ({
|
|||||||
return count;
|
return count;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getProjectImportMultiEnvCount = async ({
|
||||||
|
path: secretPath,
|
||||||
|
environments,
|
||||||
|
projectId,
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
search
|
||||||
|
}: Omit<TGetSecretImportsDTO, "environment"> & { environments: string[] }) => {
|
||||||
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
|
const filteredEnvironments = [];
|
||||||
|
for (const environment of environments) {
|
||||||
|
if (
|
||||||
|
permission.can(
|
||||||
|
ProjectPermissionActions.Read,
|
||||||
|
subject(ProjectPermissionSub.SecretImports, { environment, secretPath })
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
filteredEnvironments.push(environment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (filteredEnvironments.length === 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const environment of filteredEnvironments) {
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Read,
|
||||||
|
subject(ProjectPermissionSub.SecretImports, { environment, secretPath })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const folders = await folderDAL.findBySecretPathMultiEnv(projectId, environments, secretPath);
|
||||||
|
if (!folders?.length)
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Folder with path '${secretPath}' not found on environments with slugs '${environments.join(", ")}'`
|
||||||
|
});
|
||||||
|
const counts = await Promise.all(
|
||||||
|
folders.map((folder) => secretImportDAL.getProjectImportCount({ folderId: folder.id, search }))
|
||||||
|
);
|
||||||
|
|
||||||
|
return counts.reduce((sum, count) => sum + count, 0);
|
||||||
|
};
|
||||||
|
|
||||||
const getImports = async ({
|
const getImports = async ({
|
||||||
path: secretPath,
|
path: secretPath,
|
||||||
environment,
|
environment,
|
||||||
@ -688,6 +740,59 @@ export const secretImportServiceFactory = ({
|
|||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getImportsMultiEnv = async ({
|
||||||
|
path: secretPath,
|
||||||
|
environments,
|
||||||
|
projectId,
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
search,
|
||||||
|
limit,
|
||||||
|
offset
|
||||||
|
}: Omit<TGetSecretImportsDTO, "environment"> & { environments: string[] }) => {
|
||||||
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
|
const filteredEnvironments = [];
|
||||||
|
for (const environment of environments) {
|
||||||
|
if (
|
||||||
|
permission.can(
|
||||||
|
ProjectPermissionActions.Read,
|
||||||
|
subject(ProjectPermissionSub.SecretImports, { environment, secretPath })
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
filteredEnvironments.push(environment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (filteredEnvironments.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const folders = await folderDAL.findBySecretPathMultiEnv(projectId, filteredEnvironments, secretPath);
|
||||||
|
if (!folders?.length)
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Folder with path '${secretPath}' not found on environments with slugs '${environments.join(", ")}'`
|
||||||
|
});
|
||||||
|
|
||||||
|
const secImportsArrays = await Promise.all(
|
||||||
|
folders.map(async (folder) => {
|
||||||
|
const imports = await secretImportDAL.find({ folderId: folder.id, search, limit, offset });
|
||||||
|
return imports.map((importItem) => ({
|
||||||
|
...importItem,
|
||||||
|
environment: folder.environment.slug
|
||||||
|
}));
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return secImportsArrays.flat();
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
createImport,
|
createImport,
|
||||||
updateImport,
|
updateImport,
|
||||||
@ -698,6 +803,8 @@ export const secretImportServiceFactory = ({
|
|||||||
getRawSecretsFromImports,
|
getRawSecretsFromImports,
|
||||||
resyncSecretImportReplication,
|
resyncSecretImportReplication,
|
||||||
getProjectImportCount,
|
getProjectImportCount,
|
||||||
fnSecretsFromImports
|
fnSecretsFromImports,
|
||||||
|
getProjectImportMultiEnvCount,
|
||||||
|
getImportsMultiEnv
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -148,6 +148,28 @@ var gatewayInstallCmd = &cobra.Command{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var gatewayUninstallCmd = &cobra.Command{
|
||||||
|
Use: "uninstall",
|
||||||
|
Short: "Uninstall and remove systemd service for the gateway (requires sudo)",
|
||||||
|
Long: "Uninstall and remove systemd service for the gateway. Must be run with sudo on Linux.",
|
||||||
|
Example: "sudo infisical gateway uninstall",
|
||||||
|
DisableFlagsInUseLine: true,
|
||||||
|
Args: cobra.NoArgs,
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
if runtime.GOOS != "linux" {
|
||||||
|
util.HandleError(fmt.Errorf("systemd service installation is only supported on Linux"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if os.Geteuid() != 0 {
|
||||||
|
util.HandleError(fmt.Errorf("systemd service installation requires root/sudo privileges"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := gateway.UninstallGatewaySystemdService(); err != nil {
|
||||||
|
util.HandleError(err, "Failed to uninstall systemd service")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
var gatewayRelayCmd = &cobra.Command{
|
var gatewayRelayCmd = &cobra.Command{
|
||||||
Example: `infisical gateway relay`,
|
Example: `infisical gateway relay`,
|
||||||
Short: "Used to run infisical gateway relay",
|
Short: "Used to run infisical gateway relay",
|
||||||
@ -183,6 +205,7 @@ func init() {
|
|||||||
gatewayRelayCmd.Flags().String("config", "", "Relay config yaml file path")
|
gatewayRelayCmd.Flags().String("config", "", "Relay config yaml file path")
|
||||||
|
|
||||||
gatewayCmd.AddCommand(gatewayInstallCmd)
|
gatewayCmd.AddCommand(gatewayInstallCmd)
|
||||||
|
gatewayCmd.AddCommand(gatewayUninstallCmd)
|
||||||
gatewayCmd.AddCommand(gatewayRelayCmd)
|
gatewayCmd.AddCommand(gatewayRelayCmd)
|
||||||
rootCmd.AddCommand(gatewayCmd)
|
rootCmd.AddCommand(gatewayCmd)
|
||||||
}
|
}
|
||||||
|
@ -89,7 +89,7 @@ func (g *Gateway) ConnectWithRelay() error {
|
|||||||
turnClientCfg.Conn = turn.NewSTUNConn(conn)
|
turnClientCfg.Conn = turn.NewSTUNConn(conn)
|
||||||
} else {
|
} else {
|
||||||
log.Info().Msgf("Provided relay port %s. Using non TLS connection.", relayPort)
|
log.Info().Msgf("Provided relay port %s. Using non TLS connection.", relayPort)
|
||||||
conn, err := net.ListenPacket("udp4", turnAddr.String())
|
conn, err := net.ListenPacket("udp4", "0.0.0.0:0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Failed to connect with relay server: %w", err)
|
return fmt.Errorf("Failed to connect with relay server: %w", err)
|
||||||
}
|
}
|
||||||
@ -342,7 +342,9 @@ func (g *Gateway) registerRelayIsActive(ctx context.Context, errCh chan error) e
|
|||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
log.Debug().Msg("Performing relay connection health check")
|
log.Debug().Msg("Performing relay connection health check")
|
||||||
err := g.createPermissionForStaticIps(g.config.InfisicalStaticIp)
|
err := g.createPermissionForStaticIps(g.config.InfisicalStaticIp)
|
||||||
if err != nil && !strings.Contains(err.Error(), "tls:") {
|
// try again error message from server happens to avoid congestion
|
||||||
|
// https://github.com/pion/turn/blob/master/internal/client/udp_conn.go#L382
|
||||||
|
if err != nil && !strings.Contains(err.Error(), "try again") {
|
||||||
failures++
|
failures++
|
||||||
log.Warn().Err(err).Int("failures", failures).Msg("Failed to refresh TURN permissions")
|
log.Warn().Err(err).Int("failures", failures).Msg("Failed to refresh TURN permissions")
|
||||||
if failures >= maxFailures {
|
if failures >= maxFailures {
|
||||||
@ -351,6 +353,7 @@ func (g *Gateway) registerRelayIsActive(ctx context.Context, errCh chan error) e
|
|||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
failures = 0 // reset
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
@ -15,7 +15,8 @@ Description=Infisical Gateway Service
|
|||||||
After=network.target
|
After=network.target
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
Type=notify
|
||||||
|
NotifyAccess=all
|
||||||
EnvironmentFile=/etc/infisical/gateway.conf
|
EnvironmentFile=/etc/infisical/gateway.conf
|
||||||
ExecStart=infisical gateway
|
ExecStart=infisical gateway
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
@ -50,8 +51,6 @@ func InstallGatewaySystemdService(token string, domain string) error {
|
|||||||
configContent := fmt.Sprintf("INFISICAL_UNIVERSAL_AUTH_ACCESS_TOKEN=%s\n", token)
|
configContent := fmt.Sprintf("INFISICAL_UNIVERSAL_AUTH_ACCESS_TOKEN=%s\n", token)
|
||||||
if domain != "" {
|
if domain != "" {
|
||||||
configContent += fmt.Sprintf("INFISICAL_API_URL=%s\n", domain)
|
configContent += fmt.Sprintf("INFISICAL_API_URL=%s\n", domain)
|
||||||
} else {
|
|
||||||
configContent += "INFISICAL_API_URL=\n"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
configPath := filepath.Join(configDir, "gateway.conf")
|
configPath := filepath.Join(configDir, "gateway.conf")
|
||||||
@ -60,11 +59,6 @@ func InstallGatewaySystemdService(token string, domain string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
servicePath := "/etc/systemd/system/infisical-gateway.service"
|
servicePath := "/etc/systemd/system/infisical-gateway.service"
|
||||||
if _, err := os.Stat(servicePath); err == nil {
|
|
||||||
log.Info().Msg("Systemd service file already exists")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := os.WriteFile(servicePath, []byte(systemdServiceTemplate), 0644); err != nil {
|
if err := os.WriteFile(servicePath, []byte(systemdServiceTemplate), 0644); err != nil {
|
||||||
return fmt.Errorf("failed to write systemd service file: %v", err)
|
return fmt.Errorf("failed to write systemd service file: %v", err)
|
||||||
}
|
}
|
||||||
@ -80,3 +74,48 @@ func InstallGatewaySystemdService(token string, domain string) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func UninstallGatewaySystemdService() error {
|
||||||
|
if runtime.GOOS != "linux" {
|
||||||
|
log.Info().Msg("Skipping systemd service uninstallation - not on Linux")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if os.Geteuid() != 0 {
|
||||||
|
log.Info().Msg("Skipping systemd service uninstallation - not running as root/sudo")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop the service if it's running
|
||||||
|
stopCmd := exec.Command("systemctl", "stop", "infisical-gateway")
|
||||||
|
if err := stopCmd.Run(); err != nil {
|
||||||
|
log.Warn().Msgf("Failed to stop service: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable the service
|
||||||
|
disableCmd := exec.Command("systemctl", "disable", "infisical-gateway")
|
||||||
|
if err := disableCmd.Run(); err != nil {
|
||||||
|
log.Warn().Msgf("Failed to disable service: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the service file
|
||||||
|
servicePath := "/etc/systemd/system/infisical-gateway.service"
|
||||||
|
if err := os.Remove(servicePath); err != nil && !os.IsNotExist(err) {
|
||||||
|
return fmt.Errorf("failed to remove systemd service file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the configuration file
|
||||||
|
configPath := "/etc/infisical/gateway.conf"
|
||||||
|
if err := os.Remove(configPath); err != nil && !os.IsNotExist(err) {
|
||||||
|
return fmt.Errorf("failed to remove config file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reload systemd to apply changes
|
||||||
|
reloadCmd := exec.Command("systemctl", "daemon-reload")
|
||||||
|
if err := reloadCmd.Run(); err != nil {
|
||||||
|
return fmt.Errorf("failed to reload systemd: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info().Msg("Successfully uninstalled Infisical Gateway systemd service")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -56,6 +56,7 @@ func WriteInitalConfig(userCredentials *models.UserCredentials) error {
|
|||||||
LoggedInUsers: existingConfigFile.LoggedInUsers,
|
LoggedInUsers: existingConfigFile.LoggedInUsers,
|
||||||
VaultBackendType: existingConfigFile.VaultBackendType,
|
VaultBackendType: existingConfigFile.VaultBackendType,
|
||||||
VaultBackendPassphrase: existingConfigFile.VaultBackendPassphrase,
|
VaultBackendPassphrase: existingConfigFile.VaultBackendPassphrase,
|
||||||
|
Domains: existingConfigFile.Domains,
|
||||||
}
|
}
|
||||||
|
|
||||||
configFileMarshalled, err := json.Marshal(configFile)
|
configFileMarshalled, err := json.Marshal(configFile)
|
||||||
|
@ -245,8 +245,9 @@ func getCurrentBranch() (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func AppendAPIEndpoint(address string) string {
|
func AppendAPIEndpoint(address string) string {
|
||||||
|
// if it's empty return as it is
|
||||||
// Ensure the address does not already end with "/api"
|
// Ensure the address does not already end with "/api"
|
||||||
if strings.HasSuffix(address, "/api") {
|
if address == "" || strings.HasSuffix(address, "/api") {
|
||||||
return address
|
return address
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,20 +9,76 @@ description: "Track evert event action performed within Infisical projects."
|
|||||||
If you're using Infisical Cloud, then it is available under the **Pro**,
|
If you're using Infisical Cloud, then it is available under the **Pro**,
|
||||||
and **Enterprise Tier** with varying retention periods. If you're self-hosting Infisical,
|
and **Enterprise Tier** with varying retention periods. If you're self-hosting Infisical,
|
||||||
then you should contact sales@infisical.com to purchase an enterprise license to use it.
|
then you should contact sales@infisical.com to purchase an enterprise license to use it.
|
||||||
|
|
||||||
</Info>
|
</Info>
|
||||||
|
|
||||||
Infisical provides audit logs for security and compliance teams to monitor information access.
|
Infisical provides audit logs for security and compliance teams to monitor information access.
|
||||||
With the Audit Log functionality, teams can:
|
With the Audit Log functionality, teams can:
|
||||||
|
|
||||||
- **Track** 40+ different events;
|
- **Track** 40+ different events;
|
||||||
- **Filter** audit logs by event, actor, source, date or any combination of these filters;
|
- **Filter** audit logs by event, actor, source, date or any combination of these filters;
|
||||||
- **Inspect** extensive metadata in the event of any suspicious activity or incident review.
|
- **Inspect** extensive metadata in the event of any suspicious activity or incident review.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
## Audit Log Structure
|
||||||
|
|
||||||
Each log contains the following data:
|
Each log contains the following data:
|
||||||
|
|
||||||
- **Event**: The underlying action such as create, list, read, update, or delete secret(s).
|
| Field | Type | Description | Purpose |
|
||||||
- **Actor**: The entity responsible for performing or causing the event; this can be a user or service.
|
| ------------------------- | -------- | --------------------------------------------------------- | ------------------------------------------------------------- |
|
||||||
- **Timestamp**: The date and time at which point the event occurred.
|
| **event** | Object | Contains details about the action performed | Captures what happened |
|
||||||
- **Source** (User agent + IP): The software (user agent) and network address (IP) from which the event was initiated.
|
| event.type | String | The specific action that occurred (e.g., "create-secret") | Identifies the exact operation |
|
||||||
- **Metadata**: Additional data to provide context for each event. For example, this could be the path at which a secret was fetched from etc.
|
| event.metadata | Object | Context-specific details about the event | Provides detailed information relevant to the specific action |
|
||||||
|
| **actor** | Object | Information about who performed the action | Identifies the responsible entity |
|
||||||
|
| actor.type | String | Category of actor (user, service, identity, etc.) | Distinguishes between human and non-human actors |
|
||||||
|
| actor.metadata | Object | Details about the specific actor | Provides identity information |
|
||||||
|
| actor.metadata.userId | String | Unique identifier for user actors | Links to specific user account |
|
||||||
|
| actor.metadata.email | String | Email address for user actors | Email of the executing user |
|
||||||
|
| actor.metadata.username | String | Username for user actors | Username of the executing user |
|
||||||
|
| actor.metadata.serviceId | String | Identifier for service actors | ID of specific service token |
|
||||||
|
| actor.metadata.identityId | String | Identifier for identity actors | ID to specific identity |
|
||||||
|
| actor.metadata.permission | Object | Permission context for the action | Shows permission template data when action was performed |
|
||||||
|
| **orgId** | String | Organization identifier | Indicates which organization the action occurred in |
|
||||||
|
| **projectId** | String | Project identifier | Indicates which project the action affected |
|
||||||
|
| **ipAddress** | String | Source IP address | Shows where the request originated from |
|
||||||
|
| **userAgent** | String | Client application information | Identifies browser or application used |
|
||||||
|
| **userAgentType** | String | Category of client (web, CLI, SDK, etc.) | Classifies the access method |
|
||||||
|
| **timestamp** | DateTime | When the action occurred | Records the exact time of the event |
|
||||||
|
|
||||||
|
<Accordion title="Example Payload">
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "[UUID]",
|
||||||
|
"ipAddress": "[IP_ADDRESS]",
|
||||||
|
"userAgent": "[USER_AGENT_STRING]",
|
||||||
|
"userAgentType": "web",
|
||||||
|
"expiresAt": "[TIMESTAMP]",
|
||||||
|
"createdAt": "[TIMESTAMP]",
|
||||||
|
"updatedAt": "[TIMESTAMP]",
|
||||||
|
"orgId": "[ORGANIZATION_UUID]",
|
||||||
|
"projectId": "[PROJECT_UUID]",
|
||||||
|
"projectName": "[PROJECT_NAME]",
|
||||||
|
"event": {
|
||||||
|
"type": "get-secrets",
|
||||||
|
"metadata": {
|
||||||
|
"secretPath": "[PATH]",
|
||||||
|
"environment": "[ENVIRONMENT_NAME]",
|
||||||
|
"numberOfSecrets": [NUMBER]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"actor": {
|
||||||
|
"type": "user",
|
||||||
|
"metadata": {
|
||||||
|
"email": "[EMAIL]",
|
||||||
|
"userId": "[USER_UUID]",
|
||||||
|
"username": "[USERNAME]",
|
||||||
|
"permission": {
|
||||||
|
"metadata": {},
|
||||||
|
"auth": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</Accordion>
|
||||||
|
@ -37,7 +37,8 @@ then Infisical returns a short-lived access token that can be used to make authe
|
|||||||
To be more specific:
|
To be more specific:
|
||||||
|
|
||||||
1. The application deployed on Kubernetes retrieves its [service account credential](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#opt-out-of-api-credential-automounting) that is a JWT token at the `/var/run/secrets/kubernetes.io/serviceaccount/token` pod path.
|
1. The application deployed on Kubernetes retrieves its [service account credential](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#opt-out-of-api-credential-automounting) that is a JWT token at the `/var/run/secrets/kubernetes.io/serviceaccount/token` pod path.
|
||||||
2. The application sends the JWT token to Infisical at the `/api/v1/auth/kubernetes-auth/login` endpoint after which Infisical forwards the JWT token to the Kubernetes API Server at the [TokenReview API](https://kubernetes.io/docs/reference/kubernetes-api/authentication-resources/token-review-v1/) for verification and to obtain the service account information associated with the JWT token. Infisical is able to authenticate and interact with the TokenReview API by using a long-lived service account JWT token itself (referred to onward as the token reviewer JWT token).
|
2. The application sends the JWT token to Infisical at the `/api/v1/auth/kubernetes-auth/login` endpoint after which Infisical forwards the JWT token to the Kubernetes API Server at the TokenReview API for verification and to obtain the service account information associated with the JWT token.
|
||||||
|
Infisical is able to authenticate and interact with the TokenReview API by using either the long lived JWT token set while configuring this authentication method or by using the incoming token itself. The JWT token mentioned in this context is referred as the token reviewer JWT token.
|
||||||
3. Infisical checks the service account properties against set criteria such **Allowed Service Account Names** and **Allowed Namespaces**.
|
3. Infisical checks the service account properties against set criteria such **Allowed Service Account Names** and **Allowed Namespaces**.
|
||||||
4. If all is well, Infisical returns a short-lived access token that the application can use to make authenticated requests to the Infisical API.
|
4. If all is well, Infisical returns a short-lived access token that the application can use to make authenticated requests to the Infisical API.
|
||||||
|
|
||||||
@ -53,6 +54,12 @@ In the following steps, we explore how to create and use identities for your app
|
|||||||
|
|
||||||
<Steps>
|
<Steps>
|
||||||
<Step title="Obtaining the token reviewer JWT for Infisical">
|
<Step title="Obtaining the token reviewer JWT for Infisical">
|
||||||
|
<Tabs>
|
||||||
|
<Tab title="Option 1: Reviewer JWT Token">
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
**When to use this option**: Choose this approach when you want centralized authentication management. Only one service account needs special permissions, and your application service accounts remain unchanged.
|
||||||
|
</Note>
|
||||||
1.1. Start by creating a service account in your Kubernetes cluster that will be used by Infisical to authenticate with the Kubernetes API Server.
|
1.1. Start by creating a service account in your Kubernetes cluster that will be used by Infisical to authenticate with the Kubernetes API Server.
|
||||||
|
|
||||||
```yaml infisical-service-account.yaml
|
```yaml infisical-service-account.yaml
|
||||||
@ -61,7 +68,6 @@ In the following steps, we explore how to create and use identities for your app
|
|||||||
metadata:
|
metadata:
|
||||||
name: infisical-auth
|
name: infisical-auth
|
||||||
namespace: default
|
namespace: default
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```
|
```
|
||||||
@ -121,7 +127,40 @@ In the following steps, we explore how to create and use identities for your app
|
|||||||
|
|
||||||
Keep this JWT token handy as you will need it for the **Token Reviewer JWT** field when configuring the Kubernetes Auth authentication method for the identity in step 2.
|
Keep this JWT token handy as you will need it for the **Token Reviewer JWT** field when configuring the Kubernetes Auth authentication method for the identity in step 2.
|
||||||
|
|
||||||
</Step>
|
</Tab>
|
||||||
|
<Tab title="Option 2: Client JWT as Reviewer JWT Token">
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
**When to use this option**: Choose this approach to eliminate long-lived tokens. This option simplifies Infisical configuration but requires each application service account to have elevated permissions.
|
||||||
|
</Note>
|
||||||
|
|
||||||
|
The self-validation method eliminates the need for a separate long-lived reviewer JWT by using the same token for both authentication and validation. Instead of creating a dedicated reviewer service account, you'll grant the necessary permissions to each application service account.
|
||||||
|
|
||||||
|
For each service account that needs to authenticate with Infisical, add the `system:auth-delegator` role:
|
||||||
|
|
||||||
|
```yaml client-role-binding.yaml
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: infisical-client-binding-[your-app-name]
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: system:auth-delegator
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: [your-app-service-account]
|
||||||
|
namespace: [your-app-namespace]
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
kubectl apply -f client-role-binding.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
When configuring Kubernetes Auth in Infisical, leave the **Token Reviewer JWT** field empty. Infisical will use the client's own token for validation.
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
|
</Step>
|
||||||
|
|
||||||
<Step title="Creating an identity">
|
<Step title="Creating an identity">
|
||||||
To create an identity, head to your Organization Settings > Access Control > Machine Identities and press **Create identity**.
|
To create an identity, head to your Organization Settings > Access Control > Machine Identities and press **Create identity**.
|
||||||
@ -151,7 +190,8 @@ In the following steps, we explore how to create and use identities for your app
|
|||||||
Here's some more guidance on each field:
|
Here's some more guidance on each field:
|
||||||
|
|
||||||
- Kubernetes Host / Base Kubernetes API URL: The host string, host:port pair, or URL to the base of the Kubernetes API server. This can usually be obtained by running `kubectl cluster-info`.
|
- Kubernetes Host / Base Kubernetes API URL: The host string, host:port pair, or URL to the base of the Kubernetes API server. This can usually be obtained by running `kubectl cluster-info`.
|
||||||
- Token Reviewer JWT: A long-lived service account JWT token for Infisical to access the [TokenReview API](https://kubernetes.io/docs/reference/kubernetes-api/authentication-resources/token-review-v1/) to validate other service account JWT tokens submitted by applications/pods. This is the JWT token obtained from step 1.5.
|
- Token Reviewer JWT: A long-lived service account JWT token for Infisical to access the [TokenReview API](https://kubernetes.io/docs/reference/kubernetes-api/authentication-resources/token-review-v1/) to validate other service account JWT tokens submitted by applications/pods. This is the JWT token obtained from step 1.5(Reviewer Tab). If omitted, the client's own JWT will be used instead, which requires the client to have the `system:auth-delegator` ClusterRole binding.
|
||||||
|
This is shown in step 1, option 2.
|
||||||
- Allowed Service Account Names: A comma-separated list of trusted service account names that are allowed to authenticate with Infisical.
|
- Allowed Service Account Names: A comma-separated list of trusted service account names that are allowed to authenticate with Infisical.
|
||||||
- Allowed Namespaces: A comma-separated list of trusted namespaces that service accounts must belong to authenticate with Infisical.
|
- Allowed Namespaces: A comma-separated list of trusted namespaces that service accounts must belong to authenticate with Infisical.
|
||||||
- Allowed Audience: An optional audience claim that the service account JWT token must have to authenticate with Infisical.
|
- Allowed Audience: An optional audience claim that the service account JWT token must have to authenticate with Infisical.
|
||||||
@ -182,6 +222,7 @@ In the following steps, we explore how to create and use identities for your app
|
|||||||
For information on how to configure sevice accounts for pods, refer to the guide [here](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/).
|
For information on how to configure sevice accounts for pods, refer to the guide [here](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/).
|
||||||
|
|
||||||
We provide a code example below of how you might retrieve the JWT token and use it to authenticate with Infisical to gain access to the [Infisical API](/api-reference/overview/introduction).
|
We provide a code example below of how you might retrieve the JWT token and use it to authenticate with Infisical to gain access to the [Infisical API](/api-reference/overview/introduction).
|
||||||
|
|
||||||
<Accordion
|
<Accordion
|
||||||
title="Sample code for inside an application"
|
title="Sample code for inside an application"
|
||||||
>
|
>
|
||||||
@ -238,14 +279,15 @@ In the following steps, we explore how to create and use identities for your app
|
|||||||
<Accordion title="Why is the Infisical API rejecting my access token?">
|
<Accordion title="Why is the Infisical API rejecting my access token?">
|
||||||
There are a few reasons for why this might happen:
|
There are a few reasons for why this might happen:
|
||||||
|
|
||||||
- The access token has expired.
|
- The access token has expired.
|
||||||
- The identity is insufficently permissioned to interact with the resources you wish to access.
|
- The identity is insufficently permissioned to interact with the resources you wish to access.
|
||||||
- The client access token is being used from an untrusted IP.
|
- The client access token is being used from an untrusted IP.
|
||||||
|
|
||||||
</Accordion>
|
</Accordion>
|
||||||
<Accordion title="What is access token renewal and TTL/Max TTL?">
|
<Accordion title="What is access token renewal and TTL/Max TTL?">
|
||||||
A identity access token can have a time-to-live (TTL) or incremental lifetime after which it expires.
|
A identity access token can have a time-to-live (TTL) or incremental lifetime after which it expires.
|
||||||
|
|
||||||
In certain cases, you may want to extend the lifespan of an access token; to do so, you must set a max TTL parameter.
|
In certain cases, you may want to extend the lifespan of an access token; to do so, you must set a max TTL parameter.
|
||||||
|
|
||||||
A token can be renewed any number of times where each call to renew it can extend the token's lifetime by increments of the access token's TTL.
|
A token can be renewed any number of times where each call to renew it can extend the token's lifetime by increments of the access token's TTL.
|
||||||
Regardless of how frequently an access token is renewed, its lifespan remains bound to the maximum TTL determined at its creation.
|
Regardless of how frequently an access token is renewed, its lifespan remains bound to the maximum TTL determined at its creation.
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 1.6 MiB After Width: | Height: | Size: 430 KiB |
@ -6,7 +6,7 @@ description: "Learn how to fetch secrets from Infisical with Terraform using bot
|
|||||||
This guide demonstrates how to use Infisical to manage secrets in your Terraform infrastructure code, supporting both traditional data sources and ephemeral resources for enhanced security. It uses:
|
This guide demonstrates how to use Infisical to manage secrets in your Terraform infrastructure code, supporting both traditional data sources and ephemeral resources for enhanced security. It uses:
|
||||||
|
|
||||||
- Infisical (you can use [Infisical Cloud](https://app.infisical.com) or a [self-hosted instance of Infisical](https://infisical.com/docs/self-hosting/overview)) to store your secrets
|
- Infisical (you can use [Infisical Cloud](https://app.infisical.com) or a [self-hosted instance of Infisical](https://infisical.com/docs/self-hosting/overview)) to store your secrets
|
||||||
- The [Terraform Provider](https://registry.terraform.io/providers/Infisical/infisical/latest) to fetch secrets for your infrastructure
|
- The [Terraform Provider](https://registry.terraform.io/providers/Infisical/infisical/latest/docs) to fetch secrets for your infrastructure
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
|
5
docs/integrations/platforms/apache-airflow.mdx
Normal file
5
docs/integrations/platforms/apache-airflow.mdx
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "Apache Airflow"
|
||||||
|
description: "Learn how to use Infisical as your custom secrets backend in Apache Airflow."
|
||||||
|
url: "https://github.com/Infisical/airflow-provider-infisical?tab=readme-ov-file#airflow-infisical-provider"
|
||||||
|
---
|
@ -402,7 +402,8 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"integrations/frameworks/terraform",
|
"integrations/frameworks/terraform",
|
||||||
"integrations/platforms/ansible"
|
"integrations/platforms/ansible",
|
||||||
|
"integrations/platforms/apache-airflow"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
351
frontend/package-lock.json
generated
351
frontend/package-lock.json
generated
@ -362,26 +362,26 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/helpers": {
|
"node_modules/@babel/helpers": {
|
||||||
"version": "7.26.0",
|
"version": "7.26.10",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.10.tgz",
|
||||||
"integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==",
|
"integrity": "sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/template": "^7.25.9",
|
"@babel/template": "^7.26.9",
|
||||||
"@babel/types": "^7.26.0"
|
"@babel/types": "^7.26.10"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/parser": {
|
"node_modules/@babel/parser": {
|
||||||
"version": "7.26.3",
|
"version": "7.26.10",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.3.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.10.tgz",
|
||||||
"integrity": "sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==",
|
"integrity": "sha512-6aQR2zGE/QFi8JpDLjUZEPYOs7+mhKXm86VaKFiLP35JQwQb6bwUE+XbvkH0EptsYhbNBSUGaUBLKqxH1xSgsA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/types": "^7.26.3"
|
"@babel/types": "^7.26.10"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"parser": "bin/babel-parser.js"
|
"parser": "bin/babel-parser.js"
|
||||||
@ -423,9 +423,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/runtime": {
|
"node_modules/@babel/runtime": {
|
||||||
"version": "7.26.0",
|
"version": "7.26.10",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.10.tgz",
|
||||||
"integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==",
|
"integrity": "sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"regenerator-runtime": "^0.14.0"
|
"regenerator-runtime": "^0.14.0"
|
||||||
@ -435,14 +435,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/template": {
|
"node_modules/@babel/template": {
|
||||||
"version": "7.25.9",
|
"version": "7.26.9",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz",
|
||||||
"integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==",
|
"integrity": "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.25.9",
|
"@babel/code-frame": "^7.26.2",
|
||||||
"@babel/parser": "^7.25.9",
|
"@babel/parser": "^7.26.9",
|
||||||
"@babel/types": "^7.25.9"
|
"@babel/types": "^7.26.9"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
@ -476,9 +476,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/types": {
|
"node_modules/@babel/types": {
|
||||||
"version": "7.26.3",
|
"version": "7.26.10",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.3.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.10.tgz",
|
||||||
"integrity": "sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==",
|
"integrity": "sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/helper-string-parser": "^7.25.9",
|
"@babel/helper-string-parser": "^7.25.9",
|
||||||
@ -1010,6 +1010,23 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@esbuild/netbsd-arm64": {
|
||||||
|
"version": "0.25.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.1.tgz",
|
||||||
|
"integrity": "sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"netbsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@esbuild/netbsd-x64": {
|
"node_modules/@esbuild/netbsd-x64": {
|
||||||
"version": "0.21.5",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
|
||||||
@ -1028,9 +1045,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/openbsd-arm64": {
|
"node_modules/@esbuild/openbsd-arm64": {
|
||||||
"version": "0.23.1",
|
"version": "0.25.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.1.tgz",
|
||||||
"integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==",
|
"integrity": "sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@ -1647,12 +1664,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@octokit/endpoint": {
|
"node_modules/@octokit/endpoint": {
|
||||||
"version": "10.1.1",
|
"version": "10.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.3.tgz",
|
||||||
"integrity": "sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==",
|
"integrity": "sha512-nBRBMpKPhQUxCsQQeW+rCJ/OPSMcj3g0nfHn01zGYZXuNDvvXudF/TYY6APj5THlurerpFN4a/dQAIAaM6BYhA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/types": "^13.0.0",
|
"@octokit/types": "^13.6.2",
|
||||||
"universal-user-agent": "^7.0.2"
|
"universal-user-agent": "^7.0.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -1674,18 +1691,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@octokit/openapi-types": {
|
"node_modules/@octokit/openapi-types": {
|
||||||
"version": "22.2.0",
|
"version": "24.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz",
|
||||||
"integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==",
|
"integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@octokit/plugin-paginate-rest": {
|
"node_modules/@octokit/plugin-paginate-rest": {
|
||||||
"version": "11.3.6",
|
"version": "11.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.3.6.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.6.0.tgz",
|
||||||
"integrity": "sha512-zcvqqf/+TicbTCa/Z+3w4eBJcAxCFymtc0UAIsR3dEVoNilWld4oXdscQ3laXamTszUZdusw97K8+DrbFiOwjw==",
|
"integrity": "sha512-n5KPteiF7pWKgBIBJSk8qzoZWcUkza2O6A0za97pMGVrGfPdltxrfmfF5GucHYvHGZD8BdaZmmHGz5cX/3gdpw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/types": "^13.6.2"
|
"@octokit/types": "^13.10.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 18"
|
"node": ">= 18"
|
||||||
@ -1722,14 +1739,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@octokit/request": {
|
"node_modules/@octokit/request": {
|
||||||
"version": "9.1.3",
|
"version": "9.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.2.2.tgz",
|
||||||
"integrity": "sha512-V+TFhu5fdF3K58rs1pGUJIDH5RZLbZm5BI+MNF+6o/ssFNT4vWlCh/tVpF3NxGtP15HUxTTMUbsG5llAuU2CZA==",
|
"integrity": "sha512-dZl0ZHx6gOQGcffgm1/Sf6JfEpmh34v3Af2Uci02vzUYz6qEN6zepoRtmybWXIGXFIK8K9ylE3b+duCWqhArtg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/endpoint": "^10.0.0",
|
"@octokit/endpoint": "^10.1.3",
|
||||||
"@octokit/request-error": "^6.0.1",
|
"@octokit/request-error": "^6.1.7",
|
||||||
"@octokit/types": "^13.1.0",
|
"@octokit/types": "^13.6.2",
|
||||||
|
"fast-content-type-parse": "^2.0.0",
|
||||||
"universal-user-agent": "^7.0.2"
|
"universal-user-agent": "^7.0.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -1737,12 +1755,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@octokit/request-error": {
|
"node_modules/@octokit/request-error": {
|
||||||
"version": "6.1.5",
|
"version": "6.1.7",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.7.tgz",
|
||||||
"integrity": "sha512-IlBTfGX8Yn/oFPMwSfvugfncK2EwRLjzbrpifNaMY8o/HTEAFqCA1FZxjD9cWvSKBHgrIhc4CSBIzMxiLsbzFQ==",
|
"integrity": "sha512-69NIppAwaauwZv6aOzb+VVLwt+0havz9GT5YplkeJv7fG7a40qpLt/yZKyiDxAhgz0EtgNdNcb96Z0u+Zyuy2g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/types": "^13.0.0"
|
"@octokit/types": "^13.6.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 18"
|
"node": ">= 18"
|
||||||
@ -1764,12 +1782,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@octokit/types": {
|
"node_modules/@octokit/types": {
|
||||||
"version": "13.6.2",
|
"version": "13.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz",
|
||||||
"integrity": "sha512-WpbZfZUcZU77DrSW4wbsSgTPfKcp286q3ItaIgvSbBpZJlu6mnYXAkjZz6LVZPXkEvLIM8McanyZejKTYUHipA==",
|
"integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/openapi-types": "^22.2.0"
|
"@octokit/openapi-types": "^24.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@peculiar/asn1-cms": {
|
"node_modules/@peculiar/asn1-cms": {
|
||||||
@ -4956,9 +4974,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/axios": {
|
"node_modules/axios": {
|
||||||
"version": "1.7.9",
|
"version": "1.8.3",
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz",
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.3.tgz",
|
||||||
"integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==",
|
"integrity": "sha512-iP4DebzoNlP/YN2dpwCgb8zoCmhtkajzS48JvwmkSkXvPI3DHc7m+XYL5tGnSlJtR6nImXZmdCuN5aP8dh1d8A==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"follow-redirects": "^1.15.6",
|
"follow-redirects": "^1.15.6",
|
||||||
@ -5451,9 +5469,9 @@
|
|||||||
"license": "CC-BY-4.0"
|
"license": "CC-BY-4.0"
|
||||||
},
|
},
|
||||||
"node_modules/canvg": {
|
"node_modules/canvg": {
|
||||||
"version": "3.0.10",
|
"version": "3.0.11",
|
||||||
"resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.10.tgz",
|
"resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.11.tgz",
|
||||||
"integrity": "sha512-qwR2FRNO9NlzTeKIPIKpnTY6fqwuYSequ8Ru8c0YkYU7U0oW+hLUvWadLvAu1Rl72OMNiFhoLu4f8eUjQ7l/+Q==",
|
"integrity": "sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -7314,6 +7332,22 @@
|
|||||||
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
|
||||||
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
|
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
|
||||||
},
|
},
|
||||||
|
"node_modules/fast-content-type-parse": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/fastify"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/fastify"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/fast-deep-equal": {
|
"node_modules/fast-deep-equal": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||||
@ -12588,13 +12622,13 @@
|
|||||||
"license": "0BSD"
|
"license": "0BSD"
|
||||||
},
|
},
|
||||||
"node_modules/tsx": {
|
"node_modules/tsx": {
|
||||||
"version": "4.19.2",
|
"version": "4.19.3",
|
||||||
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.2.tgz",
|
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.3.tgz",
|
||||||
"integrity": "sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g==",
|
"integrity": "sha512-4H8vUNGNjQ4V2EOoGw005+c+dGuPSnhpPBPHBtsZdGZBk/iJb4kguGlPWaZTZ3q5nMtFOEsY0nRDlh9PJyd6SQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "~0.23.0",
|
"esbuild": "~0.25.0",
|
||||||
"get-tsconfig": "^4.7.5"
|
"get-tsconfig": "^4.7.5"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
@ -12608,9 +12642,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tsx/node_modules/@esbuild/aix-ppc64": {
|
"node_modules/tsx/node_modules/@esbuild/aix-ppc64": {
|
||||||
"version": "0.23.1",
|
"version": "0.25.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.1.tgz",
|
||||||
"integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==",
|
"integrity": "sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
@ -12625,9 +12659,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tsx/node_modules/@esbuild/android-arm": {
|
"node_modules/tsx/node_modules/@esbuild/android-arm": {
|
||||||
"version": "0.23.1",
|
"version": "0.25.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.1.tgz",
|
||||||
"integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==",
|
"integrity": "sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@ -12642,9 +12676,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tsx/node_modules/@esbuild/android-arm64": {
|
"node_modules/tsx/node_modules/@esbuild/android-arm64": {
|
||||||
"version": "0.23.1",
|
"version": "0.25.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.1.tgz",
|
||||||
"integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==",
|
"integrity": "sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@ -12659,9 +12693,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tsx/node_modules/@esbuild/android-x64": {
|
"node_modules/tsx/node_modules/@esbuild/android-x64": {
|
||||||
"version": "0.23.1",
|
"version": "0.25.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.1.tgz",
|
||||||
"integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==",
|
"integrity": "sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -12676,9 +12710,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tsx/node_modules/@esbuild/darwin-arm64": {
|
"node_modules/tsx/node_modules/@esbuild/darwin-arm64": {
|
||||||
"version": "0.23.1",
|
"version": "0.25.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.1.tgz",
|
||||||
"integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==",
|
"integrity": "sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@ -12693,9 +12727,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tsx/node_modules/@esbuild/darwin-x64": {
|
"node_modules/tsx/node_modules/@esbuild/darwin-x64": {
|
||||||
"version": "0.23.1",
|
"version": "0.25.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.1.tgz",
|
||||||
"integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==",
|
"integrity": "sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -12710,9 +12744,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tsx/node_modules/@esbuild/freebsd-arm64": {
|
"node_modules/tsx/node_modules/@esbuild/freebsd-arm64": {
|
||||||
"version": "0.23.1",
|
"version": "0.25.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.1.tgz",
|
||||||
"integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==",
|
"integrity": "sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@ -12727,9 +12761,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tsx/node_modules/@esbuild/freebsd-x64": {
|
"node_modules/tsx/node_modules/@esbuild/freebsd-x64": {
|
||||||
"version": "0.23.1",
|
"version": "0.25.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.1.tgz",
|
||||||
"integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==",
|
"integrity": "sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -12744,9 +12778,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tsx/node_modules/@esbuild/linux-arm": {
|
"node_modules/tsx/node_modules/@esbuild/linux-arm": {
|
||||||
"version": "0.23.1",
|
"version": "0.25.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.1.tgz",
|
||||||
"integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==",
|
"integrity": "sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@ -12761,9 +12795,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tsx/node_modules/@esbuild/linux-arm64": {
|
"node_modules/tsx/node_modules/@esbuild/linux-arm64": {
|
||||||
"version": "0.23.1",
|
"version": "0.25.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.1.tgz",
|
||||||
"integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==",
|
"integrity": "sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@ -12778,9 +12812,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tsx/node_modules/@esbuild/linux-ia32": {
|
"node_modules/tsx/node_modules/@esbuild/linux-ia32": {
|
||||||
"version": "0.23.1",
|
"version": "0.25.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.1.tgz",
|
||||||
"integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==",
|
"integrity": "sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
@ -12795,9 +12829,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tsx/node_modules/@esbuild/linux-loong64": {
|
"node_modules/tsx/node_modules/@esbuild/linux-loong64": {
|
||||||
"version": "0.23.1",
|
"version": "0.25.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.1.tgz",
|
||||||
"integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==",
|
"integrity": "sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
@ -12812,9 +12846,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tsx/node_modules/@esbuild/linux-mips64el": {
|
"node_modules/tsx/node_modules/@esbuild/linux-mips64el": {
|
||||||
"version": "0.23.1",
|
"version": "0.25.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.1.tgz",
|
||||||
"integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==",
|
"integrity": "sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"mips64el"
|
"mips64el"
|
||||||
],
|
],
|
||||||
@ -12829,9 +12863,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tsx/node_modules/@esbuild/linux-ppc64": {
|
"node_modules/tsx/node_modules/@esbuild/linux-ppc64": {
|
||||||
"version": "0.23.1",
|
"version": "0.25.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.1.tgz",
|
||||||
"integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==",
|
"integrity": "sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
@ -12846,9 +12880,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tsx/node_modules/@esbuild/linux-riscv64": {
|
"node_modules/tsx/node_modules/@esbuild/linux-riscv64": {
|
||||||
"version": "0.23.1",
|
"version": "0.25.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.1.tgz",
|
||||||
"integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==",
|
"integrity": "sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
@ -12863,9 +12897,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tsx/node_modules/@esbuild/linux-s390x": {
|
"node_modules/tsx/node_modules/@esbuild/linux-s390x": {
|
||||||
"version": "0.23.1",
|
"version": "0.25.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.1.tgz",
|
||||||
"integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==",
|
"integrity": "sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
@ -12880,9 +12914,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tsx/node_modules/@esbuild/linux-x64": {
|
"node_modules/tsx/node_modules/@esbuild/linux-x64": {
|
||||||
"version": "0.23.1",
|
"version": "0.25.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.1.tgz",
|
||||||
"integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==",
|
"integrity": "sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -12897,9 +12931,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tsx/node_modules/@esbuild/netbsd-x64": {
|
"node_modules/tsx/node_modules/@esbuild/netbsd-x64": {
|
||||||
"version": "0.23.1",
|
"version": "0.25.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.1.tgz",
|
||||||
"integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==",
|
"integrity": "sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -12914,9 +12948,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tsx/node_modules/@esbuild/openbsd-x64": {
|
"node_modules/tsx/node_modules/@esbuild/openbsd-x64": {
|
||||||
"version": "0.23.1",
|
"version": "0.25.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.1.tgz",
|
||||||
"integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==",
|
"integrity": "sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -12931,9 +12965,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tsx/node_modules/@esbuild/sunos-x64": {
|
"node_modules/tsx/node_modules/@esbuild/sunos-x64": {
|
||||||
"version": "0.23.1",
|
"version": "0.25.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.1.tgz",
|
||||||
"integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==",
|
"integrity": "sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -12948,9 +12982,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tsx/node_modules/@esbuild/win32-arm64": {
|
"node_modules/tsx/node_modules/@esbuild/win32-arm64": {
|
||||||
"version": "0.23.1",
|
"version": "0.25.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.1.tgz",
|
||||||
"integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==",
|
"integrity": "sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@ -12965,9 +12999,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tsx/node_modules/@esbuild/win32-ia32": {
|
"node_modules/tsx/node_modules/@esbuild/win32-ia32": {
|
||||||
"version": "0.23.1",
|
"version": "0.25.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.1.tgz",
|
||||||
"integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==",
|
"integrity": "sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
@ -12982,9 +13016,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tsx/node_modules/@esbuild/win32-x64": {
|
"node_modules/tsx/node_modules/@esbuild/win32-x64": {
|
||||||
"version": "0.23.1",
|
"version": "0.25.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.1.tgz",
|
||||||
"integrity": "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==",
|
"integrity": "sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -12999,9 +13033,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tsx/node_modules/esbuild": {
|
"node_modules/tsx/node_modules/esbuild": {
|
||||||
"version": "0.23.1",
|
"version": "0.25.1",
|
||||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.1.tgz",
|
||||||
"integrity": "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==",
|
"integrity": "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@ -13012,30 +13046,31 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@esbuild/aix-ppc64": "0.23.1",
|
"@esbuild/aix-ppc64": "0.25.1",
|
||||||
"@esbuild/android-arm": "0.23.1",
|
"@esbuild/android-arm": "0.25.1",
|
||||||
"@esbuild/android-arm64": "0.23.1",
|
"@esbuild/android-arm64": "0.25.1",
|
||||||
"@esbuild/android-x64": "0.23.1",
|
"@esbuild/android-x64": "0.25.1",
|
||||||
"@esbuild/darwin-arm64": "0.23.1",
|
"@esbuild/darwin-arm64": "0.25.1",
|
||||||
"@esbuild/darwin-x64": "0.23.1",
|
"@esbuild/darwin-x64": "0.25.1",
|
||||||
"@esbuild/freebsd-arm64": "0.23.1",
|
"@esbuild/freebsd-arm64": "0.25.1",
|
||||||
"@esbuild/freebsd-x64": "0.23.1",
|
"@esbuild/freebsd-x64": "0.25.1",
|
||||||
"@esbuild/linux-arm": "0.23.1",
|
"@esbuild/linux-arm": "0.25.1",
|
||||||
"@esbuild/linux-arm64": "0.23.1",
|
"@esbuild/linux-arm64": "0.25.1",
|
||||||
"@esbuild/linux-ia32": "0.23.1",
|
"@esbuild/linux-ia32": "0.25.1",
|
||||||
"@esbuild/linux-loong64": "0.23.1",
|
"@esbuild/linux-loong64": "0.25.1",
|
||||||
"@esbuild/linux-mips64el": "0.23.1",
|
"@esbuild/linux-mips64el": "0.25.1",
|
||||||
"@esbuild/linux-ppc64": "0.23.1",
|
"@esbuild/linux-ppc64": "0.25.1",
|
||||||
"@esbuild/linux-riscv64": "0.23.1",
|
"@esbuild/linux-riscv64": "0.25.1",
|
||||||
"@esbuild/linux-s390x": "0.23.1",
|
"@esbuild/linux-s390x": "0.25.1",
|
||||||
"@esbuild/linux-x64": "0.23.1",
|
"@esbuild/linux-x64": "0.25.1",
|
||||||
"@esbuild/netbsd-x64": "0.23.1",
|
"@esbuild/netbsd-arm64": "0.25.1",
|
||||||
"@esbuild/openbsd-arm64": "0.23.1",
|
"@esbuild/netbsd-x64": "0.25.1",
|
||||||
"@esbuild/openbsd-x64": "0.23.1",
|
"@esbuild/openbsd-arm64": "0.25.1",
|
||||||
"@esbuild/sunos-x64": "0.23.1",
|
"@esbuild/openbsd-x64": "0.25.1",
|
||||||
"@esbuild/win32-arm64": "0.23.1",
|
"@esbuild/sunos-x64": "0.25.1",
|
||||||
"@esbuild/win32-ia32": "0.23.1",
|
"@esbuild/win32-arm64": "0.25.1",
|
||||||
"@esbuild/win32-x64": "0.23.1"
|
"@esbuild/win32-ia32": "0.25.1",
|
||||||
|
"@esbuild/win32-x64": "0.25.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tsyringe": {
|
"node_modules/tsyringe": {
|
||||||
@ -13552,9 +13587,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "5.4.11",
|
"version": "5.4.14",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.14.tgz",
|
||||||
"integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==",
|
"integrity": "sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -143,6 +143,7 @@ export const useGetProjectSecretsOverview = (
|
|||||||
search = "",
|
search = "",
|
||||||
includeSecrets,
|
includeSecrets,
|
||||||
includeFolders,
|
includeFolders,
|
||||||
|
includeImports,
|
||||||
includeDynamicSecrets,
|
includeDynamicSecrets,
|
||||||
environments
|
environments
|
||||||
}: TGetDashboardProjectSecretsOverviewDTO,
|
}: TGetDashboardProjectSecretsOverviewDTO,
|
||||||
@ -170,6 +171,7 @@ export const useGetProjectSecretsOverview = (
|
|||||||
projectId,
|
projectId,
|
||||||
includeSecrets,
|
includeSecrets,
|
||||||
includeFolders,
|
includeFolders,
|
||||||
|
includeImports,
|
||||||
includeDynamicSecrets,
|
includeDynamicSecrets,
|
||||||
environments
|
environments
|
||||||
}),
|
}),
|
||||||
@ -184,6 +186,7 @@ export const useGetProjectSecretsOverview = (
|
|||||||
projectId,
|
projectId,
|
||||||
includeSecrets,
|
includeSecrets,
|
||||||
includeFolders,
|
includeFolders,
|
||||||
|
includeImports,
|
||||||
includeDynamicSecrets,
|
includeDynamicSecrets,
|
||||||
environments
|
environments
|
||||||
}),
|
}),
|
||||||
@ -197,12 +200,15 @@ export const useGetProjectSecretsOverview = (
|
|||||||
? unique(select.dynamicSecrets, (i) => i.name)
|
? unique(select.dynamicSecrets, (i) => i.name)
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
|
const uniqueSecretImports = select.imports ? unique(select.imports, (i) => i.id) : [];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...select,
|
...select,
|
||||||
secrets: secrets ? mergePersonalSecrets(secrets) : undefined,
|
secrets: secrets ? mergePersonalSecrets(secrets) : undefined,
|
||||||
totalUniqueSecretsInPage: uniqueSecrets.length,
|
totalUniqueSecretsInPage: uniqueSecrets.length,
|
||||||
totalUniqueDynamicSecretsInPage: uniqueDynamicSecrets.length,
|
totalUniqueDynamicSecretsInPage: uniqueDynamicSecrets.length,
|
||||||
totalUniqueFoldersInPage: uniqueFolders.length
|
totalUniqueFoldersInPage: uniqueFolders.length,
|
||||||
|
totalUniqueSecretImportsInPage: uniqueSecretImports.length
|
||||||
};
|
};
|
||||||
}, []),
|
}, []),
|
||||||
placeholderData: (previousData) => previousData
|
placeholderData: (previousData) => previousData
|
||||||
|
@ -9,13 +9,16 @@ export type DashboardProjectSecretsOverviewResponse = {
|
|||||||
folders?: (TSecretFolder & { environment: string })[];
|
folders?: (TSecretFolder & { environment: string })[];
|
||||||
dynamicSecrets?: (TDynamicSecret & { environment: string })[];
|
dynamicSecrets?: (TDynamicSecret & { environment: string })[];
|
||||||
secrets?: SecretV3Raw[];
|
secrets?: SecretV3Raw[];
|
||||||
|
imports?: TSecretImport[];
|
||||||
totalSecretCount?: number;
|
totalSecretCount?: number;
|
||||||
totalFolderCount?: number;
|
totalFolderCount?: number;
|
||||||
totalDynamicSecretCount?: number;
|
totalDynamicSecretCount?: number;
|
||||||
|
totalImportCount?: number;
|
||||||
totalCount: number;
|
totalCount: number;
|
||||||
totalUniqueSecretsInPage: number;
|
totalUniqueSecretsInPage: number;
|
||||||
totalUniqueDynamicSecretsInPage: number;
|
totalUniqueDynamicSecretsInPage: number;
|
||||||
totalUniqueFoldersInPage: number;
|
totalUniqueFoldersInPage: number;
|
||||||
|
totalUniqueSecretImportsInPage: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DashboardProjectSecretsDetailsResponse = {
|
export type DashboardProjectSecretsDetailsResponse = {
|
||||||
@ -63,6 +66,7 @@ export type TGetDashboardProjectSecretsOverviewDTO = {
|
|||||||
includeSecrets?: boolean;
|
includeSecrets?: boolean;
|
||||||
includeFolders?: boolean;
|
includeFolders?: boolean;
|
||||||
includeDynamicSecrets?: boolean;
|
includeDynamicSecrets?: boolean;
|
||||||
|
includeImports?: boolean;
|
||||||
environments: string[];
|
environments: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -350,7 +350,7 @@ export type AddIdentityKubernetesAuthDTO = {
|
|||||||
organizationId: string;
|
organizationId: string;
|
||||||
identityId: string;
|
identityId: string;
|
||||||
kubernetesHost: string;
|
kubernetesHost: string;
|
||||||
tokenReviewerJwt: string;
|
tokenReviewerJwt?: string;
|
||||||
allowedNamespaces: string;
|
allowedNamespaces: string;
|
||||||
allowedNames: string;
|
allowedNames: string;
|
||||||
allowedAudience: string;
|
allowedAudience: string;
|
||||||
@ -367,7 +367,7 @@ export type UpdateIdentityKubernetesAuthDTO = {
|
|||||||
organizationId: string;
|
organizationId: string;
|
||||||
identityId: string;
|
identityId: string;
|
||||||
kubernetesHost?: string;
|
kubernetesHost?: string;
|
||||||
tokenReviewerJwt?: string;
|
tokenReviewerJwt?: string | null;
|
||||||
allowedNamespaces?: string;
|
allowedNamespaces?: string;
|
||||||
allowedNames?: string;
|
allowedNames?: string;
|
||||||
allowedAudience?: string;
|
allowedAudience?: string;
|
||||||
|
@ -14,6 +14,7 @@ export type TSecretImport = {
|
|||||||
isReplicationSuccess?: boolean;
|
isReplicationSuccess?: boolean;
|
||||||
replicationStatus?: string;
|
replicationStatus?: string;
|
||||||
lastReplicated?: string;
|
lastReplicated?: string;
|
||||||
|
environment?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TGetImportedFoldersByEnvDTO = {
|
export type TGetImportedFoldersByEnvDTO = {
|
||||||
|
@ -22,14 +22,37 @@ import "./translation";
|
|||||||
// have a look at the Quick start guide
|
// have a look at the Quick start guide
|
||||||
// for passing in lng and translations on init/
|
// for passing in lng and translations on init/
|
||||||
|
|
||||||
// https://vite.dev/guide/build#load-error-handling
|
|
||||||
window.addEventListener("vite:preloadError", () => {
|
|
||||||
window.location.reload(); // for example, refresh the page
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create a new router instance
|
// Create a new router instance
|
||||||
NProgress.configure({ showSpinner: false });
|
NProgress.configure({ showSpinner: false });
|
||||||
|
|
||||||
|
window.addEventListener("vite:preloadError", async (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
// Get current count from session storage or initialize to 0
|
||||||
|
const reloadCount = parseInt(sessionStorage.getItem("vitePreloadErrorCount") || "0", 10);
|
||||||
|
|
||||||
|
// Check if we've already tried 3 times
|
||||||
|
if (reloadCount >= 2) {
|
||||||
|
console.warn("Vite preload has failed multiple times. Stopping automatic reload.");
|
||||||
|
// Optionally show a user-facing message here
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ("caches" in window) {
|
||||||
|
const keys = await caches.keys();
|
||||||
|
await Promise.all(keys.map((key) => caches.delete(key)));
|
||||||
|
}
|
||||||
|
} catch (cleanupError) {
|
||||||
|
console.error(cleanupError);
|
||||||
|
}
|
||||||
|
//
|
||||||
|
// Increment and save the counter
|
||||||
|
sessionStorage.setItem("vitePreloadErrorCount", (reloadCount + 1).toString());
|
||||||
|
|
||||||
|
console.log(`Reloading page (attempt ${reloadCount + 1} of 2)...`);
|
||||||
|
window.location.reload(); // for example, refresh the page
|
||||||
|
});
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
routeTree,
|
routeTree,
|
||||||
context: { serverConfig: null, queryClient },
|
context: { serverConfig: null, queryClient },
|
||||||
|
@ -18,14 +18,6 @@ export const RequestNewInvitePage = () => {
|
|||||||
<span className="rounded-md bg-primary-500/40 px-1 text-black">Note:</span> If it still
|
<span className="rounded-md bg-primary-500/40 px-1 text-black">Note:</span> If it still
|
||||||
doesn't work, please reach out to us at support@infisical.com
|
doesn't work, please reach out to us at support@infisical.com
|
||||||
</p>
|
</p>
|
||||||
<div className="">
|
|
||||||
<img
|
|
||||||
src="/images/invitation-expired.svg"
|
|
||||||
height={500}
|
|
||||||
width={800}
|
|
||||||
alt="invitation expired illustration"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -223,15 +223,10 @@ export const SignupInvitePage = () => {
|
|||||||
// Step 4 of the sign up process (download the emergency kit pdf)
|
// Step 4 of the sign up process (download the emergency kit pdf)
|
||||||
const stepConfirmEmail = (
|
const stepConfirmEmail = (
|
||||||
<div className="h-7/12 mx-1 mb-36 flex w-full max-w-xs flex-col items-center rounded-xl border border-mineshaft-600 bg-mineshaft-800 px-4 py-8 drop-shadow-xl md:mb-16 md:max-w-lg md:px-6">
|
<div className="h-7/12 mx-1 mb-36 flex w-full max-w-xs flex-col items-center rounded-xl border border-mineshaft-600 bg-mineshaft-800 px-4 py-8 drop-shadow-xl md:mb-16 md:max-w-lg md:px-6">
|
||||||
<p className="mb-6 flex justify-center text-center text-4xl font-semibold text-primary-100">
|
<p className="mb-2 flex justify-center text-center text-4xl font-semibold text-primary-100">
|
||||||
Confirm your email
|
Confirm your email
|
||||||
</p>
|
</p>
|
||||||
<img
|
<div className="mx-auto mb-2 mt-4 flex max-h-24 max-w-md flex-col items-center justify-center px-4 text-lg md:p-2">
|
||||||
src="/images/dragon-signupinvite.svg"
|
|
||||||
style={{ height: "262px", width: "410px" }}
|
|
||||||
alt="verify email"
|
|
||||||
/>
|
|
||||||
<div className="mx-auto mb-2 mt-10 flex max-h-24 max-w-md flex-col items-center justify-center px-4 text-lg md:p-2">
|
|
||||||
<Button
|
<Button
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
try {
|
try {
|
||||||
|
@ -31,7 +31,7 @@ import { IdentityFormTab } from "./types";
|
|||||||
const schema = z
|
const schema = z
|
||||||
.object({
|
.object({
|
||||||
kubernetesHost: z.string().min(1),
|
kubernetesHost: z.string().min(1),
|
||||||
tokenReviewerJwt: z.string().min(1),
|
tokenReviewerJwt: z.string().optional(),
|
||||||
allowedNames: z.string(),
|
allowedNames: z.string(),
|
||||||
allowedNamespaces: z.string(),
|
allowedNamespaces: z.string(),
|
||||||
allowedAudience: z.string(),
|
allowedAudience: z.string(),
|
||||||
@ -166,7 +166,7 @@ export const IdentityKubernetesAuthForm = ({
|
|||||||
await updateMutateAsync({
|
await updateMutateAsync({
|
||||||
organizationId: orgId,
|
organizationId: orgId,
|
||||||
kubernetesHost,
|
kubernetesHost,
|
||||||
tokenReviewerJwt,
|
tokenReviewerJwt: tokenReviewerJwt || null,
|
||||||
allowedNames,
|
allowedNames,
|
||||||
allowedNamespaces,
|
allowedNamespaces,
|
||||||
allowedAudience,
|
allowedAudience,
|
||||||
@ -182,7 +182,7 @@ export const IdentityKubernetesAuthForm = ({
|
|||||||
organizationId: orgId,
|
organizationId: orgId,
|
||||||
identityId,
|
identityId,
|
||||||
kubernetesHost: kubernetesHost || "",
|
kubernetesHost: kubernetesHost || "",
|
||||||
tokenReviewerJwt,
|
tokenReviewerJwt: tokenReviewerJwt || undefined,
|
||||||
allowedNames: allowedNames || "",
|
allowedNames: allowedNames || "",
|
||||||
allowedNamespaces: allowedNamespaces || "",
|
allowedNamespaces: allowedNamespaces || "",
|
||||||
allowedAudience: allowedAudience || "",
|
allowedAudience: allowedAudience || "",
|
||||||
@ -255,11 +255,11 @@ export const IdentityKubernetesAuthForm = ({
|
|||||||
name="tokenReviewerJwt"
|
name="tokenReviewerJwt"
|
||||||
render={({ field, fieldState: { error } }) => (
|
render={({ field, fieldState: { error } }) => (
|
||||||
<FormControl
|
<FormControl
|
||||||
|
tooltipClassName="max-w-md"
|
||||||
label="Token Reviewer JWT"
|
label="Token Reviewer JWT"
|
||||||
isError={Boolean(error)}
|
isError={Boolean(error)}
|
||||||
errorText={error?.message}
|
errorText={error?.message}
|
||||||
tooltipText="A long-lived service account JWT token for Infisical to access the TokenReview API to validate other service account JWT tokens submitted by applications/pods."
|
tooltipText="Optional JWT token for accessing Kubernetes TokenReview API. If provided, this long-lived token will be used to validate service account tokens during authentication. If omitted, the client's own JWT will be used instead, which requires the client to have the system:auth-delegator ClusterRole binding."
|
||||||
isRequired
|
|
||||||
>
|
>
|
||||||
<Input {...field} placeholder="" type="password" />
|
<Input {...field} placeholder="" type="password" />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
@ -18,7 +18,7 @@ export const AuditLogsPage = () => {
|
|||||||
title="Audit logs"
|
title="Audit logs"
|
||||||
description="Audit logs for security and compliance teams to monitor information access."
|
description="Audit logs for security and compliance teams to monitor information access."
|
||||||
/>
|
/>
|
||||||
<LogsSection filterClassName="static py-2" showFilters isOrgAuditLogs showActorColumn />
|
<LogsSection filterClassName="static py-2" showFilters />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -10,7 +10,7 @@ import { ActorType, EventType, UserAgentType } from "@app/hooks/api/auditLogs/en
|
|||||||
import { usePopUp } from "@app/hooks/usePopUp";
|
import { usePopUp } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
import { LogsFilter } from "./LogsFilter";
|
import { LogsFilter } from "./LogsFilter";
|
||||||
import { LogsTable, TAuditLogTableHeader } from "./LogsTable";
|
import { LogsTable } from "./LogsTable";
|
||||||
import { AuditLogFilterFormData, auditLogFilterFormSchema } from "./types";
|
import { AuditLogFilterFormData, auditLogFilterFormSchema } from "./types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@ -25,22 +25,11 @@ type Props = {
|
|||||||
|
|
||||||
showFilters?: boolean;
|
showFilters?: boolean;
|
||||||
filterClassName?: string;
|
filterClassName?: string;
|
||||||
isOrgAuditLogs?: boolean;
|
|
||||||
showActorColumn?: boolean;
|
|
||||||
remappedHeaders?: Partial<Record<TAuditLogTableHeader, string>>;
|
|
||||||
refetchInterval?: number;
|
refetchInterval?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LogsSection = withPermission(
|
export const LogsSection = withPermission(
|
||||||
({
|
({ presets, filterClassName, refetchInterval, showFilters }: Props) => {
|
||||||
presets,
|
|
||||||
filterClassName,
|
|
||||||
remappedHeaders,
|
|
||||||
isOrgAuditLogs,
|
|
||||||
showActorColumn,
|
|
||||||
refetchInterval,
|
|
||||||
showFilters
|
|
||||||
}: Props) => {
|
|
||||||
const { subscription } = useSubscription();
|
const { subscription } = useSubscription();
|
||||||
|
|
||||||
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["upgradePlan"] as const);
|
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["upgradePlan"] as const);
|
||||||
@ -90,9 +79,6 @@ export const LogsSection = withPermission(
|
|||||||
)}
|
)}
|
||||||
<LogsTable
|
<LogsTable
|
||||||
refetchInterval={refetchInterval}
|
refetchInterval={refetchInterval}
|
||||||
remappedHeaders={remappedHeaders}
|
|
||||||
isOrgAuditLogs={isOrgAuditLogs}
|
|
||||||
showActorColumn={!!showActorColumn}
|
|
||||||
filter={{
|
filter={{
|
||||||
secretPath: debouncedSecretPath || undefined,
|
secretPath: debouncedSecretPath || undefined,
|
||||||
eventMetadata: presets?.eventMetadata,
|
eventMetadata: presets?.eventMetadata,
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Fragment } from "react";
|
import { Fragment } from "react";
|
||||||
import { faFile } from "@fortawesome/free-solid-svg-icons";
|
import { faFile, faInfoCircle } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@ -11,6 +12,7 @@ import {
|
|||||||
Td,
|
Td,
|
||||||
Th,
|
Th,
|
||||||
THead,
|
THead,
|
||||||
|
Tooltip,
|
||||||
Tr
|
Tr
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { useGetAuditLogs } from "@app/hooks/api";
|
import { useGetAuditLogs } from "@app/hooks/api";
|
||||||
@ -19,32 +21,13 @@ import { TGetAuditLogsFilter } from "@app/hooks/api/auditLogs/types";
|
|||||||
import { LogsTableRow } from "./LogsTableRow";
|
import { LogsTableRow } from "./LogsTableRow";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isOrgAuditLogs?: boolean;
|
|
||||||
showActorColumn: boolean;
|
|
||||||
filter?: TGetAuditLogsFilter;
|
filter?: TGetAuditLogsFilter;
|
||||||
remappedHeaders?: Partial<Record<TAuditLogTableHeader, string>>;
|
|
||||||
refetchInterval?: number;
|
refetchInterval?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const AUDIT_LOG_LIMIT = 15;
|
const AUDIT_LOG_LIMIT = 15;
|
||||||
|
|
||||||
const TABLE_HEADERS = [
|
export const LogsTable = ({ filter, refetchInterval }: Props) => {
|
||||||
"Timestamp (MM/DD/YYYY)",
|
|
||||||
"Event",
|
|
||||||
"Project",
|
|
||||||
"Actor",
|
|
||||||
"Source",
|
|
||||||
"Metadata"
|
|
||||||
] as const;
|
|
||||||
export type TAuditLogTableHeader = (typeof TABLE_HEADERS)[number];
|
|
||||||
|
|
||||||
export const LogsTable = ({
|
|
||||||
showActorColumn,
|
|
||||||
isOrgAuditLogs,
|
|
||||||
filter,
|
|
||||||
remappedHeaders,
|
|
||||||
refetchInterval
|
|
||||||
}: Props) => {
|
|
||||||
// Determine the project ID for filtering
|
// Determine the project ID for filtering
|
||||||
const filterProjectId =
|
const filterProjectId =
|
||||||
// Use the projectId from the filter if it exists
|
// Use the projectId from the filter if it exists
|
||||||
@ -69,38 +52,37 @@ export const LogsTable = ({
|
|||||||
<Table>
|
<Table>
|
||||||
<THead>
|
<THead>
|
||||||
<Tr>
|
<Tr>
|
||||||
{TABLE_HEADERS.map((header, idx) => {
|
<Th className="w-24" />
|
||||||
if (
|
<Th className="w-64">
|
||||||
(header === "Project" && !isOrgAuditLogs) ||
|
Timestamp
|
||||||
(header === "Actor" && !showActorColumn)
|
<Tooltip
|
||||||
) {
|
className="normal-case"
|
||||||
return null;
|
content="Time displayed in your system's time zone."
|
||||||
}
|
sideOffset={10}
|
||||||
|
>
|
||||||
return (
|
<FontAwesomeIcon icon={faInfoCircle} className="ml-1" />
|
||||||
<Th key={`table-header-${idx + 1}`}>{remappedHeaders?.[header] || header}</Th>
|
</Tooltip>
|
||||||
);
|
</Th>
|
||||||
})}
|
<Th>Event</Th>
|
||||||
</Tr>
|
</Tr>
|
||||||
</THead>
|
</THead>
|
||||||
<TBody>
|
<TBody>
|
||||||
{!isPending &&
|
{!isPending &&
|
||||||
data?.pages?.map((group, i) => (
|
data?.pages?.map((group, i) => (
|
||||||
<Fragment key={`audit-log-fragment-${i + 1}`}>
|
<Fragment key={`audit-log-fragment-${i + 1}`}>
|
||||||
{group.map((auditLog) => (
|
{group.map((auditLog, index) => (
|
||||||
<LogsTableRow
|
<LogsTableRow
|
||||||
showActorColumn={showActorColumn}
|
rowNumber={index + i * AUDIT_LOG_LIMIT + 1}
|
||||||
isOrgAuditLogs={isOrgAuditLogs}
|
|
||||||
auditLog={auditLog}
|
auditLog={auditLog}
|
||||||
key={`audit-log-${auditLog.id}`}
|
key={`audit-log-${auditLog.id}`}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
))}
|
))}
|
||||||
{isPending && <TableSkeleton innerKey="logs-table" columns={5} key="logs" />}
|
{isPending && <TableSkeleton innerKey="logs-table" columns={3} key="logs-loading" />}
|
||||||
{isEmpty && (
|
{isEmpty && (
|
||||||
<Tr>
|
<Tr>
|
||||||
<Td colSpan={5}>
|
<Td colSpan={3}>
|
||||||
<EmptyState title="No audit logs on file" icon={faFile} />
|
<EmptyState title="No audit logs on file" icon={faFile} />
|
||||||
</Td>
|
</Td>
|
||||||
</Tr>
|
</Tr>
|
||||||
|
@ -1,128 +1,76 @@
|
|||||||
import { faQuestionCircle } from "@fortawesome/free-solid-svg-icons";
|
import { faCaretDown, faCaretRight } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { format } from "date-fns";
|
||||||
|
|
||||||
import { Td, Tooltip, Tr } from "@app/components/v2";
|
import { Td, Tr } from "@app/components/v2";
|
||||||
import { eventToNameMap, userAgentTTypeoNameMap } from "@app/hooks/api/auditLogs/constants";
|
import { useToggle } from "@app/hooks";
|
||||||
import { ActorType, EventType } from "@app/hooks/api/auditLogs/enums";
|
import { ActorType } from "@app/hooks/api/auditLogs/enums";
|
||||||
import { Actor, AuditLog } from "@app/hooks/api/auditLogs/types";
|
import { AuditLog } from "@app/hooks/api/auditLogs/types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
auditLog: AuditLog;
|
auditLog: AuditLog;
|
||||||
isOrgAuditLogs?: boolean;
|
rowNumber: number;
|
||||||
showActorColumn: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LogsTableRow = ({ auditLog, isOrgAuditLogs, showActorColumn }: Props) => {
|
type TagProps = {
|
||||||
const renderActor = (actor: Actor) => {
|
label: string;
|
||||||
if (!actor) {
|
value?: string;
|
||||||
return <Td />;
|
};
|
||||||
}
|
const Tag = ({ label, value }: TagProps) => {
|
||||||
|
if (!value) return null;
|
||||||
|
|
||||||
switch (actor.type) {
|
|
||||||
case ActorType.USER:
|
|
||||||
return (
|
return (
|
||||||
<Td>
|
<div className="flex items-center space-x-1.5">
|
||||||
<p>{actor.metadata.email}</p>
|
<div className="rounded bg-mineshaft-600 p-0.5 pl-1 font-mono">{label}:</div>
|
||||||
<p>User</p>
|
<div>{value}</div>
|
||||||
</Td>
|
</div>
|
||||||
);
|
);
|
||||||
case ActorType.SERVICE:
|
};
|
||||||
|
|
||||||
|
export const LogsTableRow = ({ auditLog, rowNumber }: Props) => {
|
||||||
|
const [isOpen, setIsOpen] = useToggle();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Td>
|
<>
|
||||||
<p>{`${actor.metadata.name}`}</p>
|
<Tr
|
||||||
<p>Service token</p>
|
className="h-10 cursor-pointer border-x-0 border-b border-t-0 hover:bg-mineshaft-700"
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
onClick={() => setIsOpen.toggle()}
|
||||||
|
onKeyDown={(evt) => {
|
||||||
|
if (evt.key === "Enter") setIsOpen.toggle();
|
||||||
|
}}
|
||||||
|
isHoverable
|
||||||
|
>
|
||||||
|
<Td className="flex items-center gap-2 pr-0 align-top">
|
||||||
|
<FontAwesomeIcon icon={isOpen ? faCaretDown : faCaretRight} />
|
||||||
|
{rowNumber}
|
||||||
</Td>
|
</Td>
|
||||||
);
|
<Td className="align-top">
|
||||||
case ActorType.IDENTITY:
|
{format(new Date(auditLog.createdAt), "MMM do yyyy, hh:mm a")}
|
||||||
return (
|
|
||||||
<Td>
|
|
||||||
<p>{`${actor.metadata.name}`}</p>
|
|
||||||
<p>Machine Identity</p>
|
|
||||||
</Td>
|
</Td>
|
||||||
);
|
|
||||||
case ActorType.PLATFORM:
|
|
||||||
return (
|
|
||||||
<Td>
|
<Td>
|
||||||
<p>Platform</p>
|
<div className="flex flex-wrap gap-4 text-sm">
|
||||||
</Td>
|
<Tag label="event" value={auditLog.event.type} />
|
||||||
);
|
<Tag label="actor" value={auditLog.actor.type} />
|
||||||
case ActorType.KMIP_CLIENT:
|
{auditLog.actor.type === ActorType.USER && (
|
||||||
return (
|
<Tag label="user_email" value={auditLog.actor.metadata.email} />
|
||||||
<Td>
|
)}
|
||||||
<p>{actor.metadata.name}</p>
|
{auditLog.actor.type === ActorType.IDENTITY && (
|
||||||
<p>KMIP Client</p>
|
<Tag label="identity_name" value={auditLog.actor.metadata.name} />
|
||||||
</Td>
|
)}
|
||||||
);
|
|
||||||
case ActorType.UNKNOWN_USER:
|
|
||||||
return (
|
|
||||||
<Td>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<p>Unknown User</p>
|
|
||||||
<Tooltip content="This action was performed by a user who was not authenticated at the time.">
|
|
||||||
<FontAwesomeIcon className="text-mineshaft-400" icon={faQuestionCircle} />
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
</div>
|
||||||
</Td>
|
</Td>
|
||||||
);
|
|
||||||
default:
|
|
||||||
return <Td />;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const formatDate = (dateToFormat: string) => {
|
|
||||||
const date = new Date(dateToFormat);
|
|
||||||
const year = date.getFullYear();
|
|
||||||
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
||||||
const day = String(date.getDate()).padStart(2, "0");
|
|
||||||
|
|
||||||
let hours = date.getHours();
|
|
||||||
const minutes = String(date.getMinutes()).padStart(2, "0");
|
|
||||||
|
|
||||||
// convert from 24h to 12h format
|
|
||||||
const period = hours >= 12 ? "PM" : "AM";
|
|
||||||
hours %= 12;
|
|
||||||
hours = hours || 12; // the hour '0' should be '12'
|
|
||||||
|
|
||||||
const formattedDate = `${month}-${day}-${year} at ${hours}:${minutes} ${period}`;
|
|
||||||
return formattedDate;
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderSource = () => {
|
|
||||||
const { event, actor } = auditLog;
|
|
||||||
|
|
||||||
if (event.type === EventType.INTEGRATION_SYNCED) {
|
|
||||||
if (actor.type === ActorType.USER) {
|
|
||||||
return (
|
|
||||||
<Td>
|
|
||||||
<p>Manually triggered by {actor.metadata.email}</p>
|
|
||||||
</Td>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Platform / automatic syncs
|
|
||||||
return (
|
|
||||||
<Td>
|
|
||||||
<p>Automatically synced by Infisical</p>
|
|
||||||
</Td>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Td>
|
|
||||||
<p>{userAgentTTypeoNameMap[auditLog.userAgentType]}</p>
|
|
||||||
<p>{auditLog.ipAddress}</p>
|
|
||||||
</Td>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Tr className={`log-${auditLog.id} h-10 border-x-0 border-b border-t-0`}>
|
|
||||||
<Td>{formatDate(auditLog.createdAt)}</Td>
|
|
||||||
<Td>{`${eventToNameMap[auditLog.event.type]}`}</Td>
|
|
||||||
{isOrgAuditLogs && <Td>{auditLog?.projectName ?? auditLog?.projectId ?? "N/A"}</Td>}
|
|
||||||
{showActorColumn && renderActor(auditLog.actor)}
|
|
||||||
{renderSource()}
|
|
||||||
<Td className="max-w-xs break-all">{JSON.stringify(auditLog.event.metadata || {})}</Td>
|
|
||||||
</Tr>
|
</Tr>
|
||||||
|
{isOpen && (
|
||||||
|
<Tr className={`log-${auditLog.id} h-10 border-x-0 border-b border-t-0`}>
|
||||||
|
<Td colSpan={3} className="px-3 py-2">
|
||||||
|
<div className="thin-scrollbar my-1 max-h-96 overflow-auto whitespace-pre-wrap rounded border border-mineshaft-600 bg-bunker-800 p-2 font-mono leading-6">
|
||||||
|
{JSON.stringify(auditLog, null, 4)}
|
||||||
|
</div>
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -70,11 +70,14 @@ export const ViewIdentityKubernetesAuthContent = ({
|
|||||||
{data.kubernetesHost}
|
{data.kubernetesHost}
|
||||||
</IdentityAuthFieldDisplay>
|
</IdentityAuthFieldDisplay>
|
||||||
<IdentityAuthFieldDisplay className="col-span-2" label="Token Reviewer JWT">
|
<IdentityAuthFieldDisplay className="col-span-2" label="Token Reviewer JWT">
|
||||||
|
{data.tokenReviewerJwt ? (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
side="right"
|
side="right"
|
||||||
className="max-w-xl p-2"
|
className="max-w-xl p-2"
|
||||||
content={
|
content={
|
||||||
<p className="break-words rounded bg-mineshaft-600 p-2">{data.tokenReviewerJwt}</p>
|
<p className="break-words rounded bg-mineshaft-600 p-2">
|
||||||
|
{data.tokenReviewerJwt || "Not provided"}
|
||||||
|
</p>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className="w-min">
|
<div className="w-min">
|
||||||
@ -84,6 +87,9 @@ export const ViewIdentityKubernetesAuthContent = ({
|
|||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
) : (
|
||||||
|
<p className="text-base italic leading-4 text-bunker-400">Not set</p>
|
||||||
|
)}
|
||||||
</IdentityAuthFieldDisplay>
|
</IdentityAuthFieldDisplay>
|
||||||
<IdentityAuthFieldDisplay className="col-span-2" label="Allowed Service Account Names">
|
<IdentityAuthFieldDisplay className="col-span-2" label="Allowed Service Account Names">
|
||||||
{data.allowedNames
|
{data.allowedNames
|
||||||
|
@ -45,7 +45,6 @@ export const UserAuditLogsSection = withPermission(
|
|||||||
presets={{
|
presets={{
|
||||||
actorId: orgMembership.user.id
|
actorId: orgMembership.user.id
|
||||||
}}
|
}}
|
||||||
isOrgAuditLogs
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -29,9 +29,6 @@ export const IntegrationAuditLogsSection = ({ integration }: Props) => {
|
|||||||
</div>
|
</div>
|
||||||
<LogsSection
|
<LogsSection
|
||||||
refetchInterval={4000}
|
refetchInterval={4000}
|
||||||
remappedHeaders={{
|
|
||||||
Metadata: "Sync Status"
|
|
||||||
}}
|
|
||||||
showFilters={false}
|
showFilters={false}
|
||||||
presets={{
|
presets={{
|
||||||
eventMetadata: { integrationId: integration.id },
|
eventMetadata: { integrationId: integration.id },
|
||||||
|
@ -192,6 +192,15 @@ export const IntegrationConnectionSection = ({ integration }: Props) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (integration.integration === "windmill" && integration.url) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<FormLabel className="text-sm font-semibold text-mineshaft-300" label="Instance URL" />
|
||||||
|
<div className="text-sm text-mineshaft-300">{integration.url}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ import {
|
|||||||
faAngleDown,
|
faAngleDown,
|
||||||
faArrowDown,
|
faArrowDown,
|
||||||
faArrowUp,
|
faArrowUp,
|
||||||
|
faFileImport,
|
||||||
faFingerprint,
|
faFingerprint,
|
||||||
faFolder,
|
faFolder,
|
||||||
faFolderBlank,
|
faFolderBlank,
|
||||||
@ -80,6 +81,7 @@ import { CreateSecretForm } from "./components/CreateSecretForm";
|
|||||||
import { FolderBreadCrumbs } from "./components/FolderBreadCrumbs";
|
import { FolderBreadCrumbs } from "./components/FolderBreadCrumbs";
|
||||||
import { SecretOverviewDynamicSecretRow } from "./components/SecretOverviewDynamicSecretRow";
|
import { SecretOverviewDynamicSecretRow } from "./components/SecretOverviewDynamicSecretRow";
|
||||||
import { SecretOverviewFolderRow } from "./components/SecretOverviewFolderRow";
|
import { SecretOverviewFolderRow } from "./components/SecretOverviewFolderRow";
|
||||||
|
import { SecretOverviewImportListView } from "./components/SecretOverviewImportListView";
|
||||||
import {
|
import {
|
||||||
SecretNoAccessOverviewTableRow,
|
SecretNoAccessOverviewTableRow,
|
||||||
SecretOverviewTableRow
|
SecretOverviewTableRow
|
||||||
@ -97,7 +99,8 @@ export enum EntryType {
|
|||||||
enum RowType {
|
enum RowType {
|
||||||
Folder = "folder",
|
Folder = "folder",
|
||||||
DynamicSecret = "dynamic",
|
DynamicSecret = "dynamic",
|
||||||
Secret = "secret"
|
Secret = "secret",
|
||||||
|
Import = "import"
|
||||||
}
|
}
|
||||||
|
|
||||||
type Filter = {
|
type Filter = {
|
||||||
@ -107,7 +110,8 @@ type Filter = {
|
|||||||
const DEFAULT_FILTER_STATE = {
|
const DEFAULT_FILTER_STATE = {
|
||||||
[RowType.Folder]: true,
|
[RowType.Folder]: true,
|
||||||
[RowType.DynamicSecret]: true,
|
[RowType.DynamicSecret]: true,
|
||||||
[RowType.Secret]: true
|
[RowType.Secret]: true,
|
||||||
|
[RowType.Import]: true
|
||||||
};
|
};
|
||||||
|
|
||||||
export const OverviewPage = () => {
|
export const OverviewPage = () => {
|
||||||
@ -216,6 +220,7 @@ export const OverviewPage = () => {
|
|||||||
includeFolders: filter.folder,
|
includeFolders: filter.folder,
|
||||||
includeDynamicSecrets: filter.dynamic,
|
includeDynamicSecrets: filter.dynamic,
|
||||||
includeSecrets: filter.secret,
|
includeSecrets: filter.secret,
|
||||||
|
includeImports: filter.import,
|
||||||
search: debouncedSearchFilter,
|
search: debouncedSearchFilter,
|
||||||
limit,
|
limit,
|
||||||
offset
|
offset
|
||||||
@ -227,15 +232,29 @@ export const OverviewPage = () => {
|
|||||||
secrets,
|
secrets,
|
||||||
folders,
|
folders,
|
||||||
dynamicSecrets,
|
dynamicSecrets,
|
||||||
|
imports,
|
||||||
totalFolderCount,
|
totalFolderCount,
|
||||||
totalSecretCount,
|
totalSecretCount,
|
||||||
totalDynamicSecretCount,
|
totalDynamicSecretCount,
|
||||||
|
totalImportCount,
|
||||||
totalCount = 0,
|
totalCount = 0,
|
||||||
totalUniqueFoldersInPage,
|
totalUniqueFoldersInPage,
|
||||||
totalUniqueSecretsInPage,
|
totalUniqueSecretsInPage,
|
||||||
|
totalUniqueSecretImportsInPage,
|
||||||
totalUniqueDynamicSecretsInPage
|
totalUniqueDynamicSecretsInPage
|
||||||
} = overview ?? {};
|
} = overview ?? {};
|
||||||
|
|
||||||
|
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({
|
useResetPageHelper({
|
||||||
totalCount,
|
totalCount,
|
||||||
offset,
|
offset,
|
||||||
@ -678,7 +697,6 @@ export const OverviewPage = () => {
|
|||||||
<SecretV2MigrationSection />
|
<SecretV2MigrationSection />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="">
|
<div className="">
|
||||||
<Helmet>
|
<Helmet>
|
||||||
@ -767,6 +785,19 @@ export const OverviewPage = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuItem> */}
|
</DropdownMenuItem> */}
|
||||||
<DropdownMenuLabel>Filter project resources</DropdownMenuLabel>
|
<DropdownMenuLabel>Filter project resources</DropdownMenuLabel>
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
handleToggleRowType(RowType.Import);
|
||||||
|
}}
|
||||||
|
icon={filter[RowType.Import] && <FontAwesomeIcon icon={faCheckCircle} />}
|
||||||
|
iconPos="right"
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<FontAwesomeIcon icon={faFileImport} className="text-green-700" />
|
||||||
|
<span>Imports</span>
|
||||||
|
</div>
|
||||||
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@ -1093,6 +1124,17 @@ export const OverviewPage = () => {
|
|||||||
key={`overview-${dynamicSecretName}-${index + 1}`}
|
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) => (
|
{secKeys.map((key, index) => (
|
||||||
<SecretOverviewTableRow
|
<SecretOverviewTableRow
|
||||||
isSelected={Boolean(selectedEntries.secret[key])}
|
isSelected={Boolean(selectedEntries.secret[key])}
|
||||||
@ -1116,7 +1158,8 @@ export const OverviewPage = () => {
|
|||||||
(page * perPage > totalCount ? totalCount % perPage : perPage) -
|
(page * perPage > totalCount ? totalCount % perPage : perPage) -
|
||||||
(totalUniqueFoldersInPage || 0) -
|
(totalUniqueFoldersInPage || 0) -
|
||||||
(totalUniqueDynamicSecretsInPage || 0) -
|
(totalUniqueDynamicSecretsInPage || 0) -
|
||||||
(totalUniqueSecretsInPage || 0),
|
(totalUniqueSecretsInPage || 0) -
|
||||||
|
(totalUniqueSecretImportsInPage || 0),
|
||||||
0
|
0
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@ -1156,6 +1199,7 @@ export const OverviewPage = () => {
|
|||||||
dynamicSecretCount={totalDynamicSecretCount}
|
dynamicSecretCount={totalDynamicSecretCount}
|
||||||
secretCount={totalSecretCount}
|
secretCount={totalSecretCount}
|
||||||
folderCount={totalFolderCount}
|
folderCount={totalFolderCount}
|
||||||
|
importCount={totalImportCount}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
className="rounded-b-md border-t border-solid border-t-mineshaft-600"
|
className="rounded-b-md border-t border-solid border-t-mineshaft-600"
|
||||||
|
@ -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";
|
@ -35,9 +35,6 @@ export const SecretSyncAuditLogsSection = ({ secretSync }: Props) => {
|
|||||||
{subscription.auditLogs ? (
|
{subscription.auditLogs ? (
|
||||||
<LogsSection
|
<LogsSection
|
||||||
refetchInterval={4000}
|
refetchInterval={4000}
|
||||||
remappedHeaders={{
|
|
||||||
Metadata: "Sync Status"
|
|
||||||
}}
|
|
||||||
showFilters={false}
|
showFilters={false}
|
||||||
presets={{
|
presets={{
|
||||||
eventMetadata: { syncId: secretSync.id },
|
eventMetadata: { syncId: secretSync.id },
|
||||||
|
@ -3,31 +3,75 @@ import { useNavigate } from "@tanstack/react-router";
|
|||||||
|
|
||||||
import { Button, Card, CardTitle, FormControl, Input } from "@app/components/v2";
|
import { Button, Card, CardTitle, FormControl, Input } from "@app/components/v2";
|
||||||
import { useWorkspace } from "@app/context";
|
import { useWorkspace } from "@app/context";
|
||||||
|
import { isInfisicalCloud } from "@app/helpers/platform";
|
||||||
import { useSaveIntegrationAccessToken } from "@app/hooks/api";
|
import { useSaveIntegrationAccessToken } from "@app/hooks/api";
|
||||||
|
|
||||||
export const WindmillAuthorizePage = () => {
|
export const WindmillAuthorizePage = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { mutateAsync } = useSaveIntegrationAccessToken();
|
const { mutateAsync } = useSaveIntegrationAccessToken();
|
||||||
|
|
||||||
const { currentWorkspace } = useWorkspace();
|
const { currentWorkspace } = useWorkspace();
|
||||||
const [apiKey, setApiKey] = useState("");
|
const [apiKey, setApiKey] = useState("");
|
||||||
const [apiKeyErrorText, setApiKeyErrorText] = useState("");
|
const [apiKeyErrorText, setApiKeyErrorText] = useState("");
|
||||||
|
const [apiUrl, setApiUrl] = useState<string | null>(null);
|
||||||
|
const [apiUrlErrorText, setApiUrlErrorText] = useState("");
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
|
const isLocalOrPrivateIpAddress = (url: string): boolean => {
|
||||||
|
try {
|
||||||
|
const validUrl = new URL(url);
|
||||||
|
// Check for localhost
|
||||||
|
if (validUrl.hostname === "localhost" || validUrl.hostname === "127.0.0.1") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for 10.x.x.x
|
||||||
|
if (validUrl.hostname.match(/^10\.\d+\.\d+\.\d+/)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for host.docker.internal
|
||||||
|
if (validUrl.hostname === "host.docker.internal") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for 192.168.x.x
|
||||||
|
if (validUrl.hostname.match(/^192\.168\.\d+\.\d+/)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleButtonClick = async () => {
|
const handleButtonClick = async () => {
|
||||||
try {
|
try {
|
||||||
setApiKeyErrorText("");
|
setApiKeyErrorText("");
|
||||||
|
setApiUrlErrorText("");
|
||||||
if (apiKey.length === 0) {
|
if (apiKey.length === 0) {
|
||||||
setApiKeyErrorText("API Key cannot be blank");
|
setApiKeyErrorText("API Key cannot be blank");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (apiUrl) {
|
||||||
|
if (!apiUrl.startsWith("http://") && !apiUrl.startsWith("https://")) {
|
||||||
|
setApiUrlErrorText("API URL must start with http:// or https://");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isInfisicalCloud() && isLocalOrPrivateIpAddress(apiUrl)) {
|
||||||
|
setApiUrlErrorText("Local IPs not allowed as URL");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
const integrationAuth = await mutateAsync({
|
const integrationAuth = await mutateAsync({
|
||||||
workspaceId: currentWorkspace.id,
|
workspaceId: currentWorkspace.id,
|
||||||
integration: "windmill",
|
integration: "windmill",
|
||||||
accessToken: apiKey
|
accessToken: apiKey,
|
||||||
|
url: apiUrl ?? undefined
|
||||||
});
|
});
|
||||||
|
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
@ -57,6 +101,18 @@ export const WindmillAuthorizePage = () => {
|
|||||||
>
|
>
|
||||||
<Input placeholder="" value={apiKey} onChange={(e) => setApiKey(e.target.value)} />
|
<Input placeholder="" value={apiKey} onChange={(e) => setApiKey(e.target.value)} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
<FormControl
|
||||||
|
label="Windmill Instance URL"
|
||||||
|
errorText={apiUrlErrorText}
|
||||||
|
isError={apiUrlErrorText !== ""}
|
||||||
|
tooltipText="If you are using a custom domain, enter it here. Otherwise, leave it blank."
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
value={apiUrl ?? ""}
|
||||||
|
onChange={(e) => setApiUrl(e.target.value.trim() === "" ? null : e.target.value.trim())}
|
||||||
|
placeholder="https://xxxx.windmill.dev"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleButtonClick}
|
onClick={handleButtonClick}
|
||||||
color="mineshaft"
|
color="mineshaft"
|
||||||
|
@ -67,7 +67,8 @@ export const WindmillConfigurePage = () => {
|
|||||||
(integrationAuthApp) => integrationAuthApp.name === targetApp
|
(integrationAuthApp) => integrationAuthApp.name === targetApp
|
||||||
)?.appId,
|
)?.appId,
|
||||||
sourceEnvironment: selectedSourceEnvironment,
|
sourceEnvironment: selectedSourceEnvironment,
|
||||||
secretPath
|
secretPath,
|
||||||
|
url: integrationAuth.url ?? undefined
|
||||||
});
|
});
|
||||||
|
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
|
Reference in New Issue
Block a user