mirror of
https://github.com/Infisical/infisical.git
synced 2025-07-05 04:29:09 +00:00
Compare commits
66 Commits
infisical/
...
misc/add-o
Author | SHA1 | Date | |
---|---|---|---|
e28471a9f4 | |||
7cdc47cd3a | |||
d666d60f9f | |||
491c4259ca | |||
cff20eb621 | |||
84d8879177 | |||
aa4f2adbb6 | |||
86ed3ef6d6 | |||
a5bb80adc4 | |||
0e87dd3996 | |||
e1801e9eb4 | |||
f4a33caba6 | |||
e0a6f09b5e | |||
1e701687ae | |||
15758b91f8 | |||
2d3a4a7559 | |||
a1d01d5cbd | |||
2e3aedc62b | |||
e0a5b1444a | |||
b50833bded | |||
e0c774c045 | |||
514df55d67 | |||
311b378f3b | |||
b01b4323ca | |||
285a01af51 | |||
f7e658e62b | |||
a8aef2934a | |||
cc30476f79 | |||
5139bf2385 | |||
a016d0d33f | |||
663be06d30 | |||
fa392382da | |||
d34b2669c5 | |||
11ea5990c9 | |||
9a66514178 | |||
d4f9faf24d | |||
a3c8d06845 | |||
71b7be4057 | |||
5079a5889a | |||
232b375f46 | |||
d2acedf79e | |||
9d846319b0 | |||
d69267a3ca | |||
051eee8701 | |||
b5aa650899 | |||
a15a0a257c | |||
745f1c4e12 | |||
6029eaa9df | |||
8703314c0c | |||
084fc7c99e | |||
b6cc17d62a | |||
bd0d0bd333 | |||
4072a40fe9 | |||
0dc132dda3 | |||
605ccb13e9 | |||
2160c66e20 | |||
1c5c7c75c4 | |||
24c75c6325 | |||
0a22a2a9ef | |||
d0f1cad98c | |||
afee158b95 | |||
f9a9599659 | |||
637b0b955f | |||
092665737f | |||
f83c2215a5 | |||
0f41590d6a |
@ -56,7 +56,7 @@ jobs:
|
|||||||
# Learn more about [Twingate Services](https://docs.twingate.com/docs/services)
|
# Learn more about [Twingate Services](https://docs.twingate.com/docs/services)
|
||||||
#
|
#
|
||||||
# Required
|
# Required
|
||||||
service-key: ${{ secrets.TWINGATE_GAMMA_SERVICE_KEY }}
|
service-key: ${{ secrets.TWINGATE_SERVICE_KEY }}
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
- name: Setup Node.js environment
|
- name: Setup Node.js environment
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasTable(TableName.Certificate)) {
|
||||||
|
const hasAltNamesColumn = await knex.schema.hasColumn(TableName.Certificate, "altNames");
|
||||||
|
if (!hasAltNamesColumn) {
|
||||||
|
await knex.schema.alterTable(TableName.Certificate, (t) => {
|
||||||
|
t.string("altNames").defaultTo("");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasTable(TableName.Certificate)) {
|
||||||
|
if (await knex.schema.hasColumn(TableName.Certificate, "altNames")) {
|
||||||
|
await knex.schema.alterTable(TableName.Certificate, (t) => {
|
||||||
|
t.dropColumn("altNames");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (!(await knex.schema.hasColumn(TableName.LdapConfig, "uniqueUserAttribute"))) {
|
||||||
|
await knex.schema.alterTable(TableName.LdapConfig, (tb) => {
|
||||||
|
tb.string("uniqueUserAttribute").notNullable().defaultTo("");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasColumn(TableName.LdapConfig, "uniqueUserAttribute")) {
|
||||||
|
await knex.schema.alterTable(TableName.LdapConfig, (t) => {
|
||||||
|
t.dropColumn("uniqueUserAttribute");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (!(await knex.schema.hasColumn(TableName.Project, "auditLogsRetentionDays"))) {
|
||||||
|
await knex.schema.alterTable(TableName.Project, (tb) => {
|
||||||
|
tb.integer("auditLogsRetentionDays").nullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasColumn(TableName.Project, "auditLogsRetentionDays")) {
|
||||||
|
await knex.schema.alterTable(TableName.Project, (t) => {
|
||||||
|
t.dropColumn("auditLogsRetentionDays");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
await createOnUpdateTrigger(knex, TableName.OidcConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.OidcConfig);
|
||||||
|
}
|
@ -19,7 +19,8 @@ export const CertificatesSchema = z.object({
|
|||||||
notBefore: z.date(),
|
notBefore: z.date(),
|
||||||
notAfter: z.date(),
|
notAfter: z.date(),
|
||||||
revokedAt: z.date().nullable().optional(),
|
revokedAt: z.date().nullable().optional(),
|
||||||
revocationReason: z.number().nullable().optional()
|
revocationReason: z.number().nullable().optional(),
|
||||||
|
altNames: z.string().default("").nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TCertificates = z.infer<typeof CertificatesSchema>;
|
export type TCertificates = z.infer<typeof CertificatesSchema>;
|
||||||
|
@ -26,7 +26,8 @@ export const LdapConfigsSchema = z.object({
|
|||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
groupSearchBase: z.string().default(""),
|
groupSearchBase: z.string().default(""),
|
||||||
groupSearchFilter: z.string().default(""),
|
groupSearchFilter: z.string().default(""),
|
||||||
searchFilter: z.string().default("")
|
searchFilter: z.string().default(""),
|
||||||
|
uniqueUserAttribute: z.string().default("")
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TLdapConfigs = z.infer<typeof LdapConfigsSchema>;
|
export type TLdapConfigs = z.infer<typeof LdapConfigsSchema>;
|
||||||
|
@ -17,8 +17,9 @@ export const ProjectsSchema = z.object({
|
|||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
version: z.number().default(1),
|
version: z.number().default(1),
|
||||||
upgradeStatus: z.string().nullable().optional(),
|
upgradeStatus: z.string().nullable().optional(),
|
||||||
|
pitVersionLimit: z.number().default(10),
|
||||||
kmsCertificateKeyId: z.string().uuid().nullable().optional(),
|
kmsCertificateKeyId: z.string().uuid().nullable().optional(),
|
||||||
pitVersionLimit: z.number().default(10)
|
auditLogsRetentionDays: z.number().nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TProjects = z.infer<typeof ProjectsSchema>;
|
export type TProjects = z.infer<typeof ProjectsSchema>;
|
||||||
|
@ -70,10 +70,13 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => {
|
|||||||
groups = await searchGroups(ldapConfig, groupSearchFilter, ldapConfig.groupSearchBase);
|
groups = await searchGroups(ldapConfig, groupSearchFilter, ldapConfig.groupSearchBase);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const externalId = ldapConfig.uniqueUserAttribute ? user[ldapConfig.uniqueUserAttribute] : user.uidNumber;
|
||||||
|
const username = ldapConfig.uniqueUserAttribute ? externalId : user.uid;
|
||||||
|
|
||||||
const { isUserCompleted, providerAuthToken } = await server.services.ldap.ldapLogin({
|
const { isUserCompleted, providerAuthToken } = await server.services.ldap.ldapLogin({
|
||||||
|
externalId,
|
||||||
|
username,
|
||||||
ldapConfigId: ldapConfig.id,
|
ldapConfigId: ldapConfig.id,
|
||||||
externalId: user.uidNumber,
|
|
||||||
username: user.uid,
|
|
||||||
firstName: user.givenName ?? user.cn ?? "",
|
firstName: user.givenName ?? user.cn ?? "",
|
||||||
lastName: user.sn ?? "",
|
lastName: user.sn ?? "",
|
||||||
email: user.mail,
|
email: user.mail,
|
||||||
@ -138,6 +141,7 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => {
|
|||||||
url: z.string(),
|
url: z.string(),
|
||||||
bindDN: z.string(),
|
bindDN: z.string(),
|
||||||
bindPass: z.string(),
|
bindPass: z.string(),
|
||||||
|
uniqueUserAttribute: z.string(),
|
||||||
searchBase: z.string(),
|
searchBase: z.string(),
|
||||||
searchFilter: z.string(),
|
searchFilter: z.string(),
|
||||||
groupSearchBase: z.string(),
|
groupSearchBase: z.string(),
|
||||||
@ -172,6 +176,7 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => {
|
|||||||
url: z.string().trim(),
|
url: z.string().trim(),
|
||||||
bindDN: z.string().trim(),
|
bindDN: z.string().trim(),
|
||||||
bindPass: z.string().trim(),
|
bindPass: z.string().trim(),
|
||||||
|
uniqueUserAttribute: z.string().trim().default("uidNumber"),
|
||||||
searchBase: z.string().trim(),
|
searchBase: z.string().trim(),
|
||||||
searchFilter: z.string().trim().default("(uid={{username}})"),
|
searchFilter: z.string().trim().default("(uid={{username}})"),
|
||||||
groupSearchBase: z.string().trim(),
|
groupSearchBase: z.string().trim(),
|
||||||
@ -213,6 +218,7 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => {
|
|||||||
url: z.string().trim(),
|
url: z.string().trim(),
|
||||||
bindDN: z.string().trim(),
|
bindDN: z.string().trim(),
|
||||||
bindPass: z.string().trim(),
|
bindPass: z.string().trim(),
|
||||||
|
uniqueUserAttribute: z.string().trim(),
|
||||||
searchBase: z.string().trim(),
|
searchBase: z.string().trim(),
|
||||||
searchFilter: z.string().trim(),
|
searchFilter: z.string().trim(),
|
||||||
groupSearchBase: z.string().trim(),
|
groupSearchBase: z.string().trim(),
|
||||||
|
@ -45,18 +45,29 @@ export const auditLogQueueServiceFactory = ({
|
|||||||
const { actor, event, ipAddress, projectId, userAgent, userAgentType } = job.data;
|
const { actor, event, ipAddress, projectId, userAgent, userAgentType } = job.data;
|
||||||
let { orgId } = job.data;
|
let { orgId } = job.data;
|
||||||
const MS_IN_DAY = 24 * 60 * 60 * 1000;
|
const MS_IN_DAY = 24 * 60 * 60 * 1000;
|
||||||
|
let project;
|
||||||
|
|
||||||
if (!orgId) {
|
if (!orgId) {
|
||||||
// it will never be undefined for both org and project id
|
// it will never be undefined for both org and project id
|
||||||
// TODO(akhilmhdh): use caching here in dal to avoid db calls
|
// TODO(akhilmhdh): use caching here in dal to avoid db calls
|
||||||
const project = await projectDAL.findById(projectId as string);
|
project = await projectDAL.findById(projectId as string);
|
||||||
orgId = project.orgId;
|
orgId = project.orgId;
|
||||||
}
|
}
|
||||||
|
|
||||||
const plan = await licenseService.getPlan(orgId);
|
const plan = await licenseService.getPlan(orgId);
|
||||||
const ttl = plan.auditLogsRetentionDays * MS_IN_DAY;
|
if (plan.auditLogsRetentionDays === 0) {
|
||||||
// skip inserting if audit log retention is 0 meaning its not supported
|
// skip inserting if audit log retention is 0 meaning its not supported
|
||||||
if (ttl === 0) return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For project actions, set TTL to project-level audit log retention config
|
||||||
|
// This condition ensures that the plan's audit log retention days cannot be bypassed
|
||||||
|
const ttlInDays =
|
||||||
|
project?.auditLogsRetentionDays && project.auditLogsRetentionDays < plan.auditLogsRetentionDays
|
||||||
|
? project.auditLogsRetentionDays
|
||||||
|
: plan.auditLogsRetentionDays;
|
||||||
|
|
||||||
|
const ttl = ttlInDays * MS_IN_DAY;
|
||||||
|
|
||||||
const auditLog = await auditLogDAL.create({
|
const auditLog = await auditLogDAL.create({
|
||||||
actor: actor.type,
|
actor: actor.type,
|
||||||
|
@ -65,25 +65,31 @@ export enum EventType {
|
|||||||
ADD_IDENTITY_UNIVERSAL_AUTH = "add-identity-universal-auth",
|
ADD_IDENTITY_UNIVERSAL_AUTH = "add-identity-universal-auth",
|
||||||
UPDATE_IDENTITY_UNIVERSAL_AUTH = "update-identity-universal-auth",
|
UPDATE_IDENTITY_UNIVERSAL_AUTH = "update-identity-universal-auth",
|
||||||
GET_IDENTITY_UNIVERSAL_AUTH = "get-identity-universal-auth",
|
GET_IDENTITY_UNIVERSAL_AUTH = "get-identity-universal-auth",
|
||||||
|
REVOKE_IDENTITY_UNIVERSAL_AUTH = "revoke-identity-universal-auth",
|
||||||
LOGIN_IDENTITY_KUBERNETES_AUTH = "login-identity-kubernetes-auth",
|
LOGIN_IDENTITY_KUBERNETES_AUTH = "login-identity-kubernetes-auth",
|
||||||
ADD_IDENTITY_KUBERNETES_AUTH = "add-identity-kubernetes-auth",
|
ADD_IDENTITY_KUBERNETES_AUTH = "add-identity-kubernetes-auth",
|
||||||
UPDATE_IDENTITY_KUBENETES_AUTH = "update-identity-kubernetes-auth",
|
UPDATE_IDENTITY_KUBENETES_AUTH = "update-identity-kubernetes-auth",
|
||||||
GET_IDENTITY_KUBERNETES_AUTH = "get-identity-kubernetes-auth",
|
GET_IDENTITY_KUBERNETES_AUTH = "get-identity-kubernetes-auth",
|
||||||
|
REVOKE_IDENTITY_KUBERNETES_AUTH = "revoke-identity-kubernetes-auth",
|
||||||
CREATE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET = "create-identity-universal-auth-client-secret",
|
CREATE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET = "create-identity-universal-auth-client-secret",
|
||||||
REVOKE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET = "revoke-identity-universal-auth-client-secret",
|
REVOKE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET = "revoke-identity-universal-auth-client-secret",
|
||||||
GET_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRETS = "get-identity-universal-auth-client-secret",
|
GET_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRETS = "get-identity-universal-auth-client-secret",
|
||||||
|
GET_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET_BY_ID = "get-identity-universal-auth-client-secret-by-id",
|
||||||
LOGIN_IDENTITY_GCP_AUTH = "login-identity-gcp-auth",
|
LOGIN_IDENTITY_GCP_AUTH = "login-identity-gcp-auth",
|
||||||
ADD_IDENTITY_GCP_AUTH = "add-identity-gcp-auth",
|
ADD_IDENTITY_GCP_AUTH = "add-identity-gcp-auth",
|
||||||
UPDATE_IDENTITY_GCP_AUTH = "update-identity-gcp-auth",
|
UPDATE_IDENTITY_GCP_AUTH = "update-identity-gcp-auth",
|
||||||
|
REVOKE_IDENTITY_GCP_AUTH = "revoke-identity-gcp-auth",
|
||||||
GET_IDENTITY_GCP_AUTH = "get-identity-gcp-auth",
|
GET_IDENTITY_GCP_AUTH = "get-identity-gcp-auth",
|
||||||
LOGIN_IDENTITY_AWS_AUTH = "login-identity-aws-auth",
|
LOGIN_IDENTITY_AWS_AUTH = "login-identity-aws-auth",
|
||||||
ADD_IDENTITY_AWS_AUTH = "add-identity-aws-auth",
|
ADD_IDENTITY_AWS_AUTH = "add-identity-aws-auth",
|
||||||
UPDATE_IDENTITY_AWS_AUTH = "update-identity-aws-auth",
|
UPDATE_IDENTITY_AWS_AUTH = "update-identity-aws-auth",
|
||||||
|
REVOKE_IDENTITY_AWS_AUTH = "revoke-identity-aws-auth",
|
||||||
GET_IDENTITY_AWS_AUTH = "get-identity-aws-auth",
|
GET_IDENTITY_AWS_AUTH = "get-identity-aws-auth",
|
||||||
LOGIN_IDENTITY_AZURE_AUTH = "login-identity-azure-auth",
|
LOGIN_IDENTITY_AZURE_AUTH = "login-identity-azure-auth",
|
||||||
ADD_IDENTITY_AZURE_AUTH = "add-identity-azure-auth",
|
ADD_IDENTITY_AZURE_AUTH = "add-identity-azure-auth",
|
||||||
UPDATE_IDENTITY_AZURE_AUTH = "update-identity-azure-auth",
|
UPDATE_IDENTITY_AZURE_AUTH = "update-identity-azure-auth",
|
||||||
GET_IDENTITY_AZURE_AUTH = "get-identity-azure-auth",
|
GET_IDENTITY_AZURE_AUTH = "get-identity-azure-auth",
|
||||||
|
REVOKE_IDENTITY_AZURE_AUTH = "revoke-identity-azure-auth",
|
||||||
CREATE_ENVIRONMENT = "create-environment",
|
CREATE_ENVIRONMENT = "create-environment",
|
||||||
UPDATE_ENVIRONMENT = "update-environment",
|
UPDATE_ENVIRONMENT = "update-environment",
|
||||||
DELETE_ENVIRONMENT = "delete-environment",
|
DELETE_ENVIRONMENT = "delete-environment",
|
||||||
@ -434,6 +440,13 @@ interface GetIdentityUniversalAuthEvent {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface DeleteIdentityUniversalAuthEvent {
|
||||||
|
type: EventType.REVOKE_IDENTITY_UNIVERSAL_AUTH;
|
||||||
|
metadata: {
|
||||||
|
identityId: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
interface LoginIdentityKubernetesAuthEvent {
|
interface LoginIdentityKubernetesAuthEvent {
|
||||||
type: EventType.LOGIN_IDENTITY_KUBERNETES_AUTH;
|
type: EventType.LOGIN_IDENTITY_KUBERNETES_AUTH;
|
||||||
metadata: {
|
metadata: {
|
||||||
@ -457,6 +470,13 @@ interface AddIdentityKubernetesAuthEvent {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface DeleteIdentityKubernetesAuthEvent {
|
||||||
|
type: EventType.REVOKE_IDENTITY_KUBERNETES_AUTH;
|
||||||
|
metadata: {
|
||||||
|
identityId: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
interface UpdateIdentityKubernetesAuthEvent {
|
interface UpdateIdentityKubernetesAuthEvent {
|
||||||
type: EventType.UPDATE_IDENTITY_KUBENETES_AUTH;
|
type: EventType.UPDATE_IDENTITY_KUBENETES_AUTH;
|
||||||
metadata: {
|
metadata: {
|
||||||
@ -493,6 +513,14 @@ interface GetIdentityUniversalAuthClientSecretsEvent {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface GetIdentityUniversalAuthClientSecretByIdEvent {
|
||||||
|
type: EventType.GET_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET_BY_ID;
|
||||||
|
metadata: {
|
||||||
|
identityId: string;
|
||||||
|
clientSecretId: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
interface RevokeIdentityUniversalAuthClientSecretEvent {
|
interface RevokeIdentityUniversalAuthClientSecretEvent {
|
||||||
type: EventType.REVOKE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET;
|
type: EventType.REVOKE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET;
|
||||||
metadata: {
|
metadata: {
|
||||||
@ -525,6 +553,13 @@ interface AddIdentityGcpAuthEvent {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface DeleteIdentityGcpAuthEvent {
|
||||||
|
type: EventType.REVOKE_IDENTITY_GCP_AUTH;
|
||||||
|
metadata: {
|
||||||
|
identityId: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
interface UpdateIdentityGcpAuthEvent {
|
interface UpdateIdentityGcpAuthEvent {
|
||||||
type: EventType.UPDATE_IDENTITY_GCP_AUTH;
|
type: EventType.UPDATE_IDENTITY_GCP_AUTH;
|
||||||
metadata: {
|
metadata: {
|
||||||
@ -570,6 +605,13 @@ interface AddIdentityAwsAuthEvent {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface DeleteIdentityAwsAuthEvent {
|
||||||
|
type: EventType.REVOKE_IDENTITY_AWS_AUTH;
|
||||||
|
metadata: {
|
||||||
|
identityId: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
interface UpdateIdentityAwsAuthEvent {
|
interface UpdateIdentityAwsAuthEvent {
|
||||||
type: EventType.UPDATE_IDENTITY_AWS_AUTH;
|
type: EventType.UPDATE_IDENTITY_AWS_AUTH;
|
||||||
metadata: {
|
metadata: {
|
||||||
@ -613,6 +655,13 @@ interface AddIdentityAzureAuthEvent {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface DeleteIdentityAzureAuthEvent {
|
||||||
|
type: EventType.REVOKE_IDENTITY_AZURE_AUTH;
|
||||||
|
metadata: {
|
||||||
|
identityId: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
interface UpdateIdentityAzureAuthEvent {
|
interface UpdateIdentityAzureAuthEvent {
|
||||||
type: EventType.UPDATE_IDENTITY_AZURE_AUTH;
|
type: EventType.UPDATE_IDENTITY_AZURE_AUTH;
|
||||||
metadata: {
|
metadata: {
|
||||||
@ -1003,24 +1052,30 @@ export type Event =
|
|||||||
| LoginIdentityUniversalAuthEvent
|
| LoginIdentityUniversalAuthEvent
|
||||||
| AddIdentityUniversalAuthEvent
|
| AddIdentityUniversalAuthEvent
|
||||||
| UpdateIdentityUniversalAuthEvent
|
| UpdateIdentityUniversalAuthEvent
|
||||||
|
| DeleteIdentityUniversalAuthEvent
|
||||||
| GetIdentityUniversalAuthEvent
|
| GetIdentityUniversalAuthEvent
|
||||||
| LoginIdentityKubernetesAuthEvent
|
| LoginIdentityKubernetesAuthEvent
|
||||||
|
| DeleteIdentityKubernetesAuthEvent
|
||||||
| AddIdentityKubernetesAuthEvent
|
| AddIdentityKubernetesAuthEvent
|
||||||
| UpdateIdentityKubernetesAuthEvent
|
| UpdateIdentityKubernetesAuthEvent
|
||||||
| GetIdentityKubernetesAuthEvent
|
| GetIdentityKubernetesAuthEvent
|
||||||
| CreateIdentityUniversalAuthClientSecretEvent
|
| CreateIdentityUniversalAuthClientSecretEvent
|
||||||
| GetIdentityUniversalAuthClientSecretsEvent
|
| GetIdentityUniversalAuthClientSecretsEvent
|
||||||
|
| GetIdentityUniversalAuthClientSecretByIdEvent
|
||||||
| RevokeIdentityUniversalAuthClientSecretEvent
|
| RevokeIdentityUniversalAuthClientSecretEvent
|
||||||
| LoginIdentityGcpAuthEvent
|
| LoginIdentityGcpAuthEvent
|
||||||
| AddIdentityGcpAuthEvent
|
| AddIdentityGcpAuthEvent
|
||||||
|
| DeleteIdentityGcpAuthEvent
|
||||||
| UpdateIdentityGcpAuthEvent
|
| UpdateIdentityGcpAuthEvent
|
||||||
| GetIdentityGcpAuthEvent
|
| GetIdentityGcpAuthEvent
|
||||||
| LoginIdentityAwsAuthEvent
|
| LoginIdentityAwsAuthEvent
|
||||||
| AddIdentityAwsAuthEvent
|
| AddIdentityAwsAuthEvent
|
||||||
| UpdateIdentityAwsAuthEvent
|
| UpdateIdentityAwsAuthEvent
|
||||||
| GetIdentityAwsAuthEvent
|
| GetIdentityAwsAuthEvent
|
||||||
|
| DeleteIdentityAwsAuthEvent
|
||||||
| LoginIdentityAzureAuthEvent
|
| LoginIdentityAzureAuthEvent
|
||||||
| AddIdentityAzureAuthEvent
|
| AddIdentityAzureAuthEvent
|
||||||
|
| DeleteIdentityAzureAuthEvent
|
||||||
| UpdateIdentityAzureAuthEvent
|
| UpdateIdentityAzureAuthEvent
|
||||||
| GetIdentityAzureAuthEvent
|
| GetIdentityAzureAuthEvent
|
||||||
| CreateEnvironmentEvent
|
| CreateEnvironmentEvent
|
||||||
|
@ -122,6 +122,7 @@ export const ldapConfigServiceFactory = ({
|
|||||||
url,
|
url,
|
||||||
bindDN,
|
bindDN,
|
||||||
bindPass,
|
bindPass,
|
||||||
|
uniqueUserAttribute,
|
||||||
searchBase,
|
searchBase,
|
||||||
searchFilter,
|
searchFilter,
|
||||||
groupSearchBase,
|
groupSearchBase,
|
||||||
@ -200,6 +201,7 @@ export const ldapConfigServiceFactory = ({
|
|||||||
encryptedBindPass,
|
encryptedBindPass,
|
||||||
bindPassIV,
|
bindPassIV,
|
||||||
bindPassTag,
|
bindPassTag,
|
||||||
|
uniqueUserAttribute,
|
||||||
searchBase,
|
searchBase,
|
||||||
searchFilter,
|
searchFilter,
|
||||||
groupSearchBase,
|
groupSearchBase,
|
||||||
@ -222,6 +224,7 @@ export const ldapConfigServiceFactory = ({
|
|||||||
url,
|
url,
|
||||||
bindDN,
|
bindDN,
|
||||||
bindPass,
|
bindPass,
|
||||||
|
uniqueUserAttribute,
|
||||||
searchBase,
|
searchBase,
|
||||||
searchFilter,
|
searchFilter,
|
||||||
groupSearchBase,
|
groupSearchBase,
|
||||||
@ -244,7 +247,8 @@ export const ldapConfigServiceFactory = ({
|
|||||||
searchBase,
|
searchBase,
|
||||||
searchFilter,
|
searchFilter,
|
||||||
groupSearchBase,
|
groupSearchBase,
|
||||||
groupSearchFilter
|
groupSearchFilter,
|
||||||
|
uniqueUserAttribute
|
||||||
};
|
};
|
||||||
|
|
||||||
const orgBot = await orgBotDAL.findOne({ orgId });
|
const orgBot = await orgBotDAL.findOne({ orgId });
|
||||||
@ -345,6 +349,7 @@ export const ldapConfigServiceFactory = ({
|
|||||||
url: ldapConfig.url,
|
url: ldapConfig.url,
|
||||||
bindDN,
|
bindDN,
|
||||||
bindPass,
|
bindPass,
|
||||||
|
uniqueUserAttribute: ldapConfig.uniqueUserAttribute,
|
||||||
searchBase: ldapConfig.searchBase,
|
searchBase: ldapConfig.searchBase,
|
||||||
searchFilter: ldapConfig.searchFilter,
|
searchFilter: ldapConfig.searchFilter,
|
||||||
groupSearchBase: ldapConfig.groupSearchBase,
|
groupSearchBase: ldapConfig.groupSearchBase,
|
||||||
@ -381,6 +386,7 @@ export const ldapConfigServiceFactory = ({
|
|||||||
url: ldapConfig.url,
|
url: ldapConfig.url,
|
||||||
bindDN: ldapConfig.bindDN,
|
bindDN: ldapConfig.bindDN,
|
||||||
bindCredentials: ldapConfig.bindPass,
|
bindCredentials: ldapConfig.bindPass,
|
||||||
|
uniqueUserAttribute: ldapConfig.uniqueUserAttribute,
|
||||||
searchBase: ldapConfig.searchBase,
|
searchBase: ldapConfig.searchBase,
|
||||||
searchFilter: ldapConfig.searchFilter || "(uid={{username}})",
|
searchFilter: ldapConfig.searchFilter || "(uid={{username}})",
|
||||||
// searchAttributes: ["uid", "uidNumber", "givenName", "sn", "mail"],
|
// searchAttributes: ["uid", "uidNumber", "givenName", "sn", "mail"],
|
||||||
|
@ -7,6 +7,7 @@ export type TLDAPConfig = {
|
|||||||
url: string;
|
url: string;
|
||||||
bindDN: string;
|
bindDN: string;
|
||||||
bindPass: string;
|
bindPass: string;
|
||||||
|
uniqueUserAttribute: string;
|
||||||
searchBase: string;
|
searchBase: string;
|
||||||
groupSearchBase: string;
|
groupSearchBase: string;
|
||||||
groupSearchFilter: string;
|
groupSearchFilter: string;
|
||||||
@ -19,6 +20,7 @@ export type TCreateLdapCfgDTO = {
|
|||||||
url: string;
|
url: string;
|
||||||
bindDN: string;
|
bindDN: string;
|
||||||
bindPass: string;
|
bindPass: string;
|
||||||
|
uniqueUserAttribute: string;
|
||||||
searchBase: string;
|
searchBase: string;
|
||||||
searchFilter: string;
|
searchFilter: string;
|
||||||
groupSearchBase: string;
|
groupSearchBase: string;
|
||||||
@ -33,6 +35,7 @@ export type TUpdateLdapCfgDTO = {
|
|||||||
url: string;
|
url: string;
|
||||||
bindDN: string;
|
bindDN: string;
|
||||||
bindPass: string;
|
bindPass: string;
|
||||||
|
uniqueUserAttribute: string;
|
||||||
searchBase: string;
|
searchBase: string;
|
||||||
searchFilter: string;
|
searchFilter: string;
|
||||||
groupSearchBase: string;
|
groupSearchBase: string;
|
||||||
|
@ -42,6 +42,13 @@ export const IDENTITIES = {
|
|||||||
},
|
},
|
||||||
DELETE: {
|
DELETE: {
|
||||||
identityId: "The ID of the identity to delete."
|
identityId: "The ID of the identity to delete."
|
||||||
|
},
|
||||||
|
GET_BY_ID: {
|
||||||
|
identityId: "The ID of the identity to get details.",
|
||||||
|
orgId: "The ID of the org of the identity"
|
||||||
|
},
|
||||||
|
LIST: {
|
||||||
|
orgId: "The ID of the organization to list identities."
|
||||||
}
|
}
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
@ -65,6 +72,9 @@ export const UNIVERSAL_AUTH = {
|
|||||||
RETRIEVE: {
|
RETRIEVE: {
|
||||||
identityId: "The ID of the identity to retrieve."
|
identityId: "The ID of the identity to retrieve."
|
||||||
},
|
},
|
||||||
|
REVOKE: {
|
||||||
|
identityId: "The ID of the identity to revoke."
|
||||||
|
},
|
||||||
UPDATE: {
|
UPDATE: {
|
||||||
identityId: "The ID of the identity to update.",
|
identityId: "The ID of the identity to update.",
|
||||||
clientSecretTrustedIps: "The new list of IPs or CIDR ranges that the Client Secret can be used from.",
|
clientSecretTrustedIps: "The new list of IPs or CIDR ranges that the Client Secret can be used from.",
|
||||||
@ -83,6 +93,10 @@ export const UNIVERSAL_AUTH = {
|
|||||||
LIST_CLIENT_SECRETS: {
|
LIST_CLIENT_SECRETS: {
|
||||||
identityId: "The ID of the identity to list client secrets for."
|
identityId: "The ID of the identity to list client secrets for."
|
||||||
},
|
},
|
||||||
|
GET_CLIENT_SECRET: {
|
||||||
|
identityId: "The ID of the identity to get the client secret from.",
|
||||||
|
clientSecretId: "The ID of the client secret to get details."
|
||||||
|
},
|
||||||
REVOKE_CLIENT_SECRET: {
|
REVOKE_CLIENT_SECRET: {
|
||||||
identityId: "The ID of the identity to revoke the client secret from.",
|
identityId: "The ID of the identity to revoke the client secret from.",
|
||||||
clientSecretId: "The ID of the client secret to revoke."
|
clientSecretId: "The ID of the client secret to revoke."
|
||||||
@ -104,6 +118,27 @@ export const AWS_AUTH = {
|
|||||||
iamRequestBody:
|
iamRequestBody:
|
||||||
"The base64-encoded body of the signed request. Most likely, the base64-encoding of Action=GetCallerIdentity&Version=2011-06-15.",
|
"The base64-encoded body of the signed request. Most likely, the base64-encoding of Action=GetCallerIdentity&Version=2011-06-15.",
|
||||||
iamRequestHeaders: "The base64-encoded headers of the sts:GetCallerIdentity signed request."
|
iamRequestHeaders: "The base64-encoded headers of the sts:GetCallerIdentity signed request."
|
||||||
|
},
|
||||||
|
REVOKE: {
|
||||||
|
identityId: "The ID of the identity to revoke."
|
||||||
|
}
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const AZURE_AUTH = {
|
||||||
|
REVOKE: {
|
||||||
|
identityId: "The ID of the identity to revoke."
|
||||||
|
}
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const GCP_AUTH = {
|
||||||
|
REVOKE: {
|
||||||
|
identityId: "The ID of the identity to revoke."
|
||||||
|
}
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const KUBERNETES_AUTH = {
|
||||||
|
REVOKE: {
|
||||||
|
identityId: "The ID of the identity to revoke."
|
||||||
}
|
}
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
@ -805,6 +840,8 @@ export const CERTIFICATE_AUTHORITIES = {
|
|||||||
caId: "The ID of the CA to issue the certificate from",
|
caId: "The ID of the CA to issue the certificate from",
|
||||||
friendlyName: "A friendly name for the certificate",
|
friendlyName: "A friendly name for the certificate",
|
||||||
commonName: "The common name (CN) for the certificate",
|
commonName: "The common name (CN) for the certificate",
|
||||||
|
altNames:
|
||||||
|
"A comma-delimited list of Subject Alternative Names (SANs) for the certificate; these can be host names or email addresses.",
|
||||||
ttl: "The time to live for the certificate such as 1m, 1h, 1d, 1y, ...",
|
ttl: "The time to live for the certificate such as 1m, 1h, 1d, 1y, ...",
|
||||||
notBefore: "The date and time when the certificate becomes valid in YYYY-MM-DDTHH:mm:ss.sssZ format",
|
notBefore: "The date and time when the certificate becomes valid in YYYY-MM-DDTHH:mm:ss.sssZ format",
|
||||||
notAfter: "The date and time when the certificate expires in YYYY-MM-DDTHH:mm:ss.sssZ format",
|
notAfter: "The date and time when the certificate expires in YYYY-MM-DDTHH:mm:ss.sssZ format",
|
||||||
|
@ -9,7 +9,10 @@ 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 { CertKeyAlgorithm } from "@app/services/certificate/certificate-types";
|
import { CertKeyAlgorithm } from "@app/services/certificate/certificate-types";
|
||||||
import { CaStatus, CaType } from "@app/services/certificate-authority/certificate-authority-types";
|
import { CaStatus, CaType } from "@app/services/certificate-authority/certificate-authority-types";
|
||||||
import { validateCaDateField } from "@app/services/certificate-authority/certificate-authority-validators";
|
import {
|
||||||
|
validateAltNamesField,
|
||||||
|
validateCaDateField
|
||||||
|
} from "@app/services/certificate-authority/certificate-authority-validators";
|
||||||
|
|
||||||
export const registerCaRouter = async (server: FastifyZodProvider) => {
|
export const registerCaRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
@ -452,6 +455,7 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
|
|||||||
.object({
|
.object({
|
||||||
friendlyName: z.string().optional().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.friendlyName),
|
friendlyName: z.string().optional().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.friendlyName),
|
||||||
commonName: z.string().trim().min(1).describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.commonName),
|
commonName: z.string().trim().min(1).describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.commonName),
|
||||||
|
altNames: validateAltNamesField.describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.altNames),
|
||||||
ttl: z
|
ttl: z
|
||||||
.string()
|
.string()
|
||||||
.refine((val) => ms(val) > 0, "TTL must be a positive number")
|
.refine((val) => ms(val) > 0, "TTL must be a positive number")
|
||||||
|
@ -266,4 +266,51 @@ export const registerIdentityAwsAuthRouter = async (server: FastifyZodProvider)
|
|||||||
return { identityAwsAuth };
|
return { identityAwsAuth };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "DELETE",
|
||||||
|
url: "/aws-auth/identities/:identityId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
schema: {
|
||||||
|
description: "Delete AWS Auth configuration on identity",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
params: z.object({
|
||||||
|
identityId: z.string().describe(AWS_AUTH.REVOKE.identityId)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
identityAwsAuth: IdentityAwsAuthsSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const identityAwsAuth = await server.services.identityAwsAuth.revokeIdentityAwsAuth({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
identityId: req.params.identityId
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: identityAwsAuth.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.REVOKE_IDENTITY_AWS_AUTH,
|
||||||
|
metadata: {
|
||||||
|
identityId: identityAwsAuth.identityId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { identityAwsAuth };
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
@ -2,6 +2,7 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { IdentityAzureAuthsSchema } from "@app/db/schemas";
|
import { IdentityAzureAuthsSchema } from "@app/db/schemas";
|
||||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
|
import { AZURE_AUTH } from "@app/lib/api-docs";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } 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";
|
||||||
@ -259,4 +260,51 @@ export const registerIdentityAzureAuthRouter = async (server: FastifyZodProvider
|
|||||||
return { identityAzureAuth };
|
return { identityAzureAuth };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "DELETE",
|
||||||
|
url: "/azure-auth/identities/:identityId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
schema: {
|
||||||
|
description: "Delete Azure Auth configuration on identity",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
params: z.object({
|
||||||
|
identityId: z.string().describe(AZURE_AUTH.REVOKE.identityId)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
identityAzureAuth: IdentityAzureAuthsSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const identityAzureAuth = await server.services.identityAzureAuth.revokeIdentityAzureAuth({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
identityId: req.params.identityId
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: identityAzureAuth.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.REVOKE_IDENTITY_AZURE_AUTH,
|
||||||
|
metadata: {
|
||||||
|
identityId: identityAzureAuth.identityId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { identityAzureAuth };
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
@ -2,6 +2,7 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { IdentityGcpAuthsSchema } from "@app/db/schemas";
|
import { IdentityGcpAuthsSchema } from "@app/db/schemas";
|
||||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
|
import { GCP_AUTH } from "@app/lib/api-docs";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } 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";
|
||||||
@ -265,4 +266,51 @@ export const registerIdentityGcpAuthRouter = async (server: FastifyZodProvider)
|
|||||||
return { identityGcpAuth };
|
return { identityGcpAuth };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "DELETE",
|
||||||
|
url: "/gcp-auth/identities/:identityId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
schema: {
|
||||||
|
description: "Delete GCP Auth configuration on identity",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
params: z.object({
|
||||||
|
identityId: z.string().describe(GCP_AUTH.REVOKE.identityId)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
identityGcpAuth: IdentityGcpAuthsSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const identityGcpAuth = await server.services.identityGcpAuth.revokeIdentityGcpAuth({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
identityId: req.params.identityId
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: identityGcpAuth.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.REVOKE_IDENTITY_GCP_AUTH,
|
||||||
|
metadata: {
|
||||||
|
identityId: identityGcpAuth.identityId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { identityGcpAuth };
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
@ -2,6 +2,7 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { IdentityKubernetesAuthsSchema } from "@app/db/schemas";
|
import { IdentityKubernetesAuthsSchema } from "@app/db/schemas";
|
||||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
|
import { KUBERNETES_AUTH } from "@app/lib/api-docs";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } 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";
|
||||||
@ -280,4 +281,54 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide
|
|||||||
return { identityKubernetesAuth: IdentityKubernetesAuthResponseSchema.parse(identityKubernetesAuth) };
|
return { identityKubernetesAuth: IdentityKubernetesAuthResponseSchema.parse(identityKubernetesAuth) };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "DELETE",
|
||||||
|
url: "/kubernetes-auth/identities/:identityId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
schema: {
|
||||||
|
description: "Delete Kubernetes Auth configuration on identity",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
params: z.object({
|
||||||
|
identityId: z.string().describe(KUBERNETES_AUTH.REVOKE.identityId)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
identityKubernetesAuth: IdentityKubernetesAuthResponseSchema.omit({
|
||||||
|
caCert: true,
|
||||||
|
tokenReviewerJwt: true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const identityKubernetesAuth = await server.services.identityKubernetesAuth.revokeIdentityKubernetesAuth({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
identityId: req.params.identityId
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: identityKubernetesAuth.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.REVOKE_IDENTITY_KUBERNETES_AUTH,
|
||||||
|
metadata: {
|
||||||
|
identityId: identityKubernetesAuth.identityId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { identityKubernetesAuth };
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { IdentitiesSchema, OrgMembershipRole } from "@app/db/schemas";
|
import { IdentitiesSchema, IdentityOrgMembershipsSchema, OrgMembershipRole, OrgRolesSchema } from "@app/db/schemas";
|
||||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import { IDENTITIES } from "@app/lib/api-docs";
|
import { IDENTITIES } from "@app/lib/api-docs";
|
||||||
import { creationLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { creationLimit, readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
||||||
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";
|
||||||
@ -170,4 +170,94 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
|
|||||||
return { identity };
|
return { identity };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/:identityId",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
schema: {
|
||||||
|
description: "Get an identity by id",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
params: z.object({
|
||||||
|
identityId: z.string().describe(IDENTITIES.GET_BY_ID.identityId)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
identity: IdentityOrgMembershipsSchema.extend({
|
||||||
|
customRole: OrgRolesSchema.pick({
|
||||||
|
id: true,
|
||||||
|
name: true,
|
||||||
|
slug: true,
|
||||||
|
permissions: true,
|
||||||
|
description: true
|
||||||
|
}).optional(),
|
||||||
|
identity: IdentitiesSchema.pick({ name: true, id: true, authMethod: true })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const identity = await server.services.identity.getIdentityById({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
id: req.params.identityId
|
||||||
|
});
|
||||||
|
|
||||||
|
return { identity };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
schema: {
|
||||||
|
description: "List identities",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
querystring: z.object({
|
||||||
|
orgId: z.string().describe(IDENTITIES.LIST.orgId)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
identities: IdentityOrgMembershipsSchema.extend({
|
||||||
|
customRole: OrgRolesSchema.pick({
|
||||||
|
id: true,
|
||||||
|
name: true,
|
||||||
|
slug: true,
|
||||||
|
permissions: true,
|
||||||
|
description: true
|
||||||
|
}).optional(),
|
||||||
|
identity: IdentitiesSchema.pick({ name: true, id: true, authMethod: true })
|
||||||
|
}).array()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const identities = await server.services.identity.listOrgIdentities({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
orgId: req.query.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
return { identities };
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
@ -134,7 +134,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const identityUniversalAuth = await server.services.identityUa.attachUa({
|
const identityUniversalAuth = await server.services.identityUa.attachUniversalAuth({
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
@ -219,7 +219,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const identityUniversalAuth = await server.services.identityUa.updateUa({
|
const identityUniversalAuth = await server.services.identityUa.updateUniversalAuth({
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
@ -272,7 +272,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const identityUniversalAuth = await server.services.identityUa.getIdentityUa({
|
const identityUniversalAuth = await server.services.identityUa.getIdentityUniversalAuth({
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
@ -295,6 +295,53 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "DELETE",
|
||||||
|
url: "/universal-auth/identities/:identityId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
schema: {
|
||||||
|
description: "Delete Universal Auth configuration on identity",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
params: z.object({
|
||||||
|
identityId: z.string().describe(UNIVERSAL_AUTH.REVOKE.identityId)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
identityUniversalAuth: IdentityUniversalAuthsSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const identityUniversalAuth = await server.services.identityUa.revokeIdentityUniversalAuth({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
identityId: req.params.identityId
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: identityUniversalAuth.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.REVOKE_IDENTITY_UNIVERSAL_AUTH,
|
||||||
|
metadata: {
|
||||||
|
identityId: identityUniversalAuth.identityId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { identityUniversalAuth };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
url: "/universal-auth/identities/:identityId/client-secrets",
|
url: "/universal-auth/identities/:identityId/client-secrets",
|
||||||
@ -325,7 +372,8 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const { clientSecret, clientSecretData, orgId } = await server.services.identityUa.createUaClientSecret({
|
const { clientSecret, clientSecretData, orgId } =
|
||||||
|
await server.services.identityUa.createUniversalAuthClientSecret({
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
@ -374,13 +422,15 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const { clientSecrets: clientSecretData, orgId } = await server.services.identityUa.getUaClientSecrets({
|
const { clientSecrets: clientSecretData, orgId } = await server.services.identityUa.getUniversalAuthClientSecrets(
|
||||||
|
{
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
identityId: req.params.identityId
|
identityId: req.params.identityId
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
await server.services.auditLog.createAuditLog({
|
||||||
...req.auditLogInfo,
|
...req.auditLogInfo,
|
||||||
@ -396,6 +446,56 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/universal-auth/identities/:identityId/client-secrets/:clientSecretId",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
schema: {
|
||||||
|
description: "Get Universal Auth Client Secret for identity",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
params: z.object({
|
||||||
|
identityId: z.string().describe(UNIVERSAL_AUTH.GET_CLIENT_SECRET.identityId),
|
||||||
|
clientSecretId: z.string().describe(UNIVERSAL_AUTH.GET_CLIENT_SECRET.clientSecretId)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
clientSecretData: sanitizedClientSecretSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const clientSecretData = await server.services.identityUa.getUniversalAuthClientSecretById({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
identityId: req.params.identityId,
|
||||||
|
clientSecretId: req.params.clientSecretId
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: clientSecretData.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.REVOKE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET,
|
||||||
|
metadata: {
|
||||||
|
identityId: clientSecretData.identityId,
|
||||||
|
clientSecretId: clientSecretData.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { clientSecretData };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
url: "/universal-auth/identities/:identityId/client-secrets/:clientSecretId/revoke",
|
url: "/universal-auth/identities/:identityId/client-secrets/:clientSecretId/revoke",
|
||||||
@ -421,7 +521,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const clientSecretData = await server.services.identityUa.revokeUaClientSecret({
|
const clientSecretData = await server.services.identityUa.revokeUniversalAuthClientSecret({
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
@ -9,7 +9,7 @@ import { registerIdentityAzureAuthRouter } from "./identity-azure-auth-router";
|
|||||||
import { registerIdentityGcpAuthRouter } from "./identity-gcp-auth-router";
|
import { registerIdentityGcpAuthRouter } from "./identity-gcp-auth-router";
|
||||||
import { registerIdentityKubernetesRouter } from "./identity-kubernetes-auth-router";
|
import { registerIdentityKubernetesRouter } from "./identity-kubernetes-auth-router";
|
||||||
import { registerIdentityRouter } from "./identity-router";
|
import { registerIdentityRouter } from "./identity-router";
|
||||||
import { registerIdentityUaRouter } from "./identity-ua";
|
import { registerIdentityUaRouter } from "./identity-universal-auth-router";
|
||||||
import { registerIntegrationAuthRouter } from "./integration-auth-router";
|
import { registerIntegrationAuthRouter } from "./integration-auth-router";
|
||||||
import { registerIntegrationRouter } from "./integration-router";
|
import { registerIntegrationRouter } from "./integration-router";
|
||||||
import { registerInviteOrgRouter } from "./invite-org-router";
|
import { registerInviteOrgRouter } from "./invite-org-router";
|
||||||
|
@ -372,6 +372,44 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "PUT",
|
||||||
|
url: "/:workspaceSlug/audit-logs-retention",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
workspaceSlug: z.string().trim()
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
auditLogsRetentionDays: z.number().min(0)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
message: z.string(),
|
||||||
|
workspace: ProjectsSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const workspace = await server.services.project.updateAuditLogsRetention({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
workspaceSlug: req.params.workspaceSlug,
|
||||||
|
auditLogsRetentionDays: req.body.auditLogsRetentionDays
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
message: "Successfully updated project's audit logs retention period",
|
||||||
|
workspace
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/:workspaceId/integrations",
|
url: "/:workspaceId/integrations",
|
||||||
|
@ -3,6 +3,7 @@ import { ForbiddenError } from "@casl/ability";
|
|||||||
import * as x509 from "@peculiar/x509";
|
import * as x509 from "@peculiar/x509";
|
||||||
import crypto, { KeyObject } from "crypto";
|
import crypto, { KeyObject } from "crypto";
|
||||||
import ms from "ms";
|
import ms from "ms";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
@ -38,6 +39,7 @@ import {
|
|||||||
TSignIntermediateDTO,
|
TSignIntermediateDTO,
|
||||||
TUpdateCaDTO
|
TUpdateCaDTO
|
||||||
} from "./certificate-authority-types";
|
} from "./certificate-authority-types";
|
||||||
|
import { hostnameRegex } from "./certificate-authority-validators";
|
||||||
|
|
||||||
type TCertificateAuthorityServiceFactoryDep = {
|
type TCertificateAuthorityServiceFactoryDep = {
|
||||||
certificateAuthorityDAL: Pick<
|
certificateAuthorityDAL: Pick<
|
||||||
@ -653,6 +655,7 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
caId,
|
caId,
|
||||||
friendlyName,
|
friendlyName,
|
||||||
commonName,
|
commonName,
|
||||||
|
altNames,
|
||||||
ttl,
|
ttl,
|
||||||
notBefore,
|
notBefore,
|
||||||
notAfter,
|
notAfter,
|
||||||
@ -738,6 +741,45 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
kmsService
|
kmsService
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const extensions: x509.Extension[] = [
|
||||||
|
new x509.KeyUsagesExtension(x509.KeyUsageFlags.digitalSignature | x509.KeyUsageFlags.keyEncipherment, true),
|
||||||
|
new x509.BasicConstraintsExtension(false),
|
||||||
|
await x509.AuthorityKeyIdentifierExtension.create(caCertObj, false),
|
||||||
|
await x509.SubjectKeyIdentifierExtension.create(csrObj.publicKey)
|
||||||
|
];
|
||||||
|
|
||||||
|
if (altNames) {
|
||||||
|
const altNamesArray: {
|
||||||
|
type: "email" | "dns";
|
||||||
|
value: string;
|
||||||
|
}[] = altNames
|
||||||
|
.split(",")
|
||||||
|
.map((name) => name.trim())
|
||||||
|
.map((altName) => {
|
||||||
|
// check if the altName is a valid email
|
||||||
|
if (z.string().email().safeParse(altName).success) {
|
||||||
|
return {
|
||||||
|
type: "email",
|
||||||
|
value: altName
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if the altName is a valid hostname
|
||||||
|
if (hostnameRegex.test(altName)) {
|
||||||
|
return {
|
||||||
|
type: "dns",
|
||||||
|
value: altName
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// If altName is neither a valid email nor a valid hostname, throw an error or handle it accordingly
|
||||||
|
throw new Error(`Invalid altName: ${altName}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
const altNamesExtension = new x509.SubjectAlternativeNameExtension(altNamesArray, false);
|
||||||
|
extensions.push(altNamesExtension);
|
||||||
|
}
|
||||||
|
|
||||||
const serialNumber = crypto.randomBytes(32).toString("hex");
|
const serialNumber = crypto.randomBytes(32).toString("hex");
|
||||||
const leafCert = await x509.X509CertificateGenerator.create({
|
const leafCert = await x509.X509CertificateGenerator.create({
|
||||||
serialNumber,
|
serialNumber,
|
||||||
@ -748,12 +790,7 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
signingKey: caPrivateKey,
|
signingKey: caPrivateKey,
|
||||||
publicKey: csrObj.publicKey,
|
publicKey: csrObj.publicKey,
|
||||||
signingAlgorithm: alg,
|
signingAlgorithm: alg,
|
||||||
extensions: [
|
extensions
|
||||||
new x509.KeyUsagesExtension(x509.KeyUsageFlags.digitalSignature | x509.KeyUsageFlags.keyEncipherment, true),
|
|
||||||
new x509.BasicConstraintsExtension(false),
|
|
||||||
await x509.AuthorityKeyIdentifierExtension.create(caCertObj, false),
|
|
||||||
await x509.SubjectKeyIdentifierExtension.create(csrObj.publicKey)
|
|
||||||
]
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const skLeafObj = KeyObject.from(leafKeys.privateKey);
|
const skLeafObj = KeyObject.from(leafKeys.privateKey);
|
||||||
@ -771,6 +808,7 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
status: CertStatus.ACTIVE,
|
status: CertStatus.ACTIVE,
|
||||||
friendlyName: friendlyName || commonName,
|
friendlyName: friendlyName || commonName,
|
||||||
commonName,
|
commonName,
|
||||||
|
altNames,
|
||||||
serialNumber,
|
serialNumber,
|
||||||
notBefore: notBeforeDate,
|
notBefore: notBeforeDate,
|
||||||
notAfter: notAfterDate
|
notAfter: notAfterDate
|
||||||
|
@ -75,6 +75,7 @@ export type TIssueCertFromCaDTO = {
|
|||||||
caId: string;
|
caId: string;
|
||||||
friendlyName?: string;
|
friendlyName?: string;
|
||||||
commonName: string;
|
commonName: string;
|
||||||
|
altNames: string;
|
||||||
ttl: string;
|
ttl: string;
|
||||||
notBefore?: string;
|
notBefore?: string;
|
||||||
notAfter?: string;
|
notAfter?: string;
|
||||||
|
@ -6,3 +6,29 @@ const isValidDate = (dateString: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const validateCaDateField = z.string().trim().refine(isValidDate, { message: "Invalid date format" });
|
export const validateCaDateField = z.string().trim().refine(isValidDate, { message: "Invalid date format" });
|
||||||
|
|
||||||
|
export const hostnameRegex = /^(?!:\/\/)([a-zA-Z0-9-_]{1,63}\.?)+(?!:\/\/)([a-zA-Z]{2,63})$/;
|
||||||
|
export const validateAltNamesField = z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.default("")
|
||||||
|
.transform((data) => {
|
||||||
|
if (data === "") return "";
|
||||||
|
// Trim each alt name and join with ', ' to ensure formatting
|
||||||
|
return data
|
||||||
|
.split(",")
|
||||||
|
.map((id) => id.trim())
|
||||||
|
.join(", ");
|
||||||
|
})
|
||||||
|
.refine(
|
||||||
|
(data) => {
|
||||||
|
if (data === "") return true;
|
||||||
|
// Split and validate each alt name
|
||||||
|
return data.split(", ").every((name) => {
|
||||||
|
return hostnameRegex.test(name) || z.string().email().safeParse(name).success;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: "Each alt name must be a valid hostname or email address"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
@ -7,11 +7,12 @@ import { IdentityAuthMethod } from "@app/db/schemas";
|
|||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
|
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, UnauthorizedError } from "@app/lib/errors";
|
||||||
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
|
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
|
||||||
|
|
||||||
import { AuthTokenType } from "../auth/auth-type";
|
import { ActorType, AuthTokenType } from "../auth/auth-type";
|
||||||
import { TIdentityDALFactory } from "../identity/identity-dal";
|
import { TIdentityDALFactory } from "../identity/identity-dal";
|
||||||
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
|
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
|
||||||
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
|
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
|
||||||
@ -24,12 +25,13 @@ import {
|
|||||||
TGetAwsAuthDTO,
|
TGetAwsAuthDTO,
|
||||||
TGetCallerIdentityResponse,
|
TGetCallerIdentityResponse,
|
||||||
TLoginAwsAuthDTO,
|
TLoginAwsAuthDTO,
|
||||||
|
TRevokeAwsAuthDTO,
|
||||||
TUpdateAwsAuthDTO
|
TUpdateAwsAuthDTO
|
||||||
} from "./identity-aws-auth-types";
|
} from "./identity-aws-auth-types";
|
||||||
|
|
||||||
type TIdentityAwsAuthServiceFactoryDep = {
|
type TIdentityAwsAuthServiceFactoryDep = {
|
||||||
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create">;
|
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create">;
|
||||||
identityAwsAuthDAL: Pick<TIdentityAwsAuthDALFactory, "findOne" | "transaction" | "create" | "updateById">;
|
identityAwsAuthDAL: Pick<TIdentityAwsAuthDALFactory, "findOne" | "transaction" | "create" | "updateById" | "delete">;
|
||||||
identityOrgMembershipDAL: Pick<TIdentityOrgDALFactory, "findOne">;
|
identityOrgMembershipDAL: Pick<TIdentityOrgDALFactory, "findOne">;
|
||||||
identityDAL: Pick<TIdentityDALFactory, "updateById">;
|
identityDAL: Pick<TIdentityDALFactory, "updateById">;
|
||||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
@ -301,10 +303,54 @@ export const identityAwsAuthServiceFactory = ({
|
|||||||
return { ...awsIdentityAuth, orgId: identityMembershipOrg.orgId };
|
return { ...awsIdentityAuth, orgId: identityMembershipOrg.orgId };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const revokeIdentityAwsAuth = async ({
|
||||||
|
identityId,
|
||||||
|
actorId,
|
||||||
|
actor,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
}: TRevokeAwsAuthDTO) => {
|
||||||
|
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
|
||||||
|
if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" });
|
||||||
|
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.AWS_AUTH)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "The identity does not have aws auth"
|
||||||
|
});
|
||||||
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
identityMembershipOrg.orgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity);
|
||||||
|
|
||||||
|
const { permission: rolePermission } = await permissionService.getOrgPermission(
|
||||||
|
ActorType.IDENTITY,
|
||||||
|
identityMembershipOrg.identityId,
|
||||||
|
identityMembershipOrg.orgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
const hasPriviledge = isAtLeastAsPrivileged(permission, rolePermission);
|
||||||
|
if (!hasPriviledge)
|
||||||
|
throw new ForbiddenRequestError({
|
||||||
|
message: "Failed to revoke aws auth of identity with more privileged role"
|
||||||
|
});
|
||||||
|
|
||||||
|
const revokedIdentityAwsAuth = await identityAwsAuthDAL.transaction(async (tx) => {
|
||||||
|
const deletedAwsAuth = await identityAwsAuthDAL.delete({ identityId }, tx);
|
||||||
|
await identityDAL.updateById(identityId, { authMethod: null }, tx);
|
||||||
|
return { ...deletedAwsAuth?.[0], orgId: identityMembershipOrg.orgId };
|
||||||
|
});
|
||||||
|
return revokedIdentityAwsAuth;
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
login,
|
login,
|
||||||
attachAwsAuth,
|
attachAwsAuth,
|
||||||
updateAwsAuth,
|
updateAwsAuth,
|
||||||
getAwsAuth
|
getAwsAuth,
|
||||||
|
revokeIdentityAwsAuth
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -52,3 +52,7 @@ export type TGetCallerIdentityResponse = {
|
|||||||
ResponseMetadata: { RequestId: string };
|
ResponseMetadata: { RequestId: string };
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TRevokeAwsAuthDTO = {
|
||||||
|
identityId: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
@ -5,11 +5,12 @@ import { IdentityAuthMethod } from "@app/db/schemas";
|
|||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
|
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, UnauthorizedError } from "@app/lib/errors";
|
||||||
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
|
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
|
||||||
|
|
||||||
import { AuthTokenType } from "../auth/auth-type";
|
import { ActorType, AuthTokenType } from "../auth/auth-type";
|
||||||
import { TIdentityDALFactory } from "../identity/identity-dal";
|
import { TIdentityDALFactory } from "../identity/identity-dal";
|
||||||
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
|
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
|
||||||
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
|
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
|
||||||
@ -20,11 +21,15 @@ import {
|
|||||||
TAttachAzureAuthDTO,
|
TAttachAzureAuthDTO,
|
||||||
TGetAzureAuthDTO,
|
TGetAzureAuthDTO,
|
||||||
TLoginAzureAuthDTO,
|
TLoginAzureAuthDTO,
|
||||||
|
TRevokeAzureAuthDTO,
|
||||||
TUpdateAzureAuthDTO
|
TUpdateAzureAuthDTO
|
||||||
} from "./identity-azure-auth-types";
|
} from "./identity-azure-auth-types";
|
||||||
|
|
||||||
type TIdentityAzureAuthServiceFactoryDep = {
|
type TIdentityAzureAuthServiceFactoryDep = {
|
||||||
identityAzureAuthDAL: Pick<TIdentityAzureAuthDALFactory, "findOne" | "transaction" | "create" | "updateById">;
|
identityAzureAuthDAL: Pick<
|
||||||
|
TIdentityAzureAuthDALFactory,
|
||||||
|
"findOne" | "transaction" | "create" | "updateById" | "delete"
|
||||||
|
>;
|
||||||
identityOrgMembershipDAL: Pick<TIdentityOrgDALFactory, "findOne">;
|
identityOrgMembershipDAL: Pick<TIdentityOrgDALFactory, "findOne">;
|
||||||
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create">;
|
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create">;
|
||||||
identityDAL: Pick<TIdentityDALFactory, "updateById">;
|
identityDAL: Pick<TIdentityDALFactory, "updateById">;
|
||||||
@ -277,10 +282,54 @@ export const identityAzureAuthServiceFactory = ({
|
|||||||
return { ...identityAzureAuth, orgId: identityMembershipOrg.orgId };
|
return { ...identityAzureAuth, orgId: identityMembershipOrg.orgId };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const revokeIdentityAzureAuth = async ({
|
||||||
|
identityId,
|
||||||
|
actorId,
|
||||||
|
actor,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
}: TRevokeAzureAuthDTO) => {
|
||||||
|
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
|
||||||
|
if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" });
|
||||||
|
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.AZURE_AUTH)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "The identity does not have azure auth"
|
||||||
|
});
|
||||||
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
identityMembershipOrg.orgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity);
|
||||||
|
|
||||||
|
const { permission: rolePermission } = await permissionService.getOrgPermission(
|
||||||
|
ActorType.IDENTITY,
|
||||||
|
identityMembershipOrg.identityId,
|
||||||
|
identityMembershipOrg.orgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
const hasPriviledge = isAtLeastAsPrivileged(permission, rolePermission);
|
||||||
|
if (!hasPriviledge)
|
||||||
|
throw new ForbiddenRequestError({
|
||||||
|
message: "Failed to revoke azure auth of identity with more privileged role"
|
||||||
|
});
|
||||||
|
|
||||||
|
const revokedIdentityAzureAuth = await identityAzureAuthDAL.transaction(async (tx) => {
|
||||||
|
const deletedAzureAuth = await identityAzureAuthDAL.delete({ identityId }, tx);
|
||||||
|
await identityDAL.updateById(identityId, { authMethod: null }, tx);
|
||||||
|
return { ...deletedAzureAuth?.[0], orgId: identityMembershipOrg.orgId };
|
||||||
|
});
|
||||||
|
return revokedIdentityAzureAuth;
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
login,
|
login,
|
||||||
attachAzureAuth,
|
attachAzureAuth,
|
||||||
updateAzureAuth,
|
updateAzureAuth,
|
||||||
getAzureAuth
|
getAzureAuth,
|
||||||
|
revokeIdentityAzureAuth
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -118,3 +118,7 @@ export type TDecodedAzureAuthJwt = {
|
|||||||
[key: string]: string;
|
[key: string]: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TRevokeAzureAuthDTO = {
|
||||||
|
identityId: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
@ -5,11 +5,12 @@ import { IdentityAuthMethod } from "@app/db/schemas";
|
|||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
|
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, UnauthorizedError } from "@app/lib/errors";
|
||||||
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
|
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
|
||||||
|
|
||||||
import { AuthTokenType } from "../auth/auth-type";
|
import { ActorType, AuthTokenType } from "../auth/auth-type";
|
||||||
import { TIdentityDALFactory } from "../identity/identity-dal";
|
import { TIdentityDALFactory } from "../identity/identity-dal";
|
||||||
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
|
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
|
||||||
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
|
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
|
||||||
@ -21,11 +22,12 @@ import {
|
|||||||
TGcpIdentityDetails,
|
TGcpIdentityDetails,
|
||||||
TGetGcpAuthDTO,
|
TGetGcpAuthDTO,
|
||||||
TLoginGcpAuthDTO,
|
TLoginGcpAuthDTO,
|
||||||
|
TRevokeGcpAuthDTO,
|
||||||
TUpdateGcpAuthDTO
|
TUpdateGcpAuthDTO
|
||||||
} from "./identity-gcp-auth-types";
|
} from "./identity-gcp-auth-types";
|
||||||
|
|
||||||
type TIdentityGcpAuthServiceFactoryDep = {
|
type TIdentityGcpAuthServiceFactoryDep = {
|
||||||
identityGcpAuthDAL: Pick<TIdentityGcpAuthDALFactory, "findOne" | "transaction" | "create" | "updateById">;
|
identityGcpAuthDAL: Pick<TIdentityGcpAuthDALFactory, "findOne" | "transaction" | "create" | "updateById" | "delete">;
|
||||||
identityOrgMembershipDAL: Pick<TIdentityOrgDALFactory, "findOne">;
|
identityOrgMembershipDAL: Pick<TIdentityOrgDALFactory, "findOne">;
|
||||||
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create">;
|
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create">;
|
||||||
identityDAL: Pick<TIdentityDALFactory, "updateById">;
|
identityDAL: Pick<TIdentityDALFactory, "updateById">;
|
||||||
@ -315,10 +317,54 @@ export const identityGcpAuthServiceFactory = ({
|
|||||||
return { ...identityGcpAuth, orgId: identityMembershipOrg.orgId };
|
return { ...identityGcpAuth, orgId: identityMembershipOrg.orgId };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const revokeIdentityGcpAuth = async ({
|
||||||
|
identityId,
|
||||||
|
actorId,
|
||||||
|
actor,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
}: TRevokeGcpAuthDTO) => {
|
||||||
|
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
|
||||||
|
if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" });
|
||||||
|
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.GCP_AUTH)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "The identity does not have gcp auth"
|
||||||
|
});
|
||||||
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
identityMembershipOrg.orgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity);
|
||||||
|
|
||||||
|
const { permission: rolePermission } = await permissionService.getOrgPermission(
|
||||||
|
ActorType.IDENTITY,
|
||||||
|
identityMembershipOrg.identityId,
|
||||||
|
identityMembershipOrg.orgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
const hasPriviledge = isAtLeastAsPrivileged(permission, rolePermission);
|
||||||
|
if (!hasPriviledge)
|
||||||
|
throw new ForbiddenRequestError({
|
||||||
|
message: "Failed to revoke gcp auth of identity with more privileged role"
|
||||||
|
});
|
||||||
|
|
||||||
|
const revokedIdentityGcpAuth = await identityGcpAuthDAL.transaction(async (tx) => {
|
||||||
|
const deletedGcpAuth = await identityGcpAuthDAL.delete({ identityId }, tx);
|
||||||
|
await identityDAL.updateById(identityId, { authMethod: null }, tx);
|
||||||
|
return { ...deletedGcpAuth?.[0], orgId: identityMembershipOrg.orgId };
|
||||||
|
});
|
||||||
|
return revokedIdentityGcpAuth;
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
login,
|
login,
|
||||||
attachGcpAuth,
|
attachGcpAuth,
|
||||||
updateGcpAuth,
|
updateGcpAuth,
|
||||||
getGcpAuth
|
getGcpAuth,
|
||||||
|
revokeIdentityGcpAuth
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -76,3 +76,7 @@ export type TDecodedGcpIamAuthJwt = {
|
|||||||
[key: string]: string;
|
[key: string]: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TRevokeGcpAuthDTO = {
|
||||||
|
identityId: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
@ -7,6 +7,7 @@ import { IdentityAuthMethod, SecretKeyEncoding, TIdentityKubernetesAuthsUpdate }
|
|||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
|
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import {
|
import {
|
||||||
decryptSymmetric,
|
decryptSymmetric,
|
||||||
@ -16,11 +17,11 @@ import {
|
|||||||
infisicalSymmetricDecrypt,
|
infisicalSymmetricDecrypt,
|
||||||
infisicalSymmetricEncypt
|
infisicalSymmetricEncypt
|
||||||
} from "@app/lib/crypto/encryption";
|
} from "@app/lib/crypto/encryption";
|
||||||
import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, UnauthorizedError } from "@app/lib/errors";
|
||||||
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
|
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
|
||||||
import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal";
|
import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal";
|
||||||
|
|
||||||
import { AuthTokenType } from "../auth/auth-type";
|
import { ActorType, AuthTokenType } from "../auth/auth-type";
|
||||||
import { TIdentityDALFactory } from "../identity/identity-dal";
|
import { TIdentityDALFactory } from "../identity/identity-dal";
|
||||||
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
|
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
|
||||||
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
|
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
|
||||||
@ -32,13 +33,14 @@ import {
|
|||||||
TCreateTokenReviewResponse,
|
TCreateTokenReviewResponse,
|
||||||
TGetKubernetesAuthDTO,
|
TGetKubernetesAuthDTO,
|
||||||
TLoginKubernetesAuthDTO,
|
TLoginKubernetesAuthDTO,
|
||||||
|
TRevokeKubernetesAuthDTO,
|
||||||
TUpdateKubernetesAuthDTO
|
TUpdateKubernetesAuthDTO
|
||||||
} from "./identity-kubernetes-auth-types";
|
} from "./identity-kubernetes-auth-types";
|
||||||
|
|
||||||
type TIdentityKubernetesAuthServiceFactoryDep = {
|
type TIdentityKubernetesAuthServiceFactoryDep = {
|
||||||
identityKubernetesAuthDAL: Pick<
|
identityKubernetesAuthDAL: Pick<
|
||||||
TIdentityKubernetesAuthDALFactory,
|
TIdentityKubernetesAuthDALFactory,
|
||||||
"create" | "findOne" | "transaction" | "updateById"
|
"create" | "findOne" | "transaction" | "updateById" | "delete"
|
||||||
>;
|
>;
|
||||||
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create">;
|
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create">;
|
||||||
identityOrgMembershipDAL: Pick<TIdentityOrgDALFactory, "findOne" | "findById">;
|
identityOrgMembershipDAL: Pick<TIdentityOrgDALFactory, "findOne" | "findById">;
|
||||||
@ -533,10 +535,54 @@ export const identityKubernetesAuthServiceFactory = ({
|
|||||||
return { ...identityKubernetesAuth, caCert, tokenReviewerJwt, orgId: identityMembershipOrg.orgId };
|
return { ...identityKubernetesAuth, caCert, tokenReviewerJwt, orgId: identityMembershipOrg.orgId };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const revokeIdentityKubernetesAuth = async ({
|
||||||
|
identityId,
|
||||||
|
actorId,
|
||||||
|
actor,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
}: TRevokeKubernetesAuthDTO) => {
|
||||||
|
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
|
||||||
|
if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" });
|
||||||
|
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.KUBERNETES_AUTH)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "The identity does not have kubenetes auth"
|
||||||
|
});
|
||||||
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
identityMembershipOrg.orgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity);
|
||||||
|
|
||||||
|
const { permission: rolePermission } = await permissionService.getOrgPermission(
|
||||||
|
ActorType.IDENTITY,
|
||||||
|
identityMembershipOrg.identityId,
|
||||||
|
identityMembershipOrg.orgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
const hasPriviledge = isAtLeastAsPrivileged(permission, rolePermission);
|
||||||
|
if (!hasPriviledge)
|
||||||
|
throw new ForbiddenRequestError({
|
||||||
|
message: "Failed to revoke kubenetes auth of identity with more privileged role"
|
||||||
|
});
|
||||||
|
|
||||||
|
const revokedIdentityKubernetesAuth = await identityKubernetesAuthDAL.transaction(async (tx) => {
|
||||||
|
const deletedKubernetesAuth = await identityKubernetesAuthDAL.delete({ identityId }, tx);
|
||||||
|
await identityDAL.updateById(identityId, { authMethod: null }, tx);
|
||||||
|
return { ...deletedKubernetesAuth?.[0], orgId: identityMembershipOrg.orgId };
|
||||||
|
});
|
||||||
|
return revokedIdentityKubernetesAuth;
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
login,
|
login,
|
||||||
attachKubernetesAuth,
|
attachKubernetesAuth,
|
||||||
updateKubernetesAuth,
|
updateKubernetesAuth,
|
||||||
getKubernetesAuth
|
getKubernetesAuth,
|
||||||
|
revokeIdentityKubernetesAuth
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -59,3 +59,7 @@ export type TCreateTokenReviewResponse = {
|
|||||||
};
|
};
|
||||||
status: TCreateTokenReviewSuccessResponse | TCreateTokenReviewErrorResponse;
|
status: TCreateTokenReviewSuccessResponse | TCreateTokenReviewErrorResponse;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TRevokeKubernetesAuthDTO = {
|
||||||
|
identityId: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
@ -25,7 +25,9 @@ import {
|
|||||||
TCreateUaClientSecretDTO,
|
TCreateUaClientSecretDTO,
|
||||||
TGetUaClientSecretsDTO,
|
TGetUaClientSecretsDTO,
|
||||||
TGetUaDTO,
|
TGetUaDTO,
|
||||||
|
TGetUniversalAuthClientSecretByIdDTO,
|
||||||
TRevokeUaClientSecretDTO,
|
TRevokeUaClientSecretDTO,
|
||||||
|
TRevokeUaDTO,
|
||||||
TUpdateUaDTO
|
TUpdateUaDTO
|
||||||
} from "./identity-ua-types";
|
} from "./identity-ua-types";
|
||||||
|
|
||||||
@ -136,7 +138,7 @@ export const identityUaServiceFactory = ({
|
|||||||
return { accessToken, identityUa, validClientSecretInfo, identityAccessToken, identityMembershipOrg };
|
return { accessToken, identityUa, validClientSecretInfo, identityAccessToken, identityMembershipOrg };
|
||||||
};
|
};
|
||||||
|
|
||||||
const attachUa = async ({
|
const attachUniversalAuth = async ({
|
||||||
accessTokenMaxTTL,
|
accessTokenMaxTTL,
|
||||||
identityId,
|
identityId,
|
||||||
accessTokenNumUsesLimit,
|
accessTokenNumUsesLimit,
|
||||||
@ -227,7 +229,7 @@ export const identityUaServiceFactory = ({
|
|||||||
return { ...identityUa, orgId: identityMembershipOrg.orgId };
|
return { ...identityUa, orgId: identityMembershipOrg.orgId };
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateUa = async ({
|
const updateUniversalAuth = async ({
|
||||||
accessTokenMaxTTL,
|
accessTokenMaxTTL,
|
||||||
identityId,
|
identityId,
|
||||||
accessTokenNumUsesLimit,
|
accessTokenNumUsesLimit,
|
||||||
@ -312,7 +314,7 @@ export const identityUaServiceFactory = ({
|
|||||||
return { ...updatedUaAuth, orgId: identityMembershipOrg.orgId };
|
return { ...updatedUaAuth, orgId: identityMembershipOrg.orgId };
|
||||||
};
|
};
|
||||||
|
|
||||||
const getIdentityUa = async ({ identityId, actorId, actor, actorAuthMethod, actorOrgId }: TGetUaDTO) => {
|
const getIdentityUniversalAuth = async ({ identityId, actorId, actor, actorAuthMethod, actorOrgId }: TGetUaDTO) => {
|
||||||
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
|
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
|
||||||
if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" });
|
if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" });
|
||||||
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.Univeral)
|
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.Univeral)
|
||||||
@ -333,7 +335,50 @@ export const identityUaServiceFactory = ({
|
|||||||
return { ...uaIdentityAuth, orgId: identityMembershipOrg.orgId };
|
return { ...uaIdentityAuth, orgId: identityMembershipOrg.orgId };
|
||||||
};
|
};
|
||||||
|
|
||||||
const createUaClientSecret = async ({
|
const revokeIdentityUniversalAuth = async ({
|
||||||
|
identityId,
|
||||||
|
actorId,
|
||||||
|
actor,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
}: TRevokeUaDTO) => {
|
||||||
|
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
|
||||||
|
if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" });
|
||||||
|
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.Univeral)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "The identity does not have universal auth"
|
||||||
|
});
|
||||||
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
identityMembershipOrg.orgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity);
|
||||||
|
|
||||||
|
const { permission: rolePermission } = await permissionService.getOrgPermission(
|
||||||
|
ActorType.IDENTITY,
|
||||||
|
identityMembershipOrg.identityId,
|
||||||
|
identityMembershipOrg.orgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
const hasPriviledge = isAtLeastAsPrivileged(permission, rolePermission);
|
||||||
|
if (!hasPriviledge)
|
||||||
|
throw new ForbiddenRequestError({
|
||||||
|
message: "Failed to revoke universal auth of identity with more privileged role"
|
||||||
|
});
|
||||||
|
|
||||||
|
const revokedIdentityUniversalAuth = await identityUaDAL.transaction(async (tx) => {
|
||||||
|
const deletedUniversalAuth = await identityUaDAL.delete({ identityId }, tx);
|
||||||
|
await identityDAL.updateById(identityId, { authMethod: null }, tx);
|
||||||
|
return { ...deletedUniversalAuth?.[0], orgId: identityMembershipOrg.orgId };
|
||||||
|
});
|
||||||
|
return revokedIdentityUniversalAuth;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createUniversalAuthClientSecret = async ({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
actorOrgId,
|
actorOrgId,
|
||||||
@ -396,7 +441,7 @@ export const identityUaServiceFactory = ({
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const getUaClientSecrets = async ({
|
const getUniversalAuthClientSecrets = async ({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
actorOrgId,
|
actorOrgId,
|
||||||
@ -442,7 +487,47 @@ export const identityUaServiceFactory = ({
|
|||||||
return { clientSecrets, orgId: identityMembershipOrg.orgId };
|
return { clientSecrets, orgId: identityMembershipOrg.orgId };
|
||||||
};
|
};
|
||||||
|
|
||||||
const revokeUaClientSecret = async ({
|
const getUniversalAuthClientSecretById = async ({
|
||||||
|
identityId,
|
||||||
|
actorId,
|
||||||
|
actor,
|
||||||
|
actorOrgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
clientSecretId
|
||||||
|
}: TGetUniversalAuthClientSecretByIdDTO) => {
|
||||||
|
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
|
||||||
|
if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" });
|
||||||
|
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.Univeral)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "The identity does not have universal auth"
|
||||||
|
});
|
||||||
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
identityMembershipOrg.orgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity);
|
||||||
|
|
||||||
|
const { permission: rolePermission } = await permissionService.getOrgPermission(
|
||||||
|
ActorType.IDENTITY,
|
||||||
|
identityMembershipOrg.identityId,
|
||||||
|
identityMembershipOrg.orgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
const hasPriviledge = isAtLeastAsPrivileged(permission, rolePermission);
|
||||||
|
if (!hasPriviledge)
|
||||||
|
throw new ForbiddenRequestError({
|
||||||
|
message: "Failed to read identity client secret of project with more privileged role"
|
||||||
|
});
|
||||||
|
|
||||||
|
const clientSecret = await identityUaClientSecretDAL.findById(clientSecretId);
|
||||||
|
return { ...clientSecret, identityId, orgId: identityMembershipOrg.orgId };
|
||||||
|
};
|
||||||
|
|
||||||
|
const revokeUniversalAuthClientSecret = async ({
|
||||||
identityId,
|
identityId,
|
||||||
actorId,
|
actorId,
|
||||||
actor,
|
actor,
|
||||||
@ -475,7 +560,7 @@ export const identityUaServiceFactory = ({
|
|||||||
const hasPriviledge = isAtLeastAsPrivileged(permission, rolePermission);
|
const hasPriviledge = isAtLeastAsPrivileged(permission, rolePermission);
|
||||||
if (!hasPriviledge)
|
if (!hasPriviledge)
|
||||||
throw new ForbiddenRequestError({
|
throw new ForbiddenRequestError({
|
||||||
message: "Failed to add identity to project with more privileged role"
|
message: "Failed to revoke identity client secret with more privileged role"
|
||||||
});
|
});
|
||||||
|
|
||||||
const clientSecret = await identityUaClientSecretDAL.updateById(clientSecretId, {
|
const clientSecret = await identityUaClientSecretDAL.updateById(clientSecretId, {
|
||||||
@ -486,11 +571,13 @@ export const identityUaServiceFactory = ({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
login,
|
login,
|
||||||
attachUa,
|
attachUniversalAuth,
|
||||||
updateUa,
|
updateUniversalAuth,
|
||||||
getIdentityUa,
|
getIdentityUniversalAuth,
|
||||||
createUaClientSecret,
|
revokeIdentityUniversalAuth,
|
||||||
getUaClientSecrets,
|
createUniversalAuthClientSecret,
|
||||||
revokeUaClientSecret
|
getUniversalAuthClientSecrets,
|
||||||
|
revokeUniversalAuthClientSecret,
|
||||||
|
getUniversalAuthClientSecretById
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -22,6 +22,10 @@ export type TGetUaDTO = {
|
|||||||
identityId: string;
|
identityId: string;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TRevokeUaDTO = {
|
||||||
|
identityId: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TCreateUaClientSecretDTO = {
|
export type TCreateUaClientSecretDTO = {
|
||||||
identityId: string;
|
identityId: string;
|
||||||
description: string;
|
description: string;
|
||||||
@ -37,3 +41,8 @@ export type TRevokeUaClientSecretDTO = {
|
|||||||
identityId: string;
|
identityId: string;
|
||||||
clientSecretId: string;
|
clientSecretId: string;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TGetUniversalAuthClientSecretByIdDTO = {
|
||||||
|
identityId: string;
|
||||||
|
clientSecretId: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
@ -27,10 +27,10 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const findByOrgId = async (orgId: string, tx?: Knex) => {
|
const find = async (filter: Partial<TIdentityOrgMemberships>, tx?: Knex) => {
|
||||||
try {
|
try {
|
||||||
const docs = await (tx || db)(TableName.IdentityOrgMembership)
|
const docs = await (tx || db)(TableName.IdentityOrgMembership)
|
||||||
.where(`${TableName.IdentityOrgMembership}.orgId`, orgId)
|
.where(filter)
|
||||||
.join(TableName.Identity, `${TableName.IdentityOrgMembership}.identityId`, `${TableName.Identity}.id`)
|
.join(TableName.Identity, `${TableName.IdentityOrgMembership}.identityId`, `${TableName.Identity}.id`)
|
||||||
.leftJoin(TableName.OrgRoles, `${TableName.IdentityOrgMembership}.roleId`, `${TableName.OrgRoles}.id`)
|
.leftJoin(TableName.OrgRoles, `${TableName.IdentityOrgMembership}.roleId`, `${TableName.OrgRoles}.id`)
|
||||||
.select(selectAllTableCols(TableName.IdentityOrgMembership))
|
.select(selectAllTableCols(TableName.IdentityOrgMembership))
|
||||||
@ -79,5 +79,5 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return { ...identityOrgOrm, findOne, findByOrgId };
|
return { ...identityOrgOrm, find, findOne };
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
|
||||||
import { OrgMembershipRole, TOrgRoles } from "@app/db/schemas";
|
import { OrgMembershipRole, TableName, TOrgRoles } from "@app/db/schemas";
|
||||||
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
||||||
@ -10,7 +10,7 @@ import { TOrgPermission } from "@app/lib/types";
|
|||||||
import { ActorType } from "../auth/auth-type";
|
import { ActorType } from "../auth/auth-type";
|
||||||
import { TIdentityDALFactory } from "./identity-dal";
|
import { TIdentityDALFactory } from "./identity-dal";
|
||||||
import { TIdentityOrgDALFactory } from "./identity-org-dal";
|
import { TIdentityOrgDALFactory } from "./identity-org-dal";
|
||||||
import { TCreateIdentityDTO, TDeleteIdentityDTO, TUpdateIdentityDTO } from "./identity-types";
|
import { TCreateIdentityDTO, TDeleteIdentityDTO, TGetIdentityByIdDTO, TUpdateIdentityDTO } from "./identity-types";
|
||||||
|
|
||||||
type TIdentityServiceFactoryDep = {
|
type TIdentityServiceFactoryDep = {
|
||||||
identityDAL: TIdentityDALFactory;
|
identityDAL: TIdentityDALFactory;
|
||||||
@ -126,6 +126,24 @@ export const identityServiceFactory = ({
|
|||||||
return { ...identity, orgId: identityOrgMembership.orgId };
|
return { ...identity, orgId: identityOrgMembership.orgId };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getIdentityById = async ({ id, actor, actorId, actorOrgId, actorAuthMethod }: TGetIdentityByIdDTO) => {
|
||||||
|
const doc = await identityOrgMembershipDAL.find({
|
||||||
|
[`${TableName.IdentityOrgMembership}.identityId` as "identityId"]: id
|
||||||
|
});
|
||||||
|
const identity = doc[0];
|
||||||
|
if (!identity) throw new BadRequestError({ message: `Failed to find identity with id ${id}` });
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
identity.orgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity);
|
||||||
|
return identity;
|
||||||
|
};
|
||||||
|
|
||||||
const deleteIdentity = async ({ actorId, actor, actorOrgId, actorAuthMethod, id }: TDeleteIdentityDTO) => {
|
const deleteIdentity = async ({ actorId, actor, actorOrgId, actorAuthMethod, id }: TDeleteIdentityDTO) => {
|
||||||
const identityOrgMembership = await identityOrgMembershipDAL.findOne({ identityId: id });
|
const identityOrgMembership = await identityOrgMembershipDAL.findOne({ identityId: id });
|
||||||
if (!identityOrgMembership) throw new BadRequestError({ message: `Failed to find identity with id ${id}` });
|
if (!identityOrgMembership) throw new BadRequestError({ message: `Failed to find identity with id ${id}` });
|
||||||
@ -157,7 +175,9 @@ export const identityServiceFactory = ({
|
|||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity);
|
||||||
|
|
||||||
const identityMemberships = await identityOrgMembershipDAL.findByOrgId(orgId);
|
const identityMemberships = await identityOrgMembershipDAL.find({
|
||||||
|
[`${TableName.IdentityOrgMembership}.orgId` as "orgId"]: orgId
|
||||||
|
});
|
||||||
return identityMemberships;
|
return identityMemberships;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -165,6 +185,7 @@ export const identityServiceFactory = ({
|
|||||||
createIdentity,
|
createIdentity,
|
||||||
updateIdentity,
|
updateIdentity,
|
||||||
deleteIdentity,
|
deleteIdentity,
|
||||||
listOrgIdentities
|
listOrgIdentities,
|
||||||
|
getIdentityById
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -16,6 +16,10 @@ export type TDeleteIdentityDTO = {
|
|||||||
id: string;
|
id: string;
|
||||||
} & Omit<TOrgPermission, "orgId">;
|
} & Omit<TOrgPermission, "orgId">;
|
||||||
|
|
||||||
|
export type TGetIdentityByIdDTO = {
|
||||||
|
id: string;
|
||||||
|
} & Omit<TOrgPermission, "orgId">;
|
||||||
|
|
||||||
export interface TIdentityTrustedIp {
|
export interface TIdentityTrustedIp {
|
||||||
ipAddress: string;
|
ipAddress: string;
|
||||||
type: IPType;
|
type: IPType;
|
||||||
|
@ -257,14 +257,27 @@ const syncSecretsGCPSecretManager = async ({
|
|||||||
const syncSecretsAzureKeyVault = async ({
|
const syncSecretsAzureKeyVault = async ({
|
||||||
integration,
|
integration,
|
||||||
secrets,
|
secrets,
|
||||||
accessToken
|
accessToken,
|
||||||
|
createManySecretsRawFn,
|
||||||
|
updateManySecretsRawFn
|
||||||
}: {
|
}: {
|
||||||
integration: TIntegrations;
|
integration: TIntegrations & {
|
||||||
|
projectId: string;
|
||||||
|
environment: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
};
|
||||||
|
secretPath: string;
|
||||||
|
};
|
||||||
secrets: Record<string, { value: string; comment?: string }>;
|
secrets: Record<string, { value: string; comment?: string }>;
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
|
createManySecretsRawFn: (params: TCreateManySecretsRawFn) => Promise<Array<TSecrets & { _id: string }>>;
|
||||||
|
updateManySecretsRawFn: (params: TUpdateManySecretsRawFn) => Promise<Array<TSecrets & { _id: string }>>;
|
||||||
}) => {
|
}) => {
|
||||||
interface GetAzureKeyVaultSecret {
|
interface GetAzureKeyVaultSecret {
|
||||||
id: string; // secret URI
|
id: string; // secret URI
|
||||||
|
value: string;
|
||||||
attributes: {
|
attributes: {
|
||||||
enabled: true;
|
enabled: true;
|
||||||
created: number;
|
created: number;
|
||||||
@ -361,6 +374,83 @@ const syncSecretsAzureKeyVault = async ({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const secretsToAdd: { [key: string]: string } = {};
|
||||||
|
const secretsToUpdate: { [key: string]: string } = {};
|
||||||
|
const secretKeysToRemoveFromDelete = new Set<string>();
|
||||||
|
|
||||||
|
const metadata = IntegrationMetadataSchema.parse(integration.metadata);
|
||||||
|
if (!integration.lastUsed) {
|
||||||
|
Object.keys(res).forEach((key) => {
|
||||||
|
// first time using integration
|
||||||
|
const underscoredKey = key.replace(/-/g, "_");
|
||||||
|
|
||||||
|
// -> apply initial sync behavior
|
||||||
|
switch (metadata.initialSyncBehavior) {
|
||||||
|
case IntegrationInitialSyncBehavior.PREFER_TARGET: {
|
||||||
|
if (!(underscoredKey in secrets)) {
|
||||||
|
secretsToAdd[underscoredKey] = res[key].value;
|
||||||
|
setSecrets.push({
|
||||||
|
key,
|
||||||
|
value: res[key].value
|
||||||
|
});
|
||||||
|
} else if (secrets[underscoredKey]?.value !== res[key].value) {
|
||||||
|
secretsToUpdate[underscoredKey] = res[key].value;
|
||||||
|
const toEditSecretIndex = setSecrets.findIndex((secret) => secret.key === key);
|
||||||
|
if (toEditSecretIndex >= 0) {
|
||||||
|
setSecrets[toEditSecretIndex].value = res[key].value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
secretKeysToRemoveFromDelete.add(key);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case IntegrationInitialSyncBehavior.PREFER_SOURCE: {
|
||||||
|
if (!(underscoredKey in secrets)) {
|
||||||
|
secretsToAdd[underscoredKey] = res[key].value;
|
||||||
|
setSecrets.push({
|
||||||
|
key,
|
||||||
|
value: res[key].value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
secretKeysToRemoveFromDelete.add(key);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(secretsToUpdate).length) {
|
||||||
|
await updateManySecretsRawFn({
|
||||||
|
projectId: integration.projectId,
|
||||||
|
environment: integration.environment.slug,
|
||||||
|
path: integration.secretPath,
|
||||||
|
secrets: Object.keys(secretsToUpdate).map((key) => ({
|
||||||
|
secretName: key,
|
||||||
|
secretValue: secretsToUpdate[key],
|
||||||
|
type: SecretType.Shared,
|
||||||
|
secretComment: ""
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(secretsToAdd).length) {
|
||||||
|
await createManySecretsRawFn({
|
||||||
|
projectId: integration.projectId,
|
||||||
|
environment: integration.environment.slug,
|
||||||
|
path: integration.secretPath,
|
||||||
|
secrets: Object.keys(secretsToAdd).map((key) => ({
|
||||||
|
secretName: key,
|
||||||
|
secretValue: secretsToAdd[key],
|
||||||
|
type: SecretType.Shared,
|
||||||
|
secretComment: ""
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const setSecretAzureKeyVault = async ({
|
const setSecretAzureKeyVault = async ({
|
||||||
key,
|
key,
|
||||||
value,
|
value,
|
||||||
@ -428,7 +518,7 @@ const syncSecretsAzureKeyVault = async ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
for await (const deleteSecret of deleteSecrets) {
|
for await (const deleteSecret of deleteSecrets.filter((secret) => !secretKeysToRemoveFromDelete.has(secret.key))) {
|
||||||
const { key } = deleteSecret;
|
const { key } = deleteSecret;
|
||||||
await request.delete(`${integration.app}/secrets/${key}?api-version=7.3`, {
|
await request.delete(`${integration.app}/secrets/${key}?api-version=7.3`, {
|
||||||
headers: {
|
headers: {
|
||||||
@ -3512,7 +3602,9 @@ export const syncIntegrationSecrets = async ({
|
|||||||
await syncSecretsAzureKeyVault({
|
await syncSecretsAzureKeyVault({
|
||||||
integration,
|
integration,
|
||||||
secrets,
|
secrets,
|
||||||
accessToken
|
accessToken,
|
||||||
|
createManySecretsRawFn,
|
||||||
|
updateManySecretsRawFn
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case Integrations.AWS_PARAMETER_STORE:
|
case Integrations.AWS_PARAMETER_STORE:
|
||||||
|
@ -11,7 +11,7 @@ import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
|||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { createSecretBlindIndex } from "@app/lib/crypto";
|
import { createSecretBlindIndex } from "@app/lib/crypto";
|
||||||
import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
||||||
import { BadRequestError, ForbiddenRequestError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
import { TProjectPermission } from "@app/lib/types";
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
|
|
||||||
@ -41,6 +41,7 @@ import {
|
|||||||
TListProjectCasDTO,
|
TListProjectCasDTO,
|
||||||
TListProjectCertsDTO,
|
TListProjectCertsDTO,
|
||||||
TToggleProjectAutoCapitalizationDTO,
|
TToggleProjectAutoCapitalizationDTO,
|
||||||
|
TUpdateAuditLogsRetentionDTO,
|
||||||
TUpdateProjectDTO,
|
TUpdateProjectDTO,
|
||||||
TUpdateProjectNameDTO,
|
TUpdateProjectNameDTO,
|
||||||
TUpdateProjectVersionLimitDTO,
|
TUpdateProjectVersionLimitDTO,
|
||||||
@ -446,6 +447,43 @@ export const projectServiceFactory = ({
|
|||||||
return projectDAL.updateById(project.id, { pitVersionLimit });
|
return projectDAL.updateById(project.id, { pitVersionLimit });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updateAuditLogsRetention = async ({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorOrgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
auditLogsRetentionDays,
|
||||||
|
workspaceSlug
|
||||||
|
}: TUpdateAuditLogsRetentionDTO) => {
|
||||||
|
const project = await projectDAL.findProjectBySlug(workspaceSlug, actorOrgId);
|
||||||
|
if (!project) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: "Project not found."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { hasRole } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
project.id,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!hasRole(ProjectMembershipRole.Admin)) {
|
||||||
|
throw new BadRequestError({ message: "Only admins are allowed to take this action" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const plan = await licenseService.getPlan(project.orgId);
|
||||||
|
if (!plan.auditLogs || auditLogsRetentionDays > plan.auditLogsRetentionDays) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Failed to update audit logs retention due to plan limit reached. Upgrade plan to increase."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return projectDAL.updateById(project.id, { auditLogsRetentionDays });
|
||||||
|
};
|
||||||
|
|
||||||
const updateName = async ({
|
const updateName = async ({
|
||||||
projectId,
|
projectId,
|
||||||
actor,
|
actor,
|
||||||
@ -621,6 +659,7 @@ export const projectServiceFactory = ({
|
|||||||
upgradeProject,
|
upgradeProject,
|
||||||
listProjectCas,
|
listProjectCas,
|
||||||
listProjectCertificates,
|
listProjectCertificates,
|
||||||
updateVersionLimit
|
updateVersionLimit,
|
||||||
|
updateAuditLogsRetention
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -49,6 +49,11 @@ export type TUpdateProjectVersionLimitDTO = {
|
|||||||
workspaceSlug: string;
|
workspaceSlug: string;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TUpdateAuditLogsRetentionDTO = {
|
||||||
|
auditLogsRetentionDays: number;
|
||||||
|
workspaceSlug: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TUpdateProjectNameDTO = {
|
export type TUpdateProjectNameDTO = {
|
||||||
name: string;
|
name: string;
|
||||||
} & TProjectPermission;
|
} & TProjectPermission;
|
||||||
|
@ -30,7 +30,6 @@ export const secretBlindIndexDALFactory = (db: TDbClient) => {
|
|||||||
.leftJoin(TableName.SecretFolder, `${TableName.SecretFolder}.id`, `${TableName.Secret}.folderId`)
|
.leftJoin(TableName.SecretFolder, `${TableName.SecretFolder}.id`, `${TableName.Secret}.folderId`)
|
||||||
.leftJoin(TableName.Environment, `${TableName.Environment}.id`, `${TableName.SecretFolder}.envId`)
|
.leftJoin(TableName.Environment, `${TableName.Environment}.id`, `${TableName.SecretFolder}.envId`)
|
||||||
.where({ projectId })
|
.where({ projectId })
|
||||||
.whereNull("secretBlindIndex")
|
|
||||||
.select(selectAllTableCols(TableName.Secret))
|
.select(selectAllTableCols(TableName.Secret))
|
||||||
.select(
|
.select(
|
||||||
db.ref("slug").withSchema(TableName.Environment).as("environment"),
|
db.ref("slug").withSchema(TableName.Environment).as("environment"),
|
||||||
@ -49,7 +48,6 @@ export const secretBlindIndexDALFactory = (db: TDbClient) => {
|
|||||||
.leftJoin(TableName.Environment, `${TableName.Environment}.id`, `${TableName.SecretFolder}.envId`)
|
.leftJoin(TableName.Environment, `${TableName.Environment}.id`, `${TableName.SecretFolder}.envId`)
|
||||||
.where({ projectId })
|
.where({ projectId })
|
||||||
.whereIn(`${TableName.Secret}.id`, secretIds)
|
.whereIn(`${TableName.Secret}.id`, secretIds)
|
||||||
.whereNull("secretBlindIndex")
|
|
||||||
.select(selectAllTableCols(TableName.Secret))
|
.select(selectAllTableCols(TableName.Secret))
|
||||||
.select(
|
.select(
|
||||||
db.ref("slug").withSchema(TableName.Environment).as("environment"),
|
db.ref("slug").withSchema(TableName.Environment).as("environment"),
|
||||||
|
@ -73,6 +73,11 @@ var secretsCmd = &cobra.Command{
|
|||||||
util.HandleError(err, "Unable to parse flag")
|
util.HandleError(err, "Unable to parse flag")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
plainOutput, err := cmd.Flags().GetBool("plain")
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(err, "Unable to parse flag")
|
||||||
|
}
|
||||||
|
|
||||||
request := models.GetAllSecretsParameters{
|
request := models.GetAllSecretsParameters{
|
||||||
Environment: environmentName,
|
Environment: environmentName,
|
||||||
WorkspaceId: projectId,
|
WorkspaceId: projectId,
|
||||||
@ -100,7 +105,6 @@ var secretsCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if shouldExpandSecrets {
|
if shouldExpandSecrets {
|
||||||
|
|
||||||
authParams := models.ExpandSecretsAuthentication{}
|
authParams := models.ExpandSecretsAuthentication{}
|
||||||
if token != nil && token.Type == util.SERVICE_TOKEN_IDENTIFIER {
|
if token != nil && token.Type == util.SERVICE_TOKEN_IDENTIFIER {
|
||||||
authParams.InfisicalToken = token.Token
|
authParams.InfisicalToken = token.Token
|
||||||
@ -114,7 +118,14 @@ var secretsCmd = &cobra.Command{
|
|||||||
// Sort the secrets by key so we can create a consistent output
|
// Sort the secrets by key so we can create a consistent output
|
||||||
secrets = util.SortSecretsByKeys(secrets)
|
secrets = util.SortSecretsByKeys(secrets)
|
||||||
|
|
||||||
|
if plainOutput {
|
||||||
|
for _, secret := range secrets {
|
||||||
|
fmt.Println(secret.Value)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
visualize.PrintAllSecretDetails(secrets)
|
visualize.PrintAllSecretDetails(secrets)
|
||||||
|
}
|
||||||
|
|
||||||
Telemetry.CaptureEvent("cli-command:secrets", posthog.NewProperties().Set("secretCount", len(secrets)).Set("version", util.CLI_VERSION))
|
Telemetry.CaptureEvent("cli-command:secrets", posthog.NewProperties().Set("secretCount", len(secrets)).Set("version", util.CLI_VERSION))
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -325,9 +336,20 @@ func getSecretsByNames(cmd *cobra.Command, args []string) {
|
|||||||
util.HandleError(err, "Unable to parse recursive flag")
|
util.HandleError(err, "Unable to parse recursive flag")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// deprecated, in favor of --plain
|
||||||
showOnlyValue, err := cmd.Flags().GetBool("raw-value")
|
showOnlyValue, err := cmd.Flags().GetBool("raw-value")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.HandleError(err, "Unable to parse path flag")
|
util.HandleError(err, "Unable to parse flag")
|
||||||
|
}
|
||||||
|
|
||||||
|
plainOutput, err := cmd.Flags().GetBool("plain")
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(err, "Unable to parse flag")
|
||||||
|
}
|
||||||
|
|
||||||
|
includeImports, err := cmd.Flags().GetBool("include-imports")
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(err, "Unable to parse flag")
|
||||||
}
|
}
|
||||||
|
|
||||||
request := models.GetAllSecretsParameters{
|
request := models.GetAllSecretsParameters{
|
||||||
@ -335,7 +357,7 @@ func getSecretsByNames(cmd *cobra.Command, args []string) {
|
|||||||
WorkspaceId: projectId,
|
WorkspaceId: projectId,
|
||||||
TagSlugs: tagSlugs,
|
TagSlugs: tagSlugs,
|
||||||
SecretsPath: secretsPath,
|
SecretsPath: secretsPath,
|
||||||
IncludeImport: true,
|
IncludeImport: includeImports,
|
||||||
Recursive: recursive,
|
Recursive: recursive,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -377,15 +399,15 @@ func getSecretsByNames(cmd *cobra.Command, args []string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if showOnlyValue && len(requestedSecrets) > 1 {
|
// showOnlyValue deprecated in favor of --plain, below only for backward compatibility
|
||||||
util.PrintErrorMessageAndExit("--raw-value only works with one secret.")
|
if plainOutput || showOnlyValue {
|
||||||
|
for _, secret := range requestedSecrets {
|
||||||
|
fmt.Println(secret.Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
if showOnlyValue {
|
|
||||||
fmt.Printf(requestedSecrets[0].Value)
|
|
||||||
} else {
|
} else {
|
||||||
visualize.PrintAllSecretDetails(requestedSecrets)
|
visualize.PrintAllSecretDetails(requestedSecrets)
|
||||||
}
|
}
|
||||||
|
|
||||||
Telemetry.CaptureEvent("cli-command:secrets get", posthog.NewProperties().Set("secretCount", len(secrets)).Set("version", util.CLI_VERSION))
|
Telemetry.CaptureEvent("cli-command:secrets get", posthog.NewProperties().Set("secretCount", len(secrets)).Set("version", util.CLI_VERSION))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -639,11 +661,12 @@ func init() {
|
|||||||
secretsGetCmd.Flags().String("token", "", "Fetch secrets using service token or machine identity access token")
|
secretsGetCmd.Flags().String("token", "", "Fetch secrets using service token or machine identity access token")
|
||||||
secretsGetCmd.Flags().String("projectId", "", "manually set the project ID to fetch secrets from when using machine identity based auth")
|
secretsGetCmd.Flags().String("projectId", "", "manually set the project ID to fetch secrets from when using machine identity based auth")
|
||||||
secretsGetCmd.Flags().String("path", "/", "get secrets within a folder path")
|
secretsGetCmd.Flags().String("path", "/", "get secrets within a folder path")
|
||||||
secretsGetCmd.Flags().Bool("expand", true, "Parse shell parameter expansions in your secrets")
|
secretsGetCmd.Flags().Bool("plain", false, "print values without formatting, one per line")
|
||||||
secretsGetCmd.Flags().Bool("raw-value", false, "Returns only the value of secret, only works with one secret")
|
secretsGetCmd.Flags().Bool("raw-value", false, "deprecated. Returns only the value of secret, only works with one secret. Use --plain instead")
|
||||||
|
secretsGetCmd.Flags().Bool("include-imports", true, "Imported linked secrets ")
|
||||||
|
secretsGetCmd.Flags().Bool("expand", true, "Parse shell parameter expansions in your secrets, and process your referenced secrets")
|
||||||
secretsGetCmd.Flags().Bool("recursive", false, "Fetch secrets from all sub-folders")
|
secretsGetCmd.Flags().Bool("recursive", false, "Fetch secrets from all sub-folders")
|
||||||
secretsCmd.AddCommand(secretsGetCmd)
|
secretsCmd.AddCommand(secretsGetCmd)
|
||||||
|
|
||||||
secretsCmd.Flags().Bool("secret-overriding", true, "Prioritizes personal secrets, if any, with the same name over shared secrets")
|
secretsCmd.Flags().Bool("secret-overriding", true, "Prioritizes personal secrets, if any, with the same name over shared secrets")
|
||||||
secretsCmd.AddCommand(secretsSetCmd)
|
secretsCmd.AddCommand(secretsSetCmd)
|
||||||
secretsSetCmd.Flags().String("token", "", "Fetch secrets using service token or machine identity access token")
|
secretsSetCmd.Flags().String("token", "", "Fetch secrets using service token or machine identity access token")
|
||||||
@ -687,10 +710,11 @@ func init() {
|
|||||||
secretsCmd.Flags().String("token", "", "Fetch secrets using service token or machine identity access token")
|
secretsCmd.Flags().String("token", "", "Fetch secrets using service token or machine identity access token")
|
||||||
secretsCmd.Flags().String("projectId", "", "manually set the projectId to fetch secrets when using machine identity based auth")
|
secretsCmd.Flags().String("projectId", "", "manually set the projectId to fetch secrets when using machine identity based auth")
|
||||||
secretsCmd.PersistentFlags().String("env", "dev", "Used to select the environment name on which actions should be taken on")
|
secretsCmd.PersistentFlags().String("env", "dev", "Used to select the environment name on which actions should be taken on")
|
||||||
secretsCmd.Flags().Bool("expand", true, "Parse shell parameter expansions in your secrets")
|
secretsCmd.Flags().Bool("expand", true, "Parse shell parameter expansions in your secrets, and process your referenced secrets")
|
||||||
secretsCmd.Flags().Bool("include-imports", true, "Imported linked secrets ")
|
secretsCmd.Flags().Bool("include-imports", true, "Imported linked secrets ")
|
||||||
secretsCmd.Flags().Bool("recursive", false, "Fetch secrets from all sub-folders")
|
secretsCmd.Flags().Bool("recursive", false, "Fetch secrets from all sub-folders")
|
||||||
secretsCmd.PersistentFlags().StringP("tags", "t", "", "filter secrets by tag slugs")
|
secretsCmd.PersistentFlags().StringP("tags", "t", "", "filter secrets by tag slugs")
|
||||||
secretsCmd.Flags().String("path", "/", "get secrets within a folder path")
|
secretsCmd.Flags().String("path", "/", "get secrets within a folder path")
|
||||||
|
secretsCmd.Flags().Bool("plain", false, "print values without formatting, one per line")
|
||||||
rootCmd.AddCommand(secretsCmd)
|
rootCmd.AddCommand(secretsCmd)
|
||||||
}
|
}
|
||||||
|
5
docs/api-reference/endpoints/identities/get-by-id.mdx
Normal file
5
docs/api-reference/endpoints/identities/get-by-id.mdx
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "Get By ID"
|
||||||
|
openapi: "GET /api/v1/identities/{identityId}"
|
||||||
|
---
|
||||||
|
|
4
docs/api-reference/endpoints/identities/list.mdx
Normal file
4
docs/api-reference/endpoints/identities/list.mdx
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "List"
|
||||||
|
openapi: "GET /api/v1/identities"
|
||||||
|
---
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get Client Secret By ID"
|
||||||
|
openapi: "GET /api/v1/auth/universal-auth/identities/{identityId}/client-secrets/{clientSecretId}"
|
||||||
|
---
|
4
docs/api-reference/endpoints/universal-auth/revoke.mdx
Normal file
4
docs/api-reference/endpoints/universal-auth/revoke.mdx
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Revoke"
|
||||||
|
openapi: "DELETE /api/v1/auth/universal-auth/identities/{identityId}"
|
||||||
|
---
|
@ -88,6 +88,27 @@ $ infisical secrets
|
|||||||
```
|
```
|
||||||
|
|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
<Accordion title="--plain">
|
||||||
|
The `--plain` flag will output all your secret values without formatting, one per line.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Example
|
||||||
|
infisical secrets --plain --silent
|
||||||
|
```
|
||||||
|
|
||||||
|
</Accordion>
|
||||||
|
|
||||||
|
<Accordion title="--silent">
|
||||||
|
The `--silent` flag disables output of tip/info messages. Useful when running in scripts or CI/CD pipelines.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Example
|
||||||
|
infisical secrets --silent
|
||||||
|
```
|
||||||
|
|
||||||
|
Can be used inline to replace `INFISICAL_DISABLE_UPDATE_CHECK`
|
||||||
|
|
||||||
|
</Accordion>
|
||||||
|
|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
|
||||||
@ -99,6 +120,7 @@ $ infisical secrets get <secret-name-a> <secret-name-b> ...
|
|||||||
|
|
||||||
# Example
|
# Example
|
||||||
$ infisical secrets get DOMAIN
|
$ infisical secrets get DOMAIN
|
||||||
|
$ infisical secrets get DOMAIN PORT
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -111,7 +133,41 @@ $ infisical secrets get DOMAIN
|
|||||||
|
|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
|
||||||
<Accordion title="--raw-value">
|
<Accordion title="--plain">
|
||||||
|
The `--plain` flag will output all your requested secret values without formatting, one per line.
|
||||||
|
|
||||||
|
Default value: `false`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Example
|
||||||
|
infisical secrets get FOO --plain
|
||||||
|
infisical secrets get FOO BAR --plain
|
||||||
|
|
||||||
|
# Fetch a single value and assign it to a variable
|
||||||
|
API_KEY=$(infisical secrets get FOO --plain --silent)
|
||||||
|
```
|
||||||
|
|
||||||
|
<Tip>
|
||||||
|
When running in CI/CD environments or in a script, set `INFISICAL_DISABLE_UPDATE_CHECK=true` or add the `--silent` flag. This will help hide any CLI info/debug output and only show the secret value.
|
||||||
|
</Tip>
|
||||||
|
|
||||||
|
</Accordion>
|
||||||
|
|
||||||
|
<Accordion title="--silent">
|
||||||
|
The `--silent` flag disables output of tip/info messages. Useful when running in scripts or CI/CD pipelines.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Example
|
||||||
|
infisical secrets get FOO --plain --silent
|
||||||
|
```
|
||||||
|
|
||||||
|
Can be used inline to replace `INFISICAL_DISABLE_UPDATE_CHECK`
|
||||||
|
|
||||||
|
</Accordion>
|
||||||
|
|
||||||
|
<Accordion title="--raw-value (deprecated)">
|
||||||
|
Use `--plain` instead, as it supports single and multiple secrets.
|
||||||
|
|
||||||
Used to print the plain value of a single requested secret without any table style.
|
Used to print the plain value of a single requested secret without any table style.
|
||||||
|
|
||||||
Default value: `false`
|
Default value: `false`
|
||||||
@ -119,10 +175,11 @@ $ infisical secrets get DOMAIN
|
|||||||
Example: `infisical secrets get DOMAIN --raw-value`
|
Example: `infisical secrets get DOMAIN --raw-value`
|
||||||
|
|
||||||
<Tip>
|
<Tip>
|
||||||
When running in CI/CD environments or in a script, set `INFISICAL_DISABLE_UPDATE_CHECK` env to `true`. This will help hide any CLI update messages and only show the secret value.
|
When running in CI/CD environments or in a script, set `INFISICAL_DISABLE_UPDATE_CHECK=true` or add the `--silent` flag. This will help hide any CLI info/debug output and only show the secret value.
|
||||||
</Tip>
|
</Tip>
|
||||||
|
|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
|
||||||
<Accordion title="infisical secrets set">
|
<Accordion title="infisical secrets set">
|
||||||
|
@ -30,6 +30,7 @@ Prerequisites:
|
|||||||
- Bind DN: The distinguished name of object to bind when performing the user search such as `cn=infisical,ou=Users,dc=acme,dc=com`.
|
- Bind DN: The distinguished name of object to bind when performing the user search such as `cn=infisical,ou=Users,dc=acme,dc=com`.
|
||||||
- Bind Pass: The password to use along with `Bind DN` when performing the user search.
|
- Bind Pass: The password to use along with `Bind DN` when performing the user search.
|
||||||
- User Search Base / User DN: Base DN under which to perform user search such as `ou=Users,dc=acme,dc=com`.
|
- User Search Base / User DN: Base DN under which to perform user search such as `ou=Users,dc=acme,dc=com`.
|
||||||
|
- Unique User Attribute: The attribute to use as the unique identifier of LDAP users such as `sAMAccountName`, `cn`, `uid`, `objectGUID` ... If left blank, defaults to `uidNumber`
|
||||||
- User Search Filter (optional): Template used to construct the LDAP user search filter such as `(uid={{username}})`; use literal `{{username}}` to have the given username used in the search. The default is `(uid={{username}})` which is compatible with several common directory schemas.
|
- User Search Filter (optional): Template used to construct the LDAP user search filter such as `(uid={{username}})`; use literal `{{username}}` to have the given username used in the search. The default is `(uid={{username}})` which is compatible with several common directory schemas.
|
||||||
- Group Search Base / Group DN (optional): LDAP search base to use for group membership search such as `ou=Groups,dc=acme,dc=com`.
|
- Group Search Base / Group DN (optional): LDAP search base to use for group membership search such as `ou=Groups,dc=acme,dc=com`.
|
||||||
- Group Filter (optional): Template used when constructing the group membership query such as `(&(objectClass=posixGroup)(memberUid={{.Username}}))`. The template can access the following context variables: [`UserDN`, `UserName`]. The default is `(|(memberUid={{.Username}})(member={{.UserDN}})(uniqueMember={{.UserDN}}))` which is compatible with several common directory schemas.
|
- Group Filter (optional): Template used when constructing the group membership query such as `(&(objectClass=posixGroup)(memberUid={{.Username}}))`. The template can access the following context variables: [`UserDN`, `UserName`]. The default is `(|(memberUid={{.Username}})(member={{.UserDN}})(uniqueMember={{.UserDN}}))` which is compatible with several common directory schemas.
|
||||||
|
@ -39,6 +39,7 @@ Prerequisites:
|
|||||||
- Bind DN: The distinguished name of object to bind when performing the user search (`uid=<ldap-user-username>,ou=Users,o=<your-org-id>,dc=jumpcloud,dc=com`).
|
- Bind DN: The distinguished name of object to bind when performing the user search (`uid=<ldap-user-username>,ou=Users,o=<your-org-id>,dc=jumpcloud,dc=com`).
|
||||||
- Bind Pass: The password to use along with `Bind DN` when performing the user search.
|
- Bind Pass: The password to use along with `Bind DN` when performing the user search.
|
||||||
- User Search Base / User DN: Base DN under which to perform user search (`ou=Users,o=<your-org-id>,dc=jumpcloud,dc=com`).
|
- User Search Base / User DN: Base DN under which to perform user search (`ou=Users,o=<your-org-id>,dc=jumpcloud,dc=com`).
|
||||||
|
- Unique User Attribute: The attribute to use as the unique identifier of LDAP users such as `sAMAccountName`, `cn`, `uid`, `objectGUID` ... If left blank, defaults to `uidNumber`
|
||||||
- User Search Filter (optional): Template used to construct the LDAP user search filter (`(uid={{username}})`).
|
- User Search Filter (optional): Template used to construct the LDAP user search filter (`(uid={{username}})`).
|
||||||
- Group Search Base / Group DN (optional): LDAP search base to use for group membership search (`ou=Users,o=<your-org-id>,dc=jumpcloud,dc=com`).
|
- Group Search Base / Group DN (optional): LDAP search base to use for group membership search (`ou=Users,o=<your-org-id>,dc=jumpcloud,dc=com`).
|
||||||
- Group Filter (optional): Template used when constructing the group membership query (`(&(objectClass=groupOfNames)(member=uid={{.Username}},ou=Users,o=<your-org-id>,dc=jumpcloud,dc=com))`)
|
- Group Filter (optional): Template used when constructing the group membership query (`(&(objectClass=groupOfNames)(member=uid={{.Username}},ou=Users,o=<your-org-id>,dc=jumpcloud,dc=com))`)
|
||||||
|
@ -56,9 +56,9 @@ In the following steps, we explore how to issue a X.509 certificate under a CA.
|
|||||||
|
|
||||||
- Issuing CA: The CA under which to issue the certificate.
|
- Issuing CA: The CA under which to issue the certificate.
|
||||||
- Friendly Name: A friendly name for the certificate; this is only for display and defaults to the common name of the certificate if left empty.
|
- Friendly Name: A friendly name for the certificate; this is only for display and defaults to the common name of the certificate if left empty.
|
||||||
- Common Name (CN): The (common) name of the certificate.
|
- Common Name (CN): The (common) name for the certificate like `service.acme.com`.
|
||||||
|
- Alternative Names (SANs): A comma-delimited list of Subject Alternative Names (SANs) for the certificate; these can be host names or email addresses like `app1.acme.com, app2.acme.com`.
|
||||||
- TTL: The lifetime of the certificate in seconds.
|
- TTL: The lifetime of the certificate in seconds.
|
||||||
- Valid Until: The date until which the certificate is valid in the date time string format specified [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#date_time_string_format). For example, the following formats would be valid: `YYYY`, `YYYY-MM`, `YYYY-MM-DD`, `YYYY-MM-DDTHH:mm:ss.sssZ`.
|
|
||||||
|
|
||||||
</Step>
|
</Step>
|
||||||
<Step title="Copying the certificate details">
|
<Step title="Copying the certificate details">
|
||||||
|
Binary file not shown.
After Width: | Height: | Size: 181 KiB |
Binary file not shown.
Before Width: | Height: | Size: 506 KiB After Width: | Height: | Size: 434 KiB |
Binary file not shown.
Before Width: | Height: | Size: 501 KiB After Width: | Height: | Size: 454 KiB |
@ -7,6 +7,8 @@ Prerequisites:
|
|||||||
|
|
||||||
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
||||||
|
|
||||||
|
<AccordionGroup>
|
||||||
|
<Accordion title="Push secrets to Bitbucket from Infisical">
|
||||||
<Steps>
|
<Steps>
|
||||||
<Step title="Authorize Infisical for Bitbucket">
|
<Step title="Authorize Infisical for Bitbucket">
|
||||||
Navigate to your project's integrations tab in Infisical.
|
Navigate to your project's integrations tab in Infisical.
|
||||||
@ -17,12 +19,6 @@ Prerequisites:
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
<Info>
|
|
||||||
If this is your project's first cloud integration, then you'll have to grant
|
|
||||||
Infisical access to your project's environment variables. Although this step
|
|
||||||
breaks E2EE, it's necessary for Infisical to sync the environment variables to
|
|
||||||
the cloud platform.
|
|
||||||
</Info>
|
|
||||||
</Step>
|
</Step>
|
||||||
<Step title="Start integration">
|
<Step title="Start integration">
|
||||||
Select which Infisical environment secrets you want to sync to which Bitbucket repo and press start integration to start syncing secrets to the repo.
|
Select which Infisical environment secrets you want to sync to which Bitbucket repo and press start integration to start syncing secrets to the repo.
|
||||||
@ -30,3 +26,43 @@ Prerequisites:
|
|||||||

|

|
||||||
</Step>
|
</Step>
|
||||||
</Steps>
|
</Steps>
|
||||||
|
|
||||||
|
</Accordion>
|
||||||
|
<Accordion title="Pull secrets in Bitbucket pipelines from Infisical">
|
||||||
|
<Steps>
|
||||||
|
<Step title="Configure Infisical Access">
|
||||||
|
Configure a [Machine Identity](https://infisical.com/docs/documentation/platform/identities/universal-auth) for your project and give it permissions to read secrets from your desired Infisical projects and environments.
|
||||||
|
</Step>
|
||||||
|
<Step title="Initialize Bitbucket variables">
|
||||||
|
Create Bitbucket variables (can be either workspace, repository, or deployment-level) to store Machine Identity Client ID and Client Secret.
|
||||||
|
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="Integrate Infisical secrets into the pipeline">
|
||||||
|
Edit your Bitbucket pipeline YAML file to include the use of the Infisical CLI to fetch and inject secrets into any script or command within the pipeline.
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
image: atlassian/default-image:3
|
||||||
|
|
||||||
|
pipelines:
|
||||||
|
default:
|
||||||
|
- step:
|
||||||
|
name: Build application with secrets from Infisical
|
||||||
|
script:
|
||||||
|
- apt update && apt install -y curl
|
||||||
|
- curl -1sLf 'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.deb.sh' | bash
|
||||||
|
- apt-get update && apt-get install -y infisical
|
||||||
|
- export INFISICAL_TOKEN=$(infisical login --method=universal-auth --client-id=$INFISICAL_CLIENT_ID --client-secret=$INFISICAL_CLIENT_SECRET --silent --plain)
|
||||||
|
- infisical run --projectId=1d0443c1-cd43-4b3a-91a3-9d5f81254a89 --env=dev -- npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
<Tip>
|
||||||
|
Set the values of `projectId` and `env` flags in the `infisical run` command to your intended source path. For more options, refer to the CLI command reference [here](https://infisical.com/docs/cli/commands/run).
|
||||||
|
</Tip>
|
||||||
|
</Step>
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
</Accordion>
|
||||||
|
</AccordionGroup>
|
||||||
|
@ -77,11 +77,12 @@ description: "How to sync secrets from Infisical to GitLab"
|
|||||||
</Steps>
|
</Steps>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
</AccordionGroup>
|
</AccordionGroup>
|
||||||
|
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab title="Self-Hosted Setup">
|
<Tab title="Self-Hosted Setup">
|
||||||
Using the GitLab integration on a self-hosted instance of Infisical requires configuring an application in GitLab
|
Using the GitLab integration on a self-hosted instance of Infisical requires configuring an application in GitLab
|
||||||
and registering your instance with it.
|
and registering your instance with it.
|
||||||
|
<Tip>If you're self-hosting Gitlab with custom certificates, you will have to configure your Infisical instance to trust these certificates. To learn how, please follow [this guide](../../self-hosting/guides/custom-certificates).</Tip>
|
||||||
<Steps>
|
<Steps>
|
||||||
<Step title="Create an OAuth application in GitLab">
|
<Step title="Create an OAuth application in GitLab">
|
||||||
Navigate to your user Settings > Applications to create a new GitLab application.
|
Navigate to your user Settings > Applications to create a new GitLab application.
|
||||||
@ -111,6 +112,6 @@ description: "How to sync secrets from Infisical to GitLab"
|
|||||||
Once added, restart your Infisical instance and use the GitLab integration.
|
Once added, restart your Infisical instance and use the GitLab integration.
|
||||||
</Step>
|
</Step>
|
||||||
</Steps>
|
</Steps>
|
||||||
|
|
||||||
</Tab>
|
</Tab>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
|
@ -222,7 +222,8 @@
|
|||||||
"group": "Guides",
|
"group": "Guides",
|
||||||
"pages": [
|
"pages": [
|
||||||
"self-hosting/configuration/schema-migrations",
|
"self-hosting/configuration/schema-migrations",
|
||||||
"self-hosting/guides/mongo-to-postgres"
|
"self-hosting/guides/mongo-to-postgres",
|
||||||
|
"self-hosting/guides/custom-certificates"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -419,7 +420,9 @@
|
|||||||
"pages": [
|
"pages": [
|
||||||
"api-reference/endpoints/identities/create",
|
"api-reference/endpoints/identities/create",
|
||||||
"api-reference/endpoints/identities/update",
|
"api-reference/endpoints/identities/update",
|
||||||
"api-reference/endpoints/identities/delete"
|
"api-reference/endpoints/identities/delete",
|
||||||
|
"api-reference/endpoints/identities/get-by-id",
|
||||||
|
"api-reference/endpoints/identities/list"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -429,9 +432,11 @@
|
|||||||
"api-reference/endpoints/universal-auth/attach",
|
"api-reference/endpoints/universal-auth/attach",
|
||||||
"api-reference/endpoints/universal-auth/retrieve",
|
"api-reference/endpoints/universal-auth/retrieve",
|
||||||
"api-reference/endpoints/universal-auth/update",
|
"api-reference/endpoints/universal-auth/update",
|
||||||
|
"api-reference/endpoints/universal-auth/revoke",
|
||||||
"api-reference/endpoints/universal-auth/create-client-secret",
|
"api-reference/endpoints/universal-auth/create-client-secret",
|
||||||
"api-reference/endpoints/universal-auth/list-client-secrets",
|
"api-reference/endpoints/universal-auth/list-client-secrets",
|
||||||
"api-reference/endpoints/universal-auth/revoke-client-secret",
|
"api-reference/endpoints/universal-auth/revoke-client-secret",
|
||||||
|
"api-reference/endpoints/universal-auth/get-client-secret-by-id",
|
||||||
"api-reference/endpoints/universal-auth/renew-access-token",
|
"api-reference/endpoints/universal-auth/renew-access-token",
|
||||||
"api-reference/endpoints/universal-auth/revoke-access-token"
|
"api-reference/endpoints/universal-auth/revoke-access-token"
|
||||||
]
|
]
|
||||||
|
@ -19,7 +19,7 @@ From local development to production, Infisical SDKs provide the easiest way for
|
|||||||
<Card href="/sdks/languages/java" title="Java" icon="java" color="#e41f23">
|
<Card href="/sdks/languages/java" title="Java" icon="java" color="#e41f23">
|
||||||
Manage secrets for your Java application on demand
|
Manage secrets for your Java application on demand
|
||||||
</Card>
|
</Card>
|
||||||
<Card href="/sdks/languages/go" title="Go icon="golang" color="#367B99">
|
<Card href="/sdks/languages/go" title="Go" icon="golang" color="#367B99">
|
||||||
Manage secrets for your Go application on demand
|
Manage secrets for your Go application on demand
|
||||||
</Card>
|
</Card>
|
||||||
<Card href="/sdks/languages/csharp" title="C#" icon="bars" color="#368833">
|
<Card href="/sdks/languages/csharp" title="C#" icon="bars" color="#368833">
|
||||||
|
@ -82,6 +82,13 @@ The [Docker stack file](https://github.com/Infisical/infisical/tree/main/docker-
|
|||||||
## Deployment instructions
|
## Deployment instructions
|
||||||
|
|
||||||
<Steps>
|
<Steps>
|
||||||
|
<Step title="Install Docker on nodes">
|
||||||
|
Run the following on each node to install the Docker engine.
|
||||||
|
|
||||||
|
```
|
||||||
|
curl -fsSL https://get.docker.com -o get-docker.sh && sh get-docker.sh
|
||||||
|
```
|
||||||
|
</Step>
|
||||||
<Step title="Initialize Docker Swarm on one of the VMs by running the following command">
|
<Step title="Initialize Docker Swarm on one of the VMs by running the following command">
|
||||||
```
|
```
|
||||||
docker swarm init
|
docker swarm init
|
||||||
@ -161,7 +168,12 @@ The [Docker stack file](https://github.com/Infisical/infisical/tree/main/docker-
|
|||||||
<Step title="Run schema migrations">
|
<Step title="Run schema migrations">
|
||||||
Run the schema migration to initialize the database. Follow the [guide here](/self-hosting/configuration/schema-migrations) to learn how.
|
Run the schema migration to initialize the database. Follow the [guide here](/self-hosting/configuration/schema-migrations) to learn how.
|
||||||
|
|
||||||
To connect to the Postgres database, use the following default credentials defined in the Docker swarm: username: `postgres`, password: `postgres` and database: `postgres`.
|
To run the migrations, you'll need to connect to the Postgres instance deployed on your Docker swarm. The default Postgres user credentials are defined in the Docker swarm: username: `postgres`, password: `postgres` and database: `postgres`.
|
||||||
|
We recommend you change these credentials when deploying to production and creating a separate DB for Infisical.
|
||||||
|
|
||||||
|
<Info>
|
||||||
|
After running the schema migrations, be sure to update the `.env` file to have the correct `DB_CONNECTION_URI`.
|
||||||
|
</Info>
|
||||||
</Step>
|
</Step>
|
||||||
|
|
||||||
<Step title="View service status">
|
<Step title="View service status">
|
||||||
|
26
docs/self-hosting/guides/custom-certificates.mdx
Normal file
26
docs/self-hosting/guides/custom-certificates.mdx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
title: "Adding Custom Certificates"
|
||||||
|
description: "Learn how to configure Infisical with custom certificates"
|
||||||
|
---
|
||||||
|
|
||||||
|
By default, the Infisical Docker image includes certificates from well-known public certificate authorities.
|
||||||
|
However, some integrations with Infisical may need to communicate with your internal services that use private certificate authorities.
|
||||||
|
To configure trust for custom certificates, follow these steps. This is particularly useful for connecting Infisical with self-hosted services like GitLab.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Docker
|
||||||
|
- Standalone [Infisical image](https://hub.docker.com/r/infisical/infisical)
|
||||||
|
- Certificate public key `.pem` files
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
1. Place all your public key `.pem` files into a single directory.
|
||||||
|
2. Mount the directory containing the `.pem` files to the `usr/local/share/ca-certificates/` path in the Infisical container.
|
||||||
|
3. Set the following environment variable on your Infisical container:
|
||||||
|
```
|
||||||
|
NODE_EXTRA_CA_CERTS=/etc/ssl/certs/ca-certificates.crt
|
||||||
|
```
|
||||||
|
4. Start the Infisical container.
|
||||||
|
|
||||||
|
By following these steps, your Infisical container will trust the specified certificates, allowing you to securely connect Infisical to your internal services.
|
@ -81,6 +81,7 @@ export type TCreateCertificateDTO = {
|
|||||||
caId: string;
|
caId: string;
|
||||||
friendlyName?: string;
|
friendlyName?: string;
|
||||||
commonName: string;
|
commonName: string;
|
||||||
|
altNames: string; // sans
|
||||||
ttl: string; // string compatible with ms
|
ttl: string; // string compatible with ms
|
||||||
notBefore?: string;
|
notBefore?: string;
|
||||||
notAfter?: string;
|
notAfter?: string;
|
||||||
|
@ -6,6 +6,7 @@ export type TCertificate = {
|
|||||||
status: CertStatus;
|
status: CertStatus;
|
||||||
friendlyName: string;
|
friendlyName: string;
|
||||||
commonName: string;
|
commonName: string;
|
||||||
|
altNames: string;
|
||||||
serialNumber: string;
|
serialNumber: string;
|
||||||
notBefore: string;
|
notBefore: string;
|
||||||
notAfter: string;
|
notAfter: string;
|
||||||
|
@ -13,6 +13,7 @@ export const useCreateLDAPConfig = () => {
|
|||||||
url,
|
url,
|
||||||
bindDN,
|
bindDN,
|
||||||
bindPass,
|
bindPass,
|
||||||
|
uniqueUserAttribute,
|
||||||
searchBase,
|
searchBase,
|
||||||
searchFilter,
|
searchFilter,
|
||||||
groupSearchBase,
|
groupSearchBase,
|
||||||
@ -24,6 +25,7 @@ export const useCreateLDAPConfig = () => {
|
|||||||
url: string;
|
url: string;
|
||||||
bindDN: string;
|
bindDN: string;
|
||||||
bindPass: string;
|
bindPass: string;
|
||||||
|
uniqueUserAttribute: string;
|
||||||
searchBase: string;
|
searchBase: string;
|
||||||
searchFilter: string;
|
searchFilter: string;
|
||||||
groupSearchBase: string;
|
groupSearchBase: string;
|
||||||
@ -36,6 +38,7 @@ export const useCreateLDAPConfig = () => {
|
|||||||
url,
|
url,
|
||||||
bindDN,
|
bindDN,
|
||||||
bindPass,
|
bindPass,
|
||||||
|
uniqueUserAttribute,
|
||||||
searchBase,
|
searchBase,
|
||||||
searchFilter,
|
searchFilter,
|
||||||
groupSearchBase,
|
groupSearchBase,
|
||||||
@ -60,6 +63,7 @@ export const useUpdateLDAPConfig = () => {
|
|||||||
url,
|
url,
|
||||||
bindDN,
|
bindDN,
|
||||||
bindPass,
|
bindPass,
|
||||||
|
uniqueUserAttribute,
|
||||||
searchBase,
|
searchBase,
|
||||||
searchFilter,
|
searchFilter,
|
||||||
groupSearchBase,
|
groupSearchBase,
|
||||||
@ -71,6 +75,7 @@ export const useUpdateLDAPConfig = () => {
|
|||||||
url?: string;
|
url?: string;
|
||||||
bindDN?: string;
|
bindDN?: string;
|
||||||
bindPass?: string;
|
bindPass?: string;
|
||||||
|
uniqueUserAttribute?: string;
|
||||||
searchBase?: string;
|
searchBase?: string;
|
||||||
searchFilter?: string;
|
searchFilter?: string;
|
||||||
groupSearchBase?: string;
|
groupSearchBase?: string;
|
||||||
@ -83,6 +88,7 @@ export const useUpdateLDAPConfig = () => {
|
|||||||
url,
|
url,
|
||||||
bindDN,
|
bindDN,
|
||||||
bindPass,
|
bindPass,
|
||||||
|
uniqueUserAttribute,
|
||||||
searchBase,
|
searchBase,
|
||||||
searchFilter,
|
searchFilter,
|
||||||
groupSearchBase,
|
groupSearchBase,
|
||||||
|
@ -22,6 +22,7 @@ import {
|
|||||||
ToggleAutoCapitalizationDTO,
|
ToggleAutoCapitalizationDTO,
|
||||||
TUpdateWorkspaceIdentityRoleDTO,
|
TUpdateWorkspaceIdentityRoleDTO,
|
||||||
TUpdateWorkspaceUserRoleDTO,
|
TUpdateWorkspaceUserRoleDTO,
|
||||||
|
UpdateAuditLogsRetentionDTO,
|
||||||
UpdateEnvironmentDTO,
|
UpdateEnvironmentDTO,
|
||||||
UpdatePitVersionLimitDTO,
|
UpdatePitVersionLimitDTO,
|
||||||
Workspace
|
Workspace
|
||||||
@ -284,6 +285,21 @@ export const useUpdateWorkspaceVersionLimit = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useUpdateWorkspaceAuditLogsRetention = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation<{}, {}, UpdateAuditLogsRetentionDTO>({
|
||||||
|
mutationFn: ({ projectSlug, auditLogsRetentionDays }) => {
|
||||||
|
return apiRequest.put(`/api/v1/workspace/${projectSlug}/audit-logs-retention`, {
|
||||||
|
auditLogsRetentionDays
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const useDeleteWorkspace = () => {
|
export const useDeleteWorkspace = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ export type Workspace = {
|
|||||||
autoCapitalization: boolean;
|
autoCapitalization: boolean;
|
||||||
environments: WorkspaceEnv[];
|
environments: WorkspaceEnv[];
|
||||||
pitVersionLimit: number;
|
pitVersionLimit: number;
|
||||||
|
auditLogsRetentionDays: number;
|
||||||
slug: string;
|
slug: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -51,6 +52,7 @@ export type CreateWorkspaceDTO = {
|
|||||||
|
|
||||||
export type RenameWorkspaceDTO = { workspaceID: string; newWorkspaceName: string };
|
export type RenameWorkspaceDTO = { workspaceID: string; newWorkspaceName: string };
|
||||||
export type UpdatePitVersionLimitDTO = { projectSlug: string; pitVersionLimit: number };
|
export type UpdatePitVersionLimitDTO = { projectSlug: string; pitVersionLimit: number };
|
||||||
|
export type UpdateAuditLogsRetentionDTO = { projectSlug: string; auditLogsRetentionDays: number };
|
||||||
export type ToggleAutoCapitalizationDTO = { workspaceID: string; state: boolean };
|
export type ToggleAutoCapitalizationDTO = { workspaceID: string; state: boolean };
|
||||||
|
|
||||||
export type DeleteWorkspaceDTO = { workspaceID: string };
|
export type DeleteWorkspaceDTO = { workspaceID: string };
|
||||||
|
@ -3,6 +3,7 @@ import { useRouter } from "next/router";
|
|||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
|
|
||||||
import { useCreateIntegration } from "@app/hooks/api";
|
import { useCreateIntegration } from "@app/hooks/api";
|
||||||
|
import { IntegrationSyncBehavior } from "@app/hooks/api/integrations/types";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@ -16,6 +17,18 @@ import {
|
|||||||
import { useGetIntegrationAuthById } from "../../../hooks/api/integrationAuth";
|
import { useGetIntegrationAuthById } from "../../../hooks/api/integrationAuth";
|
||||||
import { useGetWorkspaceById } from "../../../hooks/api/workspace";
|
import { useGetWorkspaceById } from "../../../hooks/api/workspace";
|
||||||
|
|
||||||
|
const initialSyncBehaviors = [
|
||||||
|
{
|
||||||
|
label: "No Import - Overwrite all values in Azure Vault",
|
||||||
|
value: IntegrationSyncBehavior.OVERWRITE_TARGET
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Import - Prefer values from Azure Vault",
|
||||||
|
value: IntegrationSyncBehavior.PREFER_TARGET
|
||||||
|
},
|
||||||
|
{ label: "Import - Prefer values from Infisical", value: IntegrationSyncBehavior.PREFER_SOURCE }
|
||||||
|
];
|
||||||
|
|
||||||
export default function AzureKeyVaultCreateIntegrationPage() {
|
export default function AzureKeyVaultCreateIntegrationPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { mutateAsync } = useCreateIntegration();
|
const { mutateAsync } = useCreateIntegration();
|
||||||
@ -30,6 +43,9 @@ export default function AzureKeyVaultCreateIntegrationPage() {
|
|||||||
|
|
||||||
const [vaultBaseUrl, setVaultBaseUrl] = useState("");
|
const [vaultBaseUrl, setVaultBaseUrl] = useState("");
|
||||||
const [vaultBaseUrlErrorText, setVaultBaseUrlErrorText] = useState("");
|
const [vaultBaseUrlErrorText, setVaultBaseUrlErrorText] = useState("");
|
||||||
|
const [initialSyncBehavior, setInitialSyncBehavior] = useState(
|
||||||
|
IntegrationSyncBehavior.PREFER_SOURCE
|
||||||
|
);
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
@ -59,7 +75,10 @@ export default function AzureKeyVaultCreateIntegrationPage() {
|
|||||||
isActive: true,
|
isActive: true,
|
||||||
app: vaultBaseUrl,
|
app: vaultBaseUrl,
|
||||||
sourceEnvironment: selectedSourceEnvironment,
|
sourceEnvironment: selectedSourceEnvironment,
|
||||||
secretPath
|
secretPath,
|
||||||
|
metadata: {
|
||||||
|
initialSyncBehavior
|
||||||
|
}
|
||||||
});
|
});
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
|
|
||||||
@ -107,6 +126,21 @@ export default function AzureKeyVaultCreateIntegrationPage() {
|
|||||||
onChange={(e) => setVaultBaseUrl(e.target.value)}
|
onChange={(e) => setVaultBaseUrl(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
<FormControl label="Initial Sync Behavior">
|
||||||
|
<Select
|
||||||
|
value={initialSyncBehavior}
|
||||||
|
onValueChange={(e) => setInitialSyncBehavior(e as IntegrationSyncBehavior)}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
{initialSyncBehaviors.map((b) => {
|
||||||
|
return (
|
||||||
|
<SelectItem value={b.value} key={`sync-behavior-${b.value}`}>
|
||||||
|
{b.label}
|
||||||
|
</SelectItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleButtonClick}
|
onClick={handleButtonClick}
|
||||||
color="mineshaft"
|
color="mineshaft"
|
||||||
|
@ -297,6 +297,7 @@ export const IntegrationsSection = ({
|
|||||||
(popUp?.deleteConfirmation?.data as TIntegration)?.app ||
|
(popUp?.deleteConfirmation?.data as TIntegration)?.app ||
|
||||||
(popUp?.deleteConfirmation?.data as TIntegration)?.owner ||
|
(popUp?.deleteConfirmation?.data as TIntegration)?.owner ||
|
||||||
(popUp?.deleteConfirmation?.data as TIntegration)?.path ||
|
(popUp?.deleteConfirmation?.data as TIntegration)?.path ||
|
||||||
|
(popUp?.deleteConfirmation?.data as TIntegration)?.integration ||
|
||||||
""
|
""
|
||||||
}
|
}
|
||||||
onDeleteApproved={async () =>
|
onDeleteApproved={async () =>
|
||||||
|
@ -24,6 +24,7 @@ const schema = z.object({
|
|||||||
caId: z.string(),
|
caId: z.string(),
|
||||||
friendlyName: z.string(),
|
friendlyName: z.string(),
|
||||||
commonName: z.string().trim().min(1),
|
commonName: z.string().trim().min(1),
|
||||||
|
altNames: z.string(),
|
||||||
ttl: z.string().trim()
|
ttl: z.string().trim()
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -71,6 +72,7 @@ export const CertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
|
|||||||
caId: cert.caId,
|
caId: cert.caId,
|
||||||
friendlyName: cert.friendlyName,
|
friendlyName: cert.friendlyName,
|
||||||
commonName: cert.commonName,
|
commonName: cert.commonName,
|
||||||
|
altNames: cert.altNames,
|
||||||
ttl: ""
|
ttl: ""
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@ -78,12 +80,13 @@ export const CertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
|
|||||||
caId: "",
|
caId: "",
|
||||||
friendlyName: "",
|
friendlyName: "",
|
||||||
commonName: "",
|
commonName: "",
|
||||||
|
altNames: "",
|
||||||
ttl: ""
|
ttl: ""
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [cert]);
|
}, [cert]);
|
||||||
|
|
||||||
const onFormSubmit = async ({ caId, friendlyName, commonName, ttl }: FormData) => {
|
const onFormSubmit = async ({ caId, friendlyName, commonName, altNames, ttl }: FormData) => {
|
||||||
try {
|
try {
|
||||||
if (!currentWorkspace?.slug) return;
|
if (!currentWorkspace?.slug) return;
|
||||||
|
|
||||||
@ -92,6 +95,7 @@ export const CertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
|
|||||||
caId,
|
caId,
|
||||||
friendlyName,
|
friendlyName,
|
||||||
commonName,
|
commonName,
|
||||||
|
altNames,
|
||||||
ttl
|
ttl
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -192,6 +196,24 @@ export const CertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
defaultValue=""
|
||||||
|
name="altNames"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Alternative Names (SANs)"
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
placeholder="app1.acme.com, app2.acme.com, ..."
|
||||||
|
isDisabled={Boolean(cert)}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="ttl"
|
name="ttl"
|
||||||
|
@ -7,6 +7,7 @@ import {
|
|||||||
faCircleDot,
|
faCircleDot,
|
||||||
faClock,
|
faClock,
|
||||||
faPlus,
|
faPlus,
|
||||||
|
faShare,
|
||||||
faTag
|
faTag
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
@ -57,6 +58,7 @@ type Props = {
|
|||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
tags: WsTag[];
|
tags: WsTag[];
|
||||||
onCreateTag: () => void;
|
onCreateTag: () => void;
|
||||||
|
handleSecretShare: (value: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SecretDetailSidebar = ({
|
export const SecretDetailSidebar = ({
|
||||||
@ -69,7 +71,8 @@ export const SecretDetailSidebar = ({
|
|||||||
tags,
|
tags,
|
||||||
onCreateTag,
|
onCreateTag,
|
||||||
environment,
|
environment,
|
||||||
secretPath
|
secretPath,
|
||||||
|
handleSecretShare
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
@ -381,7 +384,7 @@ export const SecretDetailSidebar = ({
|
|||||||
rows={5}
|
rows={5}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<div className="my-2 mb-6 border-b border-mineshaft-600 pb-4">
|
<div className="my-2 mb-4 border-b border-mineshaft-600 pb-4">
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="skipMultilineEncoding"
|
name="skipMultilineEncoding"
|
||||||
@ -412,7 +415,17 @@ export const SecretDetailSidebar = ({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="dark mb-4 flex-grow text-sm text-bunker-300">
|
<div className="ml-1 flex items-center space-x-2">
|
||||||
|
<Button
|
||||||
|
className="px-2 py-1"
|
||||||
|
variant="outline_bg"
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faShare} />}
|
||||||
|
onClick={() => handleSecretShare(secret.valueOverride ?? secret.value)}
|
||||||
|
>
|
||||||
|
Share Secret
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className="dark mt-4 mb-4 flex-grow text-sm text-bunker-300">
|
||||||
<div className="mb-2">Version History</div>
|
<div className="mb-2">Version History</div>
|
||||||
<div className="flex h-48 flex-col space-y-2 overflow-y-auto overflow-x-hidden rounded-md border border-mineshaft-600 bg-bunker-800 p-2 dark:[color-scheme:dark]">
|
<div className="flex h-48 flex-col space-y-2 overflow-y-auto overflow-x-hidden rounded-md border border-mineshaft-600 bg-bunker-800 p-2 dark:[color-scheme:dark]">
|
||||||
{secretVersion?.map(({ createdAt, value, id }, i) => (
|
{secretVersion?.map(({ createdAt, value, id }, i) => (
|
||||||
|
@ -61,6 +61,7 @@ type Props = {
|
|||||||
onCreateTag: () => void;
|
onCreateTag: () => void;
|
||||||
environment: string;
|
environment: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
|
handleSecretShare: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SecretItem = memo(
|
export const SecretItem = memo(
|
||||||
@ -75,7 +76,8 @@ export const SecretItem = memo(
|
|||||||
onCreateTag,
|
onCreateTag,
|
||||||
onToggleSecretSelect,
|
onToggleSecretSelect,
|
||||||
environment,
|
environment,
|
||||||
secretPath
|
secretPath,
|
||||||
|
handleSecretShare
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const { currentWorkspace } = useWorkspace();
|
const { currentWorkspace } = useWorkspace();
|
||||||
const { permission } = useProjectPermission();
|
const { permission } = useProjectPermission();
|
||||||
@ -420,7 +422,8 @@ export const SecretItem = memo(
|
|||||||
<Tooltip
|
<Tooltip
|
||||||
content={
|
content={
|
||||||
secretReminderRepeatDays && secretReminderRepeatDays > 0
|
secretReminderRepeatDays && secretReminderRepeatDays > 0
|
||||||
? `Every ${secretReminderRepeatDays} day${Number(secretReminderRepeatDays) > 1 ? "s" : ""
|
? `Every ${secretReminderRepeatDays} day${
|
||||||
|
Number(secretReminderRepeatDays) > 1 ? "s" : ""
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
: "Reminder"
|
: "Reminder"
|
||||||
@ -461,6 +464,20 @@ export const SecretItem = memo(
|
|||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
)}
|
)}
|
||||||
</ProjectPermissionCan>
|
</ProjectPermissionCan>
|
||||||
|
<IconButton
|
||||||
|
className="w-0 overflow-hidden p-0 group-hover:mr-2 group-hover:w-5 data-[state=open]:w-6"
|
||||||
|
variant="plain"
|
||||||
|
size="md"
|
||||||
|
ariaLabel="share-secret"
|
||||||
|
onClick={handleSecretShare}
|
||||||
|
>
|
||||||
|
<Tooltip content="Share Secret">
|
||||||
|
<FontAwesomeSymbol
|
||||||
|
className="h-3.5 w-3.5"
|
||||||
|
symbolName={FontAwesomeSpriteName.ShareSecret}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</IconButton>
|
||||||
<PopoverContent
|
<PopoverContent
|
||||||
className="w-auto border border-mineshaft-600 bg-mineshaft-800 p-2 drop-shadow-2xl"
|
className="w-auto border border-mineshaft-600 bg-mineshaft-800 p-2 drop-shadow-2xl"
|
||||||
sticky="always"
|
sticky="always"
|
||||||
|
@ -13,6 +13,7 @@ import { secretKeys } from "@app/hooks/api/secrets/queries";
|
|||||||
import { DecryptedSecret, SecretType } from "@app/hooks/api/secrets/types";
|
import { DecryptedSecret, SecretType } from "@app/hooks/api/secrets/types";
|
||||||
import { secretSnapshotKeys } from "@app/hooks/api/secretSnapshots/queries";
|
import { secretSnapshotKeys } from "@app/hooks/api/secretSnapshots/queries";
|
||||||
import { UserWsKeyPair, WsTag } from "@app/hooks/api/types";
|
import { UserWsKeyPair, WsTag } from "@app/hooks/api/types";
|
||||||
|
import { AddShareSecretModal } from "@app/views/ShareSecretPage/components/AddShareSecretModal";
|
||||||
|
|
||||||
import { useSelectedSecretActions, useSelectedSecrets } from "../../SecretMainPage.store";
|
import { useSelectedSecretActions, useSelectedSecrets } from "../../SecretMainPage.store";
|
||||||
import { Filter, GroupBy, SortDir } from "../../SecretMainPage.types";
|
import { Filter, GroupBy, SortDir } from "../../SecretMainPage.types";
|
||||||
@ -95,7 +96,8 @@ export const SecretListView = ({
|
|||||||
const { popUp, handlePopUpToggle, handlePopUpOpen, handlePopUpClose } = usePopUp([
|
const { popUp, handlePopUpToggle, handlePopUpOpen, handlePopUpClose } = usePopUp([
|
||||||
"deleteSecret",
|
"deleteSecret",
|
||||||
"secretDetail",
|
"secretDetail",
|
||||||
"createTag"
|
"createTag",
|
||||||
|
"createSharedSecret"
|
||||||
] as const);
|
] as const);
|
||||||
|
|
||||||
// strip of side effect queries
|
// strip of side effect queries
|
||||||
@ -365,6 +367,11 @@ export const SecretListView = ({
|
|||||||
onDeleteSecret={onDeleteSecret}
|
onDeleteSecret={onDeleteSecret}
|
||||||
onDetailViewSecret={onDetailViewSecret}
|
onDetailViewSecret={onDetailViewSecret}
|
||||||
onCreateTag={onCreateTag}
|
onCreateTag={onCreateTag}
|
||||||
|
handleSecretShare={() =>
|
||||||
|
handlePopUpOpen("createSharedSecret", {
|
||||||
|
value: secret.valueOverride ?? secret.value
|
||||||
|
})
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@ -391,11 +398,18 @@ export const SecretListView = ({
|
|||||||
onSaveSecret={handleSaveSecret}
|
onSaveSecret={handleSaveSecret}
|
||||||
tags={wsTags}
|
tags={wsTags}
|
||||||
onCreateTag={() => handlePopUpOpen("createTag")}
|
onCreateTag={() => handlePopUpOpen("createTag")}
|
||||||
|
handleSecretShare={(value: string) => handlePopUpOpen("createSharedSecret", { value })}
|
||||||
/>
|
/>
|
||||||
<CreateTagModal
|
<CreateTagModal
|
||||||
isOpen={popUp.createTag.isOpen}
|
isOpen={popUp.createTag.isOpen}
|
||||||
onToggle={(isOpen) => handlePopUpToggle("createTag", isOpen)}
|
onToggle={(isOpen) => handlePopUpToggle("createTag", isOpen)}
|
||||||
/>
|
/>
|
||||||
|
<AddShareSecretModal
|
||||||
|
popUp={popUp}
|
||||||
|
handlePopUpToggle={handlePopUpToggle}
|
||||||
|
isPublic={false}
|
||||||
|
inModal
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -10,6 +10,7 @@ import {
|
|||||||
faCopy,
|
faCopy,
|
||||||
faEllipsis,
|
faEllipsis,
|
||||||
faKey,
|
faKey,
|
||||||
|
faShare,
|
||||||
faTags
|
faTags
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
@ -66,7 +67,8 @@ export enum FontAwesomeSpriteName {
|
|||||||
Override = "secret-override",
|
Override = "secret-override",
|
||||||
Close = "close",
|
Close = "close",
|
||||||
CheckedCircle = "check-circle",
|
CheckedCircle = "check-circle",
|
||||||
ReplicatedSecretKey = "secret-replicated"
|
ReplicatedSecretKey = "secret-replicated",
|
||||||
|
ShareSecret = "share-secret"
|
||||||
}
|
}
|
||||||
|
|
||||||
// this is an optimization technique
|
// this is an optimization technique
|
||||||
@ -82,5 +84,6 @@ export const FontAwesomeSpriteSymbols = [
|
|||||||
{ icon: faCodeBranch, symbol: FontAwesomeSpriteName.Override },
|
{ icon: faCodeBranch, symbol: FontAwesomeSpriteName.Override },
|
||||||
{ icon: faClose, symbol: FontAwesomeSpriteName.Close },
|
{ icon: faClose, symbol: FontAwesomeSpriteName.Close },
|
||||||
{ icon: faCheckCircle, symbol: FontAwesomeSpriteName.CheckedCircle },
|
{ icon: faCheckCircle, symbol: FontAwesomeSpriteName.CheckedCircle },
|
||||||
{ icon: faClone, symbol: FontAwesomeSpriteName.ReplicatedSecretKey }
|
{ icon: faClone, symbol: FontAwesomeSpriteName.ReplicatedSecretKey },
|
||||||
|
{ icon: faShare, symbol: FontAwesomeSpriteName.ShareSecret }
|
||||||
];
|
];
|
||||||
|
@ -20,6 +20,7 @@ const LDAPFormSchema = z.object({
|
|||||||
bindPass: z.string().default(""),
|
bindPass: z.string().default(""),
|
||||||
searchBase: z.string().default(""),
|
searchBase: z.string().default(""),
|
||||||
searchFilter: z.string().default(""),
|
searchFilter: z.string().default(""),
|
||||||
|
uniqueUserAttribute: z.string().default(""),
|
||||||
groupSearchBase: z.string().default(""),
|
groupSearchBase: z.string().default(""),
|
||||||
groupSearchFilter: z.string().default(""),
|
groupSearchFilter: z.string().default(""),
|
||||||
caCert: z.string().optional()
|
caCert: z.string().optional()
|
||||||
@ -53,6 +54,7 @@ export const LDAPModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Props)
|
|||||||
const watchGroupSearchBase = watch("groupSearchBase");
|
const watchGroupSearchBase = watch("groupSearchBase");
|
||||||
const watchGroupSearchFilter = watch("groupSearchFilter");
|
const watchGroupSearchFilter = watch("groupSearchFilter");
|
||||||
const watchCaCert = watch("caCert");
|
const watchCaCert = watch("caCert");
|
||||||
|
const watchUniqueUserAttribute = watch("uniqueUserAttribute");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data) {
|
if (data) {
|
||||||
@ -64,7 +66,8 @@ export const LDAPModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Props)
|
|||||||
searchFilter: data?.searchFilter ?? "",
|
searchFilter: data?.searchFilter ?? "",
|
||||||
groupSearchBase: data?.groupSearchBase ?? "",
|
groupSearchBase: data?.groupSearchBase ?? "",
|
||||||
groupSearchFilter: data?.groupSearchFilter ?? "",
|
groupSearchFilter: data?.groupSearchFilter ?? "",
|
||||||
caCert: data?.caCert ?? ""
|
caCert: data?.caCert ?? "",
|
||||||
|
uniqueUserAttribute: data?.uniqueUserAttribute ?? ""
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [data]);
|
}, [data]);
|
||||||
@ -73,6 +76,7 @@ export const LDAPModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Props)
|
|||||||
url,
|
url,
|
||||||
bindDN,
|
bindDN,
|
||||||
bindPass,
|
bindPass,
|
||||||
|
uniqueUserAttribute,
|
||||||
searchBase,
|
searchBase,
|
||||||
searchFilter,
|
searchFilter,
|
||||||
groupSearchBase,
|
groupSearchBase,
|
||||||
@ -92,6 +96,7 @@ export const LDAPModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Props)
|
|||||||
bindPass,
|
bindPass,
|
||||||
searchBase,
|
searchBase,
|
||||||
searchFilter,
|
searchFilter,
|
||||||
|
uniqueUserAttribute,
|
||||||
groupSearchBase,
|
groupSearchBase,
|
||||||
groupSearchFilter,
|
groupSearchFilter,
|
||||||
caCert
|
caCert
|
||||||
@ -105,6 +110,7 @@ export const LDAPModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Props)
|
|||||||
bindPass,
|
bindPass,
|
||||||
searchBase,
|
searchBase,
|
||||||
searchFilter,
|
searchFilter,
|
||||||
|
uniqueUserAttribute,
|
||||||
groupSearchBase,
|
groupSearchBase,
|
||||||
groupSearchFilter,
|
groupSearchFilter,
|
||||||
caCert
|
caCert
|
||||||
@ -138,6 +144,7 @@ export const LDAPModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Props)
|
|||||||
searchFilter: watchSearchFilter,
|
searchFilter: watchSearchFilter,
|
||||||
groupSearchBase: watchGroupSearchBase,
|
groupSearchBase: watchGroupSearchBase,
|
||||||
groupSearchFilter: watchGroupSearchFilter,
|
groupSearchFilter: watchGroupSearchFilter,
|
||||||
|
uniqueUserAttribute: watchUniqueUserAttribute,
|
||||||
caCert: watchCaCert,
|
caCert: watchCaCert,
|
||||||
shouldCloseModal: false
|
shouldCloseModal: false
|
||||||
});
|
});
|
||||||
@ -217,6 +224,19 @@ export const LDAPModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Props)
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="uniqueUserAttribute"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Unique User Attribute (Optional)"
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
>
|
||||||
|
<Input {...field} placeholder="uidNumber" />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="searchFilter"
|
name="searchFilter"
|
||||||
|
@ -67,6 +67,7 @@ export const OrgLDAPSection = (): JSX.Element => {
|
|||||||
url: "",
|
url: "",
|
||||||
bindDN: "",
|
bindDN: "",
|
||||||
bindPass: "",
|
bindPass: "",
|
||||||
|
uniqueUserAttribute: "",
|
||||||
searchBase: "",
|
searchBase: "",
|
||||||
searchFilter: "",
|
searchFilter: "",
|
||||||
groupSearchBase: "",
|
groupSearchBase: "",
|
||||||
|
@ -0,0 +1,128 @@
|
|||||||
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import { Button, FormControl, Input, UpgradePlanModal } from "@app/components/v2";
|
||||||
|
import { useProjectPermission, useSubscription, useWorkspace } from "@app/context";
|
||||||
|
import { usePopUp } from "@app/hooks";
|
||||||
|
import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
|
||||||
|
import { useUpdateWorkspaceAuditLogsRetention } from "@app/hooks/api/workspace/queries";
|
||||||
|
|
||||||
|
const formSchema = z.object({
|
||||||
|
auditLogsRetentionDays: z.coerce.number().min(0)
|
||||||
|
});
|
||||||
|
|
||||||
|
type TForm = z.infer<typeof formSchema>;
|
||||||
|
|
||||||
|
export const AuditLogsRetentionSection = () => {
|
||||||
|
const { mutateAsync: updateAuditLogsRetention } = useUpdateWorkspaceAuditLogsRetention();
|
||||||
|
|
||||||
|
const { currentWorkspace } = useWorkspace();
|
||||||
|
const { membership } = useProjectPermission();
|
||||||
|
const { subscription } = useSubscription();
|
||||||
|
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["upgradePlan"] as const);
|
||||||
|
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
formState: { isSubmitting, isDirty },
|
||||||
|
handleSubmit
|
||||||
|
} = useForm<TForm>({
|
||||||
|
resolver: zodResolver(formSchema),
|
||||||
|
values: {
|
||||||
|
auditLogsRetentionDays:
|
||||||
|
currentWorkspace?.auditLogsRetentionDays ?? subscription?.auditLogsRetentionDays ?? 0
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!currentWorkspace) return null;
|
||||||
|
|
||||||
|
const handleAuditLogsRetentionSubmit = async ({ auditLogsRetentionDays }: TForm) => {
|
||||||
|
try {
|
||||||
|
if (!subscription?.auditLogs) {
|
||||||
|
handlePopUpOpen("upgradePlan", {
|
||||||
|
description: "You can only configure audit logs retention if you upgrade your plan."
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subscription && auditLogsRetentionDays > subscription?.auditLogsRetentionDays) {
|
||||||
|
handlePopUpOpen("upgradePlan", {
|
||||||
|
description:
|
||||||
|
"To update your audit logs retention period to a higher value, upgrade your plan."
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await updateAuditLogsRetention({
|
||||||
|
auditLogsRetentionDays,
|
||||||
|
projectSlug: currentWorkspace.slug
|
||||||
|
});
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
text: "Successfully updated audit logs retention period",
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
createNotification({
|
||||||
|
text: "Failed updating audit logs retention period",
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// render only for dedicated/self-hosted instances of Infisical
|
||||||
|
if (
|
||||||
|
window.location.origin.includes("https://app.infisical.com") ||
|
||||||
|
window.location.origin.includes("https://gamma.infisical.com")
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isAdmin = membership.roles.includes(ProjectMembershipRole.Admin);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
|
<div className="flex w-full items-center justify-between">
|
||||||
|
<p className="text-xl font-semibold">Audit Logs Retention</p>
|
||||||
|
</div>
|
||||||
|
<p className="mb-4 mt-2 max-w-2xl text-sm text-gray-400">
|
||||||
|
Set the number of days to keep your project audit logs.
|
||||||
|
</p>
|
||||||
|
<form onSubmit={handleSubmit(handleAuditLogsRetentionSubmit)} autoComplete="off">
|
||||||
|
<div className="max-w-xs">
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
defaultValue={0}
|
||||||
|
name="auditLogsRetentionDays"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
label="Number of days"
|
||||||
|
>
|
||||||
|
<Input {...field} type="number" min={1} step={1} isDisabled={!isAdmin} />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
colorSchema="secondary"
|
||||||
|
type="submit"
|
||||||
|
isLoading={isSubmitting}
|
||||||
|
disabled={!isAdmin || !isDirty}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<UpgradePlanModal
|
||||||
|
isOpen={popUp.upgradePlan.isOpen}
|
||||||
|
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||||
|
text={(popUp.upgradePlan?.data as { description: string })?.description}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1 @@
|
|||||||
|
export { AuditLogsRetentionSection } from "./AuditLogsRetentionSection";
|
@ -1,3 +1,4 @@
|
|||||||
|
import { AuditLogsRetentionSection } from "../AuditLogsRetentionSection";
|
||||||
import { AutoCapitalizationSection } from "../AutoCapitalizationSection";
|
import { AutoCapitalizationSection } from "../AutoCapitalizationSection";
|
||||||
import { BackfillSecretReferenceSecretion } from "../BackfillSecretReferenceSection";
|
import { BackfillSecretReferenceSecretion } from "../BackfillSecretReferenceSection";
|
||||||
import { DeleteProjectSection } from "../DeleteProjectSection";
|
import { DeleteProjectSection } from "../DeleteProjectSection";
|
||||||
@ -5,6 +6,7 @@ import { E2EESection } from "../E2EESection";
|
|||||||
import { EnvironmentSection } from "../EnvironmentSection";
|
import { EnvironmentSection } from "../EnvironmentSection";
|
||||||
import { PointInTimeVersionLimitSection } from "../PointInTimeVersionLimitSection";
|
import { PointInTimeVersionLimitSection } from "../PointInTimeVersionLimitSection";
|
||||||
import { ProjectNameChangeSection } from "../ProjectNameChangeSection";
|
import { ProjectNameChangeSection } from "../ProjectNameChangeSection";
|
||||||
|
import { RebuildSecretIndicesSection } from "../RebuildSecretIndicesSection/RebuildSecretIndicesSection";
|
||||||
import { SecretTagsSection } from "../SecretTagsSection";
|
import { SecretTagsSection } from "../SecretTagsSection";
|
||||||
|
|
||||||
export const ProjectGeneralTab = () => {
|
export const ProjectGeneralTab = () => {
|
||||||
@ -16,7 +18,9 @@ export const ProjectGeneralTab = () => {
|
|||||||
<AutoCapitalizationSection />
|
<AutoCapitalizationSection />
|
||||||
<E2EESection />
|
<E2EESection />
|
||||||
<PointInTimeVersionLimitSection />
|
<PointInTimeVersionLimitSection />
|
||||||
|
<AuditLogsRetentionSection />
|
||||||
<BackfillSecretReferenceSecretion />
|
<BackfillSecretReferenceSecretion />
|
||||||
|
<RebuildSecretIndicesSection />
|
||||||
<DeleteProjectSection />
|
<DeleteProjectSection />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -0,0 +1,93 @@
|
|||||||
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import {
|
||||||
|
decryptAssymmetric,
|
||||||
|
decryptSymmetric
|
||||||
|
} from "@app/components/utilities/cryptography/crypto";
|
||||||
|
import { Button } from "@app/components/v2";
|
||||||
|
import { useProjectPermission, useWorkspace } from "@app/context";
|
||||||
|
import { useToggle } from "@app/hooks";
|
||||||
|
import { useGetUserWsKey, useNameWorkspaceSecrets } from "@app/hooks/api";
|
||||||
|
import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
|
||||||
|
import { fetchWorkspaceSecrets } from "@app/hooks/api/workspace/queries";
|
||||||
|
|
||||||
|
export const RebuildSecretIndicesSection = () => {
|
||||||
|
const { currentWorkspace } = useWorkspace();
|
||||||
|
const { membership } = useProjectPermission();
|
||||||
|
const nameWorkspaceSecrets = useNameWorkspaceSecrets();
|
||||||
|
|
||||||
|
const [isIndexing, setIsIndexing] = useToggle();
|
||||||
|
const { data: decryptFileKey } = useGetUserWsKey(currentWorkspace?.id!);
|
||||||
|
|
||||||
|
if (!currentWorkspace) return null;
|
||||||
|
|
||||||
|
const onRebuildIndices = async () => {
|
||||||
|
if (!currentWorkspace?.id) return;
|
||||||
|
setIsIndexing.on();
|
||||||
|
try {
|
||||||
|
const encryptedSecrets = await fetchWorkspaceSecrets(currentWorkspace.id);
|
||||||
|
|
||||||
|
if (!currentWorkspace || !decryptFileKey) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const key = decryptAssymmetric({
|
||||||
|
ciphertext: decryptFileKey.encryptedKey,
|
||||||
|
nonce: decryptFileKey.nonce,
|
||||||
|
publicKey: decryptFileKey.sender.publicKey,
|
||||||
|
privateKey: localStorage.getItem("PRIVATE_KEY") as string
|
||||||
|
});
|
||||||
|
|
||||||
|
const secretsToUpdate = encryptedSecrets.map((encryptedSecret) => {
|
||||||
|
const secretName = decryptSymmetric({
|
||||||
|
ciphertext: encryptedSecret.secretKeyCiphertext,
|
||||||
|
iv: encryptedSecret.secretKeyIV,
|
||||||
|
tag: encryptedSecret.secretKeyTag,
|
||||||
|
key
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
secretName,
|
||||||
|
secretId: encryptedSecret.id
|
||||||
|
};
|
||||||
|
});
|
||||||
|
await nameWorkspaceSecrets.mutateAsync({
|
||||||
|
workspaceId: currentWorkspace.id,
|
||||||
|
secretsToUpdate
|
||||||
|
});
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
text: "Successfully rebuilt secret indices",
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
} finally {
|
||||||
|
setIsIndexing.off();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const isAdmin = membership.roles.includes(ProjectMembershipRole.Admin);
|
||||||
|
|
||||||
|
if (!isAdmin) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
|
<div className="flex w-full items-center justify-between">
|
||||||
|
<p className="text-xl font-semibold">Rebuild Secret Indices</p>
|
||||||
|
</div>
|
||||||
|
<p className="mb-4 mt-2 max-w-2xl text-sm text-gray-400">
|
||||||
|
This will rebuild indices of all secrets in the project.
|
||||||
|
</p>
|
||||||
|
<Button
|
||||||
|
variant="outline_bg"
|
||||||
|
isLoading={isIndexing}
|
||||||
|
onClick={onRebuildIndices}
|
||||||
|
isDisabled={!isAdmin}
|
||||||
|
>
|
||||||
|
Rebuild Secret Indices
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -6,14 +6,7 @@ import * as yup from "yup";
|
|||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
import { encryptSymmetric } from "@app/components/utilities/cryptography/crypto";
|
import { encryptSymmetric } from "@app/components/utilities/cryptography/crypto";
|
||||||
import {
|
import { Button, FormControl, Input, ModalClose, Select, SelectItem } from "@app/components/v2";
|
||||||
Button,
|
|
||||||
FormControl,
|
|
||||||
Input,
|
|
||||||
ModalClose,
|
|
||||||
Select,
|
|
||||||
SelectItem
|
|
||||||
} from "@app/components/v2";
|
|
||||||
import { useCreatePublicSharedSecret, useCreateSharedSecret } from "@app/hooks/api/secretSharing";
|
import { useCreatePublicSharedSecret, useCreateSharedSecret } from "@app/hooks/api/secretSharing";
|
||||||
|
|
||||||
const schema = yup.object({
|
const schema = yup.object({
|
||||||
@ -31,7 +24,8 @@ export const AddShareSecretForm = ({
|
|||||||
handleSubmit,
|
handleSubmit,
|
||||||
control,
|
control,
|
||||||
isSubmitting,
|
isSubmitting,
|
||||||
setNewSharedSecret
|
setNewSharedSecret,
|
||||||
|
isInputDisabled
|
||||||
}: {
|
}: {
|
||||||
isPublic: boolean;
|
isPublic: boolean;
|
||||||
inModal: boolean;
|
inModal: boolean;
|
||||||
@ -39,6 +33,7 @@ export const AddShareSecretForm = ({
|
|||||||
control: any;
|
control: any;
|
||||||
isSubmitting: boolean;
|
isSubmitting: boolean;
|
||||||
setNewSharedSecret: (value: string) => void;
|
setNewSharedSecret: (value: string) => void;
|
||||||
|
isInputDisabled?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const publicSharedSecretCreator = useCreatePublicSharedSecret();
|
const publicSharedSecretCreator = useCreatePublicSharedSecret();
|
||||||
const privateSharedSecretCreator = useCreateSharedSecret();
|
const privateSharedSecretCreator = useCreateSharedSecret();
|
||||||
@ -124,12 +119,13 @@ export const AddShareSecretForm = ({
|
|||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<form className="flex w-full flex-col items-center" onSubmit={handleSubmit(onFormSubmit)}>
|
<form className="flex w-full flex-col items-center" onSubmit={handleSubmit(onFormSubmit)}>
|
||||||
<div className={`${!inModal && "border border-mineshaft-600 bg-mineshaft-800 rounded-md p-6"}`}>
|
<div
|
||||||
|
className={`${!inModal && "rounded-md border border-mineshaft-600 bg-mineshaft-800 p-6"}`}
|
||||||
|
>
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="value"
|
name="value"
|
||||||
defaultValue=""
|
|
||||||
render={({ field, fieldState: { error } }) => (
|
render={({ field, fieldState: { error } }) => (
|
||||||
<FormControl
|
<FormControl
|
||||||
label="Shared Secret"
|
label="Shared Secret"
|
||||||
@ -137,16 +133,17 @@ export const AddShareSecretForm = ({
|
|||||||
errorText={error?.message}
|
errorText={error?.message}
|
||||||
>
|
>
|
||||||
<textarea
|
<textarea
|
||||||
|
disabled={isInputDisabled}
|
||||||
placeholder="Enter sensitive data to share via an encrypted link..."
|
placeholder="Enter sensitive data to share via an encrypted link..."
|
||||||
{...field}
|
{...field}
|
||||||
className="py-1.5 w-full h-40 placeholder:text-mineshaft-400 rounded-md transition-all group-hover:mr-2 text-bunker-300 hover:border-primary-400/30 focus:border-primary-400/50 outline-none border border-mineshaft-600 bg-mineshaft-900 px-2 min-h-[70px]"
|
className="h-40 min-h-[70px] w-full rounded-md border border-mineshaft-600 bg-mineshaft-900 py-1.5 px-2 text-bunker-300 outline-none transition-all placeholder:text-mineshaft-400 hover:border-primary-400/30 focus:border-primary-400/50 group-hover:mr-2"
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex w-full flex-row justify-center">
|
<div className="flex w-full flex-row justify-center">
|
||||||
<div className="hidden sm:block sm:w-2/6 flex">
|
<div className="flex hidden sm:block sm:w-2/6">
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="expiresAfterViews"
|
name="expiresAfterViews"
|
||||||
@ -163,12 +160,12 @@ export const AddShareSecretForm = ({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="hidden sm:flex sm:w-1/7 items-center justify-center px-2 mx-auto">
|
<div className="sm:w-1/7 mx-auto hidden items-center justify-center px-2 sm:flex">
|
||||||
<p className="px-4 text-sm text-gray-400">OR</p>
|
<p className="px-4 text-sm text-gray-400">OR</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full sm:w-3/6 flex justify-end">
|
<div className="flex w-full justify-end sm:w-3/6">
|
||||||
<div className="flex justify-start">
|
<div className="flex justify-start">
|
||||||
<div className="flex w-full pr-2 justify-center">
|
<div className="flex w-full justify-center pr-2">
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="expiresInValue"
|
name="expiresInValue"
|
||||||
|
@ -34,6 +34,7 @@ export const AddShareSecretModal = ({ popUp, handlePopUpToggle, isPublic, inModa
|
|||||||
control,
|
control,
|
||||||
reset,
|
reset,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
|
setValue,
|
||||||
formState: { isSubmitting }
|
formState: { isSubmitting }
|
||||||
} = useForm<FormData>({
|
} = useForm<FormData>({
|
||||||
resolver: yupResolver(schema)
|
resolver: yupResolver(schema)
|
||||||
@ -45,6 +46,8 @@ export const AddShareSecretModal = ({ popUp, handlePopUpToggle, isPublic, inModa
|
|||||||
initialState: false
|
initialState: false
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [isSecretInputDisabled, setIsSecretInputDisabled] = useState(false);
|
||||||
|
|
||||||
const copyUrlToClipboard = () => {
|
const copyUrlToClipboard = () => {
|
||||||
navigator.clipboard.writeText(newSharedSecret);
|
navigator.clipboard.writeText(newSharedSecret);
|
||||||
setIsUrlCopied(true);
|
setIsUrlCopied(true);
|
||||||
@ -55,6 +58,13 @@ export const AddShareSecretModal = ({ popUp, handlePopUpToggle, isPublic, inModa
|
|||||||
}
|
}
|
||||||
}, [isUrlCopied]);
|
}, [isUrlCopied]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (popUp.createSharedSecret.data) {
|
||||||
|
setValue("value", (popUp.createSharedSecret.data as { value: string }).value);
|
||||||
|
setIsSecretInputDisabled(true);
|
||||||
|
}
|
||||||
|
}, [popUp.createSharedSecret.data]);
|
||||||
|
|
||||||
// eslint-disable-next-line no-nested-ternary
|
// eslint-disable-next-line no-nested-ternary
|
||||||
return inModal ? (
|
return inModal ? (
|
||||||
<Modal
|
<Modal
|
||||||
@ -63,6 +73,7 @@ export const AddShareSecretModal = ({ popUp, handlePopUpToggle, isPublic, inModa
|
|||||||
handlePopUpToggle("createSharedSecret", open);
|
handlePopUpToggle("createSharedSecret", open);
|
||||||
reset();
|
reset();
|
||||||
setNewSharedSecret("");
|
setNewSharedSecret("");
|
||||||
|
setIsSecretInputDisabled(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ModalContent
|
<ModalContent
|
||||||
@ -77,6 +88,7 @@ export const AddShareSecretModal = ({ popUp, handlePopUpToggle, isPublic, inModa
|
|||||||
handleSubmit={handleSubmit}
|
handleSubmit={handleSubmit}
|
||||||
isSubmitting={isSubmitting}
|
isSubmitting={isSubmitting}
|
||||||
setNewSharedSecret={setNewSharedSecret}
|
setNewSharedSecret={setNewSharedSecret}
|
||||||
|
isInputDisabled={isSecretInputDisabled}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<ViewAndCopySharedSecret
|
<ViewAndCopySharedSecret
|
||||||
@ -96,6 +108,7 @@ export const AddShareSecretModal = ({ popUp, handlePopUpToggle, isPublic, inModa
|
|||||||
handleSubmit={handleSubmit}
|
handleSubmit={handleSubmit}
|
||||||
isSubmitting={isSubmitting}
|
isSubmitting={isSubmitting}
|
||||||
setNewSharedSecret={setNewSharedSecret}
|
setNewSharedSecret={setNewSharedSecret}
|
||||||
|
isInputDisabled={isSecretInputDisabled}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<ViewAndCopySharedSecret
|
<ViewAndCopySharedSecret
|
||||||
|
Reference in New Issue
Block a user