Compare commits
89 Commits
feat/resou
...
misc/add-p
Author | SHA1 | Date | |
---|---|---|---|
|
4bc9bca287 | ||
|
612c29225d | ||
|
9cde1995c7 | ||
|
3ed3856c85 | ||
|
8054d93851 | ||
|
02dc23425c | ||
|
01534c3525 | ||
|
325ce73b9f | ||
|
1639bda3f6 | ||
|
b5b91c929f | ||
|
9bc549ca8c | ||
|
cedc88d83a | ||
|
f866a810c1 | ||
|
0c034d69ac | ||
|
e29f7f656c | ||
|
858e569d4d | ||
|
5d8f32b774 | ||
|
bb71f5eb7e | ||
|
30b431a255 | ||
|
fd32118685 | ||
|
8528f3fd2a | ||
|
eb358bcafd | ||
|
ffbd29c575 | ||
|
a74f0170da | ||
|
a0fad34a6d | ||
|
f0dc5ec876 | ||
|
c2453f0c84 | ||
|
2819c8519e | ||
|
616b013b12 | ||
|
0b9d890a51 | ||
|
5ba507bc1c | ||
|
0ecc196e5d | ||
|
ddac9f7cc4 | ||
|
f5adc4d9f3 | ||
|
34354994d8 | ||
|
d7c3192099 | ||
|
1576358805 | ||
|
e6103d2d3f | ||
|
8bf8bc77c9 | ||
|
3219723149 | ||
|
74b95d92ab | ||
|
6d3793beff | ||
|
0df41f3391 | ||
|
1acac9d479 | ||
|
0cefd6f837 | ||
|
5e9dc0b98d | ||
|
f632847dc6 | ||
|
faa6d1cf40 | ||
|
7fb18870e3 | ||
|
ae841715e5 | ||
|
baac87c16a | ||
|
b726187ba3 | ||
|
d98ff32b07 | ||
|
1fa510b32f | ||
|
c57f0d8120 | ||
|
00490f2cff | ||
|
ee58f538c0 | ||
|
aa39451bc2 | ||
|
f5548b3e8c | ||
|
1190ca2d77 | ||
|
2fb60201bc | ||
|
4daaf80caa | ||
|
cf7768d8e5 | ||
|
e76d2f58ea | ||
|
36a13d182f | ||
|
8b26670d73 | ||
|
35d3581e23 | ||
|
0edf0dac98 | ||
|
a757ea22a1 | ||
|
74df374998 | ||
|
925a594a1b | ||
|
36af975594 | ||
|
ee54d460a0 | ||
|
3c32d8dd90 | ||
|
9b50d451ec | ||
|
7ede4e2cf5 | ||
|
4552f0efa4 | ||
|
0d35273857 | ||
|
5ad8dab250 | ||
|
92a80b3314 | ||
|
01dcbb0122 | ||
|
adb0819102 | ||
|
41ba111a69 | ||
|
1b48ce21be | ||
|
2f922d6343 | ||
|
e67b0540dd | ||
|
a78455fde6 | ||
|
967dac9be6 | ||
|
922b245780 |
6
.github/workflows/deployment-pipeline.yml
vendored
@@ -97,7 +97,7 @@ jobs:
|
||||
image: infisical/staging_infisical:${{ steps.commit.outputs.short }}
|
||||
environment-variables: "LOG_LEVEL=info"
|
||||
- name: Deploy to Amazon ECS service
|
||||
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
|
||||
uses: aws-actions/amazon-ecs-deploy-task-definition@v2
|
||||
with:
|
||||
task-definition: ${{ steps.render-web-container.outputs.task-definition }}
|
||||
service: infisical-core-gamma-stage
|
||||
@@ -153,7 +153,7 @@ jobs:
|
||||
image: infisical/staging_infisical:${{ steps.commit.outputs.short }}
|
||||
environment-variables: "LOG_LEVEL=info"
|
||||
- name: Deploy to Amazon ECS service
|
||||
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
|
||||
uses: aws-actions/amazon-ecs-deploy-task-definition@v2
|
||||
with:
|
||||
task-definition: ${{ steps.render-web-container.outputs.task-definition }}
|
||||
service: infisical-core-platform
|
||||
@@ -204,7 +204,7 @@ jobs:
|
||||
image: infisical/staging_infisical:${{ steps.commit.outputs.short }}
|
||||
environment-variables: "LOG_LEVEL=info"
|
||||
- name: Deploy to Amazon ECS service
|
||||
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
|
||||
uses: aws-actions/amazon-ecs-deploy-task-definition@v2
|
||||
with:
|
||||
task-definition: ${{ steps.render-web-container.outputs.task-definition }}
|
||||
service: infisical-core-platform
|
||||
|
@@ -22,6 +22,7 @@ import { registerSecretApprovalPolicyRouter } from "./secret-approval-policy-rou
|
||||
import { registerSecretApprovalRequestRouter } from "./secret-approval-request-router";
|
||||
import { registerSecretRotationProviderRouter } from "./secret-rotation-provider-router";
|
||||
import { registerSecretRotationRouter } from "./secret-rotation-router";
|
||||
import { registerSecretRouter } from "./secret-router";
|
||||
import { registerSecretScanningRouter } from "./secret-scanning-router";
|
||||
import { registerSecretVersionRouter } from "./secret-version-router";
|
||||
import { registerSnapshotRouter } from "./snapshot-router";
|
||||
@@ -92,6 +93,7 @@ export const registerV1EERoutes = async (server: FastifyZodProvider) => {
|
||||
await server.register(registerLdapRouter, { prefix: "/ldap" });
|
||||
await server.register(registerSecretScanningRouter, { prefix: "/secret-scanning" });
|
||||
await server.register(registerSecretRotationRouter, { prefix: "/secret-rotations" });
|
||||
await server.register(registerSecretRouter, { prefix: "/secrets" });
|
||||
await server.register(registerSecretVersionRouter, { prefix: "/secret" });
|
||||
await server.register(registerGroupRouter, { prefix: "/groups" });
|
||||
await server.register(registerAuditLogStreamRouter, { prefix: "/audit-log-streams" });
|
||||
|
@@ -84,7 +84,10 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
||||
samlConfig.audience = `spn:${ssoConfig.issuer}`;
|
||||
}
|
||||
}
|
||||
if (ssoConfig.authProvider === SamlProviders.GOOGLE_SAML) {
|
||||
if (
|
||||
ssoConfig.authProvider === SamlProviders.GOOGLE_SAML ||
|
||||
ssoConfig.authProvider === SamlProviders.AUTH0_SAML
|
||||
) {
|
||||
samlConfig.wantAssertionsSigned = false;
|
||||
}
|
||||
|
||||
@@ -123,7 +126,10 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
||||
`email: ${email} firstName: ${profile.firstName as string}`
|
||||
);
|
||||
|
||||
throw new Error("Invalid saml request. Missing email or first name");
|
||||
throw new BadRequestError({
|
||||
message:
|
||||
"Missing email or first name. Please double check your SAML attribute mapping for the selected provider."
|
||||
});
|
||||
}
|
||||
|
||||
const userMetadata = Object.keys(profile.attributes || {})
|
||||
|
71
backend/src/ee/routes/v1/secret-router.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import z from "zod";
|
||||
|
||||
import { ProjectPermissionActions } from "@app/ee/services/permission/project-permission";
|
||||
import { RAW_SECRETS } from "@app/lib/api-docs";
|
||||
import { removeTrailingSlash } from "@app/lib/fn";
|
||||
import { readLimit } from "@app/server/config/rateLimiter";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
|
||||
const AccessListEntrySchema = z
|
||||
.object({
|
||||
allowedActions: z.nativeEnum(ProjectPermissionActions).array(),
|
||||
id: z.string(),
|
||||
membershipId: z.string(),
|
||||
name: z.string()
|
||||
})
|
||||
.array();
|
||||
|
||||
export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
method: "GET",
|
||||
url: "/:secretName/access-list",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
description: "Get list of users, machine identities, and groups with access to a secret",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
secretName: z.string().trim().describe(RAW_SECRETS.GET_ACCESS_LIST.secretName)
|
||||
}),
|
||||
querystring: z.object({
|
||||
workspaceId: z.string().trim().describe(RAW_SECRETS.GET_ACCESS_LIST.workspaceId),
|
||||
environment: z.string().trim().describe(RAW_SECRETS.GET_ACCESS_LIST.environment),
|
||||
secretPath: z
|
||||
.string()
|
||||
.trim()
|
||||
.default("/")
|
||||
.transform(removeTrailingSlash)
|
||||
.describe(RAW_SECRETS.GET_ACCESS_LIST.secretPath)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
groups: AccessListEntrySchema,
|
||||
identities: AccessListEntrySchema,
|
||||
users: AccessListEntrySchema
|
||||
})
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
handler: async (req) => {
|
||||
const { secretName } = req.params;
|
||||
const { secretPath, environment, workspaceId: projectId } = req.query;
|
||||
|
||||
return server.services.secret.getSecretAccessList({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
secretPath,
|
||||
environment,
|
||||
projectId,
|
||||
secretName
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
@@ -24,6 +24,7 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
|
||||
rbac: false,
|
||||
customRateLimits: false,
|
||||
customAlerts: false,
|
||||
secretAccessInsights: false,
|
||||
auditLogs: false,
|
||||
auditLogsRetentionDays: 0,
|
||||
auditLogStreams: false,
|
||||
|
@@ -246,8 +246,7 @@ export const licenseServiceFactory = ({
|
||||
};
|
||||
|
||||
const getOrgPlan = async ({ orgId, actor, actorId, actorOrgId, actorAuthMethod, projectId }: TOrgPlanDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
|
||||
await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||
const plan = await getPlan(orgId, projectId);
|
||||
return plan;
|
||||
};
|
||||
|
@@ -48,6 +48,7 @@ export type TFeatureSet = {
|
||||
samlSSO: false;
|
||||
hsm: false;
|
||||
oidcSSO: false;
|
||||
secretAccessInsights: false;
|
||||
scim: false;
|
||||
ldap: false;
|
||||
groups: false;
|
||||
|
@@ -125,6 +125,404 @@ export const permissionDALFactory = (db: TDbClient) => {
|
||||
}
|
||||
};
|
||||
|
||||
const getProjectGroupPermissions = async (projectId: string) => {
|
||||
try {
|
||||
const docs = await db
|
||||
.replicaNode()(TableName.GroupProjectMembership)
|
||||
.join(TableName.Groups, `${TableName.Groups}.id`, `${TableName.GroupProjectMembership}.groupId`)
|
||||
.join(
|
||||
TableName.GroupProjectMembershipRole,
|
||||
`${TableName.GroupProjectMembershipRole}.projectMembershipId`,
|
||||
`${TableName.GroupProjectMembership}.id`
|
||||
)
|
||||
.leftJoin<TProjectRoles>(
|
||||
{ groupCustomRoles: TableName.ProjectRoles },
|
||||
`${TableName.GroupProjectMembershipRole}.customRoleId`,
|
||||
`groupCustomRoles.id`
|
||||
)
|
||||
.where(`${TableName.GroupProjectMembership}.projectId`, "=", projectId)
|
||||
.select(
|
||||
db.ref("id").withSchema(TableName.GroupProjectMembership).as("membershipId"),
|
||||
db.ref("id").withSchema(TableName.Groups).as("groupId"),
|
||||
db.ref("name").withSchema(TableName.Groups).as("groupName"),
|
||||
db.ref("slug").withSchema("groupCustomRoles").as("groupProjectMembershipRoleCustomRoleSlug"),
|
||||
db.ref("permissions").withSchema("groupCustomRoles").as("groupProjectMembershipRolePermission"),
|
||||
db.ref("id").withSchema(TableName.GroupProjectMembershipRole).as("groupProjectMembershipRoleId"),
|
||||
db.ref("role").withSchema(TableName.GroupProjectMembershipRole).as("groupProjectMembershipRole"),
|
||||
db
|
||||
.ref("customRoleId")
|
||||
.withSchema(TableName.GroupProjectMembershipRole)
|
||||
.as("groupProjectMembershipRoleCustomRoleId"),
|
||||
db
|
||||
.ref("isTemporary")
|
||||
.withSchema(TableName.GroupProjectMembershipRole)
|
||||
.as("groupProjectMembershipRoleIsTemporary"),
|
||||
db
|
||||
.ref("temporaryMode")
|
||||
.withSchema(TableName.GroupProjectMembershipRole)
|
||||
.as("groupProjectMembershipRoleTemporaryMode"),
|
||||
db
|
||||
.ref("temporaryRange")
|
||||
.withSchema(TableName.GroupProjectMembershipRole)
|
||||
.as("groupProjectMembershipRoleTemporaryRange"),
|
||||
db
|
||||
.ref("temporaryAccessStartTime")
|
||||
.withSchema(TableName.GroupProjectMembershipRole)
|
||||
.as("groupProjectMembershipRoleTemporaryAccessStartTime"),
|
||||
db
|
||||
.ref("temporaryAccessEndTime")
|
||||
.withSchema(TableName.GroupProjectMembershipRole)
|
||||
.as("groupProjectMembershipRoleTemporaryAccessEndTime")
|
||||
);
|
||||
|
||||
const groupPermissions = sqlNestRelationships({
|
||||
data: docs,
|
||||
key: "groupId",
|
||||
parentMapper: ({ groupId, groupName, membershipId }) => ({
|
||||
groupId,
|
||||
username: groupName,
|
||||
id: membershipId
|
||||
}),
|
||||
childrenMapper: [
|
||||
{
|
||||
key: "groupProjectMembershipRoleId",
|
||||
label: "groupRoles" as const,
|
||||
mapper: ({
|
||||
groupProjectMembershipRoleId,
|
||||
groupProjectMembershipRole,
|
||||
groupProjectMembershipRolePermission,
|
||||
groupProjectMembershipRoleCustomRoleSlug,
|
||||
groupProjectMembershipRoleIsTemporary,
|
||||
groupProjectMembershipRoleTemporaryMode,
|
||||
groupProjectMembershipRoleTemporaryAccessEndTime,
|
||||
groupProjectMembershipRoleTemporaryAccessStartTime,
|
||||
groupProjectMembershipRoleTemporaryRange
|
||||
}) => ({
|
||||
id: groupProjectMembershipRoleId,
|
||||
role: groupProjectMembershipRole,
|
||||
customRoleSlug: groupProjectMembershipRoleCustomRoleSlug,
|
||||
permissions: groupProjectMembershipRolePermission,
|
||||
temporaryRange: groupProjectMembershipRoleTemporaryRange,
|
||||
temporaryMode: groupProjectMembershipRoleTemporaryMode,
|
||||
temporaryAccessStartTime: groupProjectMembershipRoleTemporaryAccessStartTime,
|
||||
temporaryAccessEndTime: groupProjectMembershipRoleTemporaryAccessEndTime,
|
||||
isTemporary: groupProjectMembershipRoleIsTemporary
|
||||
})
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
return groupPermissions
|
||||
.map((groupPermission) => {
|
||||
if (!groupPermission) return undefined;
|
||||
|
||||
const activeGroupRoles =
|
||||
groupPermission?.groupRoles?.filter(
|
||||
({ isTemporary, temporaryAccessEndTime }) =>
|
||||
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
|
||||
) ?? [];
|
||||
|
||||
return {
|
||||
...groupPermission,
|
||||
roles: activeGroupRoles
|
||||
};
|
||||
})
|
||||
.filter((item): item is NonNullable<typeof item> => Boolean(item));
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "GetProjectGroupPermissions" });
|
||||
}
|
||||
};
|
||||
|
||||
const getProjectUserPermissions = async (projectId: string) => {
|
||||
try {
|
||||
const docs = await db
|
||||
.replicaNode()(TableName.Users)
|
||||
.where("isGhost", "=", false)
|
||||
.leftJoin(TableName.GroupProjectMembership, (queryBuilder) => {
|
||||
void queryBuilder.on(`${TableName.GroupProjectMembership}.projectId`, db.raw("?", [projectId]));
|
||||
})
|
||||
.leftJoin(
|
||||
TableName.GroupProjectMembershipRole,
|
||||
`${TableName.GroupProjectMembershipRole}.projectMembershipId`,
|
||||
`${TableName.GroupProjectMembership}.id`
|
||||
)
|
||||
.leftJoin<TProjectRoles>(
|
||||
{ groupCustomRoles: TableName.ProjectRoles },
|
||||
`${TableName.GroupProjectMembershipRole}.customRoleId`,
|
||||
`groupCustomRoles.id`
|
||||
)
|
||||
.join(TableName.ProjectMembership, (queryBuilder) => {
|
||||
void queryBuilder
|
||||
.on(`${TableName.ProjectMembership}.projectId`, db.raw("?", [projectId]))
|
||||
.andOn(`${TableName.ProjectMembership}.userId`, `${TableName.Users}.id`);
|
||||
})
|
||||
.leftJoin(
|
||||
TableName.ProjectUserMembershipRole,
|
||||
`${TableName.ProjectUserMembershipRole}.projectMembershipId`,
|
||||
`${TableName.ProjectMembership}.id`
|
||||
)
|
||||
.leftJoin(
|
||||
TableName.ProjectRoles,
|
||||
`${TableName.ProjectUserMembershipRole}.customRoleId`,
|
||||
`${TableName.ProjectRoles}.id`
|
||||
)
|
||||
.leftJoin(TableName.ProjectUserAdditionalPrivilege, (queryBuilder) => {
|
||||
void queryBuilder
|
||||
.on(`${TableName.ProjectUserAdditionalPrivilege}.projectId`, db.raw("?", [projectId]))
|
||||
.andOn(`${TableName.ProjectUserAdditionalPrivilege}.userId`, `${TableName.Users}.id`);
|
||||
})
|
||||
.join<TProjects>(TableName.Project, `${TableName.Project}.id`, db.raw("?", [projectId]))
|
||||
.join(TableName.Organization, `${TableName.Project}.orgId`, `${TableName.Organization}.id`)
|
||||
.leftJoin(TableName.IdentityMetadata, (queryBuilder) => {
|
||||
void queryBuilder
|
||||
.on(`${TableName.Users}.id`, `${TableName.IdentityMetadata}.userId`)
|
||||
.andOn(`${TableName.Organization}.id`, `${TableName.IdentityMetadata}.orgId`);
|
||||
})
|
||||
.select(
|
||||
db.ref("id").withSchema(TableName.Users).as("userId"),
|
||||
db.ref("username").withSchema(TableName.Users).as("username"),
|
||||
// groups specific
|
||||
db.ref("id").withSchema(TableName.GroupProjectMembership).as("groupMembershipId"),
|
||||
db.ref("createdAt").withSchema(TableName.GroupProjectMembership).as("groupMembershipCreatedAt"),
|
||||
db.ref("updatedAt").withSchema(TableName.GroupProjectMembership).as("groupMembershipUpdatedAt"),
|
||||
db.ref("slug").withSchema("groupCustomRoles").as("userGroupProjectMembershipRoleCustomRoleSlug"),
|
||||
db.ref("permissions").withSchema("groupCustomRoles").as("userGroupProjectMembershipRolePermission"),
|
||||
db.ref("id").withSchema(TableName.GroupProjectMembershipRole).as("userGroupProjectMembershipRoleId"),
|
||||
db.ref("role").withSchema(TableName.GroupProjectMembershipRole).as("userGroupProjectMembershipRole"),
|
||||
db
|
||||
.ref("customRoleId")
|
||||
.withSchema(TableName.GroupProjectMembershipRole)
|
||||
.as("userGroupProjectMembershipRoleCustomRoleId"),
|
||||
db
|
||||
.ref("isTemporary")
|
||||
.withSchema(TableName.GroupProjectMembershipRole)
|
||||
.as("userGroupProjectMembershipRoleIsTemporary"),
|
||||
db
|
||||
.ref("temporaryMode")
|
||||
.withSchema(TableName.GroupProjectMembershipRole)
|
||||
.as("userGroupProjectMembershipRoleTemporaryMode"),
|
||||
db
|
||||
.ref("temporaryRange")
|
||||
.withSchema(TableName.GroupProjectMembershipRole)
|
||||
.as("userGroupProjectMembershipRoleTemporaryRange"),
|
||||
db
|
||||
.ref("temporaryAccessStartTime")
|
||||
.withSchema(TableName.GroupProjectMembershipRole)
|
||||
.as("userGroupProjectMembershipRoleTemporaryAccessStartTime"),
|
||||
db
|
||||
.ref("temporaryAccessEndTime")
|
||||
.withSchema(TableName.GroupProjectMembershipRole)
|
||||
.as("userGroupProjectMembershipRoleTemporaryAccessEndTime"),
|
||||
// user specific
|
||||
db.ref("id").withSchema(TableName.ProjectMembership).as("membershipId"),
|
||||
db.ref("createdAt").withSchema(TableName.ProjectMembership).as("membershipCreatedAt"),
|
||||
db.ref("updatedAt").withSchema(TableName.ProjectMembership).as("membershipUpdatedAt"),
|
||||
db.ref("slug").withSchema(TableName.ProjectRoles).as("userProjectMembershipRoleCustomRoleSlug"),
|
||||
db.ref("permissions").withSchema(TableName.ProjectRoles).as("userProjectCustomRolePermission"),
|
||||
db.ref("id").withSchema(TableName.ProjectUserMembershipRole).as("userProjectMembershipRoleId"),
|
||||
db.ref("role").withSchema(TableName.ProjectUserMembershipRole).as("userProjectMembershipRole"),
|
||||
db
|
||||
.ref("temporaryMode")
|
||||
.withSchema(TableName.ProjectUserMembershipRole)
|
||||
.as("userProjectMembershipRoleTemporaryMode"),
|
||||
db
|
||||
.ref("isTemporary")
|
||||
.withSchema(TableName.ProjectUserMembershipRole)
|
||||
.as("userProjectMembershipRoleIsTemporary"),
|
||||
db
|
||||
.ref("temporaryRange")
|
||||
.withSchema(TableName.ProjectUserMembershipRole)
|
||||
.as("userProjectMembershipRoleTemporaryRange"),
|
||||
db
|
||||
.ref("temporaryAccessStartTime")
|
||||
.withSchema(TableName.ProjectUserMembershipRole)
|
||||
.as("userProjectMembershipRoleTemporaryAccessStartTime"),
|
||||
db
|
||||
.ref("temporaryAccessEndTime")
|
||||
.withSchema(TableName.ProjectUserMembershipRole)
|
||||
.as("userProjectMembershipRoleTemporaryAccessEndTime"),
|
||||
db.ref("id").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userAdditionalPrivilegesId"),
|
||||
db
|
||||
.ref("permissions")
|
||||
.withSchema(TableName.ProjectUserAdditionalPrivilege)
|
||||
.as("userAdditionalPrivilegesPermissions"),
|
||||
db
|
||||
.ref("temporaryMode")
|
||||
.withSchema(TableName.ProjectUserAdditionalPrivilege)
|
||||
.as("userAdditionalPrivilegesTemporaryMode"),
|
||||
db
|
||||
.ref("isTemporary")
|
||||
.withSchema(TableName.ProjectUserAdditionalPrivilege)
|
||||
.as("userAdditionalPrivilegesIsTemporary"),
|
||||
db
|
||||
.ref("temporaryRange")
|
||||
.withSchema(TableName.ProjectUserAdditionalPrivilege)
|
||||
.as("userAdditionalPrivilegesTemporaryRange"),
|
||||
db.ref("userId").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userAdditionalPrivilegesUserId"),
|
||||
db
|
||||
.ref("temporaryAccessStartTime")
|
||||
.withSchema(TableName.ProjectUserAdditionalPrivilege)
|
||||
.as("userAdditionalPrivilegesTemporaryAccessStartTime"),
|
||||
db
|
||||
.ref("temporaryAccessEndTime")
|
||||
.withSchema(TableName.ProjectUserAdditionalPrivilege)
|
||||
.as("userAdditionalPrivilegesTemporaryAccessEndTime"),
|
||||
// general
|
||||
db.ref("id").withSchema(TableName.IdentityMetadata).as("metadataId"),
|
||||
db.ref("key").withSchema(TableName.IdentityMetadata).as("metadataKey"),
|
||||
db.ref("value").withSchema(TableName.IdentityMetadata).as("metadataValue"),
|
||||
db.ref("authEnforced").withSchema(TableName.Organization).as("orgAuthEnforced"),
|
||||
db.ref("orgId").withSchema(TableName.Project),
|
||||
db.ref("type").withSchema(TableName.Project).as("projectType"),
|
||||
db.ref("id").withSchema(TableName.Project).as("projectId")
|
||||
);
|
||||
|
||||
const userPermissions = sqlNestRelationships({
|
||||
data: docs,
|
||||
key: "userId",
|
||||
parentMapper: ({
|
||||
orgId,
|
||||
username,
|
||||
orgAuthEnforced,
|
||||
membershipId,
|
||||
groupMembershipId,
|
||||
membershipCreatedAt,
|
||||
groupMembershipCreatedAt,
|
||||
groupMembershipUpdatedAt,
|
||||
membershipUpdatedAt,
|
||||
projectType,
|
||||
userId
|
||||
}) => ({
|
||||
orgId,
|
||||
orgAuthEnforced,
|
||||
userId,
|
||||
projectId,
|
||||
username,
|
||||
projectType,
|
||||
id: membershipId || groupMembershipId,
|
||||
createdAt: membershipCreatedAt || groupMembershipCreatedAt,
|
||||
updatedAt: membershipUpdatedAt || groupMembershipUpdatedAt
|
||||
}),
|
||||
childrenMapper: [
|
||||
{
|
||||
key: "userGroupProjectMembershipRoleId",
|
||||
label: "userGroupRoles" as const,
|
||||
mapper: ({
|
||||
userGroupProjectMembershipRoleId,
|
||||
userGroupProjectMembershipRole,
|
||||
userGroupProjectMembershipRolePermission,
|
||||
userGroupProjectMembershipRoleCustomRoleSlug,
|
||||
userGroupProjectMembershipRoleIsTemporary,
|
||||
userGroupProjectMembershipRoleTemporaryMode,
|
||||
userGroupProjectMembershipRoleTemporaryAccessEndTime,
|
||||
userGroupProjectMembershipRoleTemporaryAccessStartTime,
|
||||
userGroupProjectMembershipRoleTemporaryRange
|
||||
}) => ({
|
||||
id: userGroupProjectMembershipRoleId,
|
||||
role: userGroupProjectMembershipRole,
|
||||
customRoleSlug: userGroupProjectMembershipRoleCustomRoleSlug,
|
||||
permissions: userGroupProjectMembershipRolePermission,
|
||||
temporaryRange: userGroupProjectMembershipRoleTemporaryRange,
|
||||
temporaryMode: userGroupProjectMembershipRoleTemporaryMode,
|
||||
temporaryAccessStartTime: userGroupProjectMembershipRoleTemporaryAccessStartTime,
|
||||
temporaryAccessEndTime: userGroupProjectMembershipRoleTemporaryAccessEndTime,
|
||||
isTemporary: userGroupProjectMembershipRoleIsTemporary
|
||||
})
|
||||
},
|
||||
{
|
||||
key: "userProjectMembershipRoleId",
|
||||
label: "projectMembershipRoles" as const,
|
||||
mapper: ({
|
||||
userProjectMembershipRoleId,
|
||||
userProjectMembershipRole,
|
||||
userProjectCustomRolePermission,
|
||||
userProjectMembershipRoleIsTemporary,
|
||||
userProjectMembershipRoleTemporaryMode,
|
||||
userProjectMembershipRoleTemporaryRange,
|
||||
userProjectMembershipRoleTemporaryAccessEndTime,
|
||||
userProjectMembershipRoleTemporaryAccessStartTime,
|
||||
userProjectMembershipRoleCustomRoleSlug
|
||||
}) => ({
|
||||
id: userProjectMembershipRoleId,
|
||||
role: userProjectMembershipRole,
|
||||
customRoleSlug: userProjectMembershipRoleCustomRoleSlug,
|
||||
permissions: userProjectCustomRolePermission,
|
||||
temporaryRange: userProjectMembershipRoleTemporaryRange,
|
||||
temporaryMode: userProjectMembershipRoleTemporaryMode,
|
||||
temporaryAccessStartTime: userProjectMembershipRoleTemporaryAccessStartTime,
|
||||
temporaryAccessEndTime: userProjectMembershipRoleTemporaryAccessEndTime,
|
||||
isTemporary: userProjectMembershipRoleIsTemporary
|
||||
})
|
||||
},
|
||||
{
|
||||
key: "userAdditionalPrivilegesId",
|
||||
label: "additionalPrivileges" as const,
|
||||
mapper: ({
|
||||
userAdditionalPrivilegesId,
|
||||
userAdditionalPrivilegesPermissions,
|
||||
userAdditionalPrivilegesIsTemporary,
|
||||
userAdditionalPrivilegesTemporaryMode,
|
||||
userAdditionalPrivilegesTemporaryRange,
|
||||
userAdditionalPrivilegesTemporaryAccessEndTime,
|
||||
userAdditionalPrivilegesTemporaryAccessStartTime
|
||||
}) => ({
|
||||
id: userAdditionalPrivilegesId,
|
||||
permissions: userAdditionalPrivilegesPermissions,
|
||||
temporaryRange: userAdditionalPrivilegesTemporaryRange,
|
||||
temporaryMode: userAdditionalPrivilegesTemporaryMode,
|
||||
temporaryAccessStartTime: userAdditionalPrivilegesTemporaryAccessStartTime,
|
||||
temporaryAccessEndTime: userAdditionalPrivilegesTemporaryAccessEndTime,
|
||||
isTemporary: userAdditionalPrivilegesIsTemporary
|
||||
})
|
||||
},
|
||||
{
|
||||
key: "metadataId",
|
||||
label: "metadata" as const,
|
||||
mapper: ({ metadataKey, metadataValue, metadataId }) => ({
|
||||
id: metadataId,
|
||||
key: metadataKey,
|
||||
value: metadataValue
|
||||
})
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
return userPermissions
|
||||
.map((userPermission) => {
|
||||
if (!userPermission) return undefined;
|
||||
if (!userPermission?.userGroupRoles?.[0] && !userPermission?.projectMembershipRoles?.[0]) return undefined;
|
||||
|
||||
// when introducting cron mode change it here
|
||||
const activeRoles =
|
||||
userPermission?.projectMembershipRoles?.filter(
|
||||
({ isTemporary, temporaryAccessEndTime }) =>
|
||||
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
|
||||
) ?? [];
|
||||
|
||||
const activeGroupRoles =
|
||||
userPermission?.userGroupRoles?.filter(
|
||||
({ isTemporary, temporaryAccessEndTime }) =>
|
||||
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
|
||||
) ?? [];
|
||||
|
||||
const activeAdditionalPrivileges =
|
||||
userPermission?.additionalPrivileges?.filter(
|
||||
({ isTemporary, temporaryAccessEndTime }) =>
|
||||
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
|
||||
) ?? [];
|
||||
|
||||
return {
|
||||
...userPermission,
|
||||
roles: [...activeRoles, ...activeGroupRoles],
|
||||
additionalPrivileges: activeAdditionalPrivileges
|
||||
};
|
||||
})
|
||||
.filter((item): item is NonNullable<typeof item> => Boolean(item));
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "GetProjectUserPermissions" });
|
||||
}
|
||||
};
|
||||
|
||||
const getProjectPermission = async (userId: string, projectId: string) => {
|
||||
try {
|
||||
const subQueryUserGroups = db(TableName.UserGroupMembership).where("userId", userId).select("groupId");
|
||||
@@ -414,6 +812,163 @@ export const permissionDALFactory = (db: TDbClient) => {
|
||||
}
|
||||
};
|
||||
|
||||
const getProjectIdentityPermissions = async (projectId: string) => {
|
||||
try {
|
||||
const docs = await db
|
||||
.replicaNode()(TableName.IdentityProjectMembership)
|
||||
.join(
|
||||
TableName.IdentityProjectMembershipRole,
|
||||
`${TableName.IdentityProjectMembershipRole}.projectMembershipId`,
|
||||
`${TableName.IdentityProjectMembership}.id`
|
||||
)
|
||||
.join(TableName.Identity, `${TableName.Identity}.id`, `${TableName.IdentityProjectMembership}.identityId`)
|
||||
.leftJoin(
|
||||
TableName.ProjectRoles,
|
||||
`${TableName.IdentityProjectMembershipRole}.customRoleId`,
|
||||
`${TableName.ProjectRoles}.id`
|
||||
)
|
||||
.leftJoin(
|
||||
TableName.IdentityProjectAdditionalPrivilege,
|
||||
`${TableName.IdentityProjectAdditionalPrivilege}.projectMembershipId`,
|
||||
`${TableName.IdentityProjectMembership}.id`
|
||||
)
|
||||
.join(
|
||||
// Join the Project table to later select orgId
|
||||
TableName.Project,
|
||||
`${TableName.IdentityProjectMembership}.projectId`,
|
||||
`${TableName.Project}.id`
|
||||
)
|
||||
.leftJoin(TableName.IdentityMetadata, (queryBuilder) => {
|
||||
void queryBuilder
|
||||
.on(`${TableName.Identity}.id`, `${TableName.IdentityMetadata}.identityId`)
|
||||
.andOn(`${TableName.Project}.orgId`, `${TableName.IdentityMetadata}.orgId`);
|
||||
})
|
||||
.where(`${TableName.IdentityProjectMembership}.projectId`, projectId)
|
||||
.select(selectAllTableCols(TableName.IdentityProjectMembershipRole))
|
||||
.select(
|
||||
db.ref("id").withSchema(TableName.IdentityProjectMembership).as("membershipId"),
|
||||
db.ref("id").withSchema(TableName.Identity).as("identityId"),
|
||||
db.ref("name").withSchema(TableName.Identity).as("identityName"),
|
||||
db.ref("orgId").withSchema(TableName.Project).as("orgId"), // Now you can select orgId from Project
|
||||
db.ref("type").withSchema(TableName.Project).as("projectType"),
|
||||
db.ref("createdAt").withSchema(TableName.IdentityProjectMembership).as("membershipCreatedAt"),
|
||||
db.ref("updatedAt").withSchema(TableName.IdentityProjectMembership).as("membershipUpdatedAt"),
|
||||
db.ref("slug").withSchema(TableName.ProjectRoles).as("customRoleSlug"),
|
||||
db.ref("permissions").withSchema(TableName.ProjectRoles),
|
||||
db.ref("id").withSchema(TableName.IdentityProjectAdditionalPrivilege).as("identityApId"),
|
||||
db.ref("permissions").withSchema(TableName.IdentityProjectAdditionalPrivilege).as("identityApPermissions"),
|
||||
db
|
||||
.ref("temporaryMode")
|
||||
.withSchema(TableName.IdentityProjectAdditionalPrivilege)
|
||||
.as("identityApTemporaryMode"),
|
||||
db.ref("isTemporary").withSchema(TableName.IdentityProjectAdditionalPrivilege).as("identityApIsTemporary"),
|
||||
db
|
||||
.ref("temporaryRange")
|
||||
.withSchema(TableName.IdentityProjectAdditionalPrivilege)
|
||||
.as("identityApTemporaryRange"),
|
||||
db
|
||||
.ref("temporaryAccessStartTime")
|
||||
.withSchema(TableName.IdentityProjectAdditionalPrivilege)
|
||||
.as("identityApTemporaryAccessStartTime"),
|
||||
db
|
||||
.ref("temporaryAccessEndTime")
|
||||
.withSchema(TableName.IdentityProjectAdditionalPrivilege)
|
||||
.as("identityApTemporaryAccessEndTime"),
|
||||
db.ref("id").withSchema(TableName.IdentityMetadata).as("metadataId"),
|
||||
db.ref("key").withSchema(TableName.IdentityMetadata).as("metadataKey"),
|
||||
db.ref("value").withSchema(TableName.IdentityMetadata).as("metadataValue")
|
||||
);
|
||||
|
||||
const permissions = sqlNestRelationships({
|
||||
data: docs,
|
||||
key: "identityId",
|
||||
parentMapper: ({
|
||||
membershipId,
|
||||
membershipCreatedAt,
|
||||
membershipUpdatedAt,
|
||||
orgId,
|
||||
identityName,
|
||||
projectType,
|
||||
identityId
|
||||
}) => ({
|
||||
id: membershipId,
|
||||
identityId,
|
||||
username: identityName,
|
||||
projectId,
|
||||
createdAt: membershipCreatedAt,
|
||||
updatedAt: membershipUpdatedAt,
|
||||
orgId,
|
||||
projectType,
|
||||
// just a prefilled value
|
||||
orgAuthEnforced: false
|
||||
}),
|
||||
childrenMapper: [
|
||||
{
|
||||
key: "id",
|
||||
label: "roles" as const,
|
||||
mapper: (data) =>
|
||||
IdentityProjectMembershipRoleSchema.extend({
|
||||
permissions: z.unknown(),
|
||||
customRoleSlug: z.string().optional().nullable()
|
||||
}).parse(data)
|
||||
},
|
||||
{
|
||||
key: "identityApId",
|
||||
label: "additionalPrivileges" as const,
|
||||
mapper: ({
|
||||
identityApId,
|
||||
identityApPermissions,
|
||||
identityApIsTemporary,
|
||||
identityApTemporaryMode,
|
||||
identityApTemporaryRange,
|
||||
identityApTemporaryAccessEndTime,
|
||||
identityApTemporaryAccessStartTime
|
||||
}) => ({
|
||||
id: identityApId,
|
||||
permissions: identityApPermissions,
|
||||
temporaryRange: identityApTemporaryRange,
|
||||
temporaryMode: identityApTemporaryMode,
|
||||
temporaryAccessEndTime: identityApTemporaryAccessEndTime,
|
||||
temporaryAccessStartTime: identityApTemporaryAccessStartTime,
|
||||
isTemporary: identityApIsTemporary
|
||||
})
|
||||
},
|
||||
{
|
||||
key: "metadataId",
|
||||
label: "metadata" as const,
|
||||
mapper: ({ metadataKey, metadataValue, metadataId }) => ({
|
||||
id: metadataId,
|
||||
key: metadataKey,
|
||||
value: metadataValue
|
||||
})
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
return permissions
|
||||
.map((permission) => {
|
||||
if (!permission) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// when introducting cron mode change it here
|
||||
const activeRoles = permission?.roles.filter(
|
||||
({ isTemporary, temporaryAccessEndTime }) =>
|
||||
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
|
||||
);
|
||||
const activeAdditionalPrivileges = permission?.additionalPrivileges?.filter(
|
||||
({ isTemporary, temporaryAccessEndTime }) =>
|
||||
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
|
||||
);
|
||||
|
||||
return { ...permission, roles: activeRoles, additionalPrivileges: activeAdditionalPrivileges };
|
||||
})
|
||||
.filter((item): item is NonNullable<typeof item> => Boolean(item));
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "GetProjectIdentityPermissions" });
|
||||
}
|
||||
};
|
||||
|
||||
const getProjectIdentityPermission = async (identityId: string, projectId: string) => {
|
||||
try {
|
||||
const docs = await db
|
||||
@@ -568,6 +1123,9 @@ export const permissionDALFactory = (db: TDbClient) => {
|
||||
getOrgPermission,
|
||||
getOrgIdentityPermission,
|
||||
getProjectPermission,
|
||||
getProjectIdentityPermission
|
||||
getProjectIdentityPermission,
|
||||
getProjectUserPermissions,
|
||||
getProjectIdentityPermissions,
|
||||
getProjectGroupPermissions
|
||||
};
|
||||
};
|
||||
|
@@ -405,6 +405,123 @@ export const permissionServiceFactory = ({
|
||||
ForbidOnInvalidProjectType: (type: ProjectType) => void;
|
||||
};
|
||||
|
||||
const getProjectPermissions = async (projectId: string) => {
|
||||
// fetch user permissions
|
||||
const rawUserProjectPermissions = await permissionDAL.getProjectUserPermissions(projectId);
|
||||
const userPermissions = rawUserProjectPermissions.map((userProjectPermission) => {
|
||||
const rolePermissions =
|
||||
userProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || [];
|
||||
const additionalPrivileges =
|
||||
userProjectPermission.additionalPrivileges?.map(({ permissions }) => ({
|
||||
role: ProjectMembershipRole.Custom,
|
||||
permissions
|
||||
})) || [];
|
||||
|
||||
const rules = buildProjectPermissionRules(rolePermissions.concat(additionalPrivileges));
|
||||
const templatedRules = handlebars.compile(JSON.stringify(rules), { data: false });
|
||||
const metadataKeyValuePair = escapeHandlebarsMissingMetadata(
|
||||
objectify(
|
||||
userProjectPermission.metadata,
|
||||
(i) => i.key,
|
||||
(i) => i.value
|
||||
)
|
||||
);
|
||||
const interpolateRules = templatedRules(
|
||||
{
|
||||
identity: {
|
||||
id: userProjectPermission.userId,
|
||||
username: userProjectPermission.username,
|
||||
metadata: metadataKeyValuePair
|
||||
}
|
||||
},
|
||||
{ data: false }
|
||||
);
|
||||
const permission = createMongoAbility<ProjectPermissionSet>(
|
||||
JSON.parse(interpolateRules) as RawRuleOf<MongoAbility<ProjectPermissionSet>>[],
|
||||
{
|
||||
conditionsMatcher
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
permission,
|
||||
id: userProjectPermission.userId,
|
||||
name: userProjectPermission.username,
|
||||
membershipId: userProjectPermission.id
|
||||
};
|
||||
});
|
||||
|
||||
// fetch identity permissions
|
||||
const rawIdentityProjectPermissions = await permissionDAL.getProjectIdentityPermissions(projectId);
|
||||
const identityPermissions = rawIdentityProjectPermissions.map((identityProjectPermission) => {
|
||||
const rolePermissions =
|
||||
identityProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || [];
|
||||
const additionalPrivileges =
|
||||
identityProjectPermission.additionalPrivileges?.map(({ permissions }) => ({
|
||||
role: ProjectMembershipRole.Custom,
|
||||
permissions
|
||||
})) || [];
|
||||
|
||||
const rules = buildProjectPermissionRules(rolePermissions.concat(additionalPrivileges));
|
||||
const templatedRules = handlebars.compile(JSON.stringify(rules), { data: false });
|
||||
const metadataKeyValuePair = escapeHandlebarsMissingMetadata(
|
||||
objectify(
|
||||
identityProjectPermission.metadata,
|
||||
(i) => i.key,
|
||||
(i) => i.value
|
||||
)
|
||||
);
|
||||
|
||||
const interpolateRules = templatedRules(
|
||||
{
|
||||
identity: {
|
||||
id: identityProjectPermission.identityId,
|
||||
username: identityProjectPermission.username,
|
||||
metadata: metadataKeyValuePair
|
||||
}
|
||||
},
|
||||
{ data: false }
|
||||
);
|
||||
const permission = createMongoAbility<ProjectPermissionSet>(
|
||||
JSON.parse(interpolateRules) as RawRuleOf<MongoAbility<ProjectPermissionSet>>[],
|
||||
{
|
||||
conditionsMatcher
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
permission,
|
||||
id: identityProjectPermission.identityId,
|
||||
name: identityProjectPermission.username,
|
||||
membershipId: identityProjectPermission.id
|
||||
};
|
||||
});
|
||||
|
||||
// fetch group permissions
|
||||
const rawGroupProjectPermissions = await permissionDAL.getProjectGroupPermissions(projectId);
|
||||
const groupPermissions = rawGroupProjectPermissions.map((groupProjectPermission) => {
|
||||
const rolePermissions =
|
||||
groupProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || [];
|
||||
const rules = buildProjectPermissionRules(rolePermissions);
|
||||
const permission = createMongoAbility<ProjectPermissionSet>(rules, {
|
||||
conditionsMatcher
|
||||
});
|
||||
|
||||
return {
|
||||
permission,
|
||||
id: groupProjectPermission.groupId,
|
||||
name: groupProjectPermission.username,
|
||||
membershipId: groupProjectPermission.id
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
userPermissions,
|
||||
identityPermissions,
|
||||
groupPermissions
|
||||
};
|
||||
};
|
||||
|
||||
const getProjectPermission = async <T extends ActorType>(
|
||||
type: T,
|
||||
id: string,
|
||||
@@ -455,6 +572,7 @@ export const permissionServiceFactory = ({
|
||||
getOrgPermission,
|
||||
getUserProjectPermission,
|
||||
getProjectPermission,
|
||||
getProjectPermissions,
|
||||
getOrgPermissionByRole,
|
||||
getProjectPermissionByRole,
|
||||
buildOrgPermission,
|
||||
|
@@ -6,7 +6,8 @@ export enum SamlProviders {
|
||||
AZURE_SAML = "azure-saml",
|
||||
JUMPCLOUD_SAML = "jumpcloud-saml",
|
||||
GOOGLE_SAML = "google-saml",
|
||||
KEYCLOAK_SAML = "keycloak-saml"
|
||||
KEYCLOAK_SAML = "keycloak-saml",
|
||||
AUTH0_SAML = "auth0-saml"
|
||||
}
|
||||
|
||||
export type TCreateSamlCfgDTO = {
|
||||
|
@@ -741,6 +741,12 @@ export const RAW_SECRETS = {
|
||||
workspaceId: "The ID of the project where the secret is located.",
|
||||
environment: "The slug of the environment where the the secret is located.",
|
||||
secretPath: "The folder path where the secret is located."
|
||||
},
|
||||
GET_ACCESS_LIST: {
|
||||
secretName: "The name of the secret to get the access list for.",
|
||||
workspaceId: "The ID of the project where the secret is located.",
|
||||
environment: "The slug of the environment where the the secret is located.",
|
||||
secretPath: "The folder path where the secret is located."
|
||||
}
|
||||
} as const;
|
||||
|
||||
|
@@ -21,6 +21,9 @@ export const registerServeUI = async (
|
||||
server.route({
|
||||
method: "GET",
|
||||
url: "/runtime-ui-env.js",
|
||||
schema: {
|
||||
hide: true
|
||||
},
|
||||
handler: (_req, res) => {
|
||||
const appCfg = getConfig();
|
||||
void res.type("application/javascript");
|
||||
@@ -43,12 +46,19 @@ export const registerServeUI = async (
|
||||
wildcard: false
|
||||
});
|
||||
|
||||
server.get("/*", (request, reply) => {
|
||||
if (request.url.startsWith("/api")) {
|
||||
reply.callNotFound();
|
||||
return;
|
||||
server.route({
|
||||
method: "GET",
|
||||
url: "/*",
|
||||
schema: {
|
||||
hide: true
|
||||
},
|
||||
handler: (request, reply) => {
|
||||
if (request.url.startsWith("/api")) {
|
||||
reply.callNotFound();
|
||||
return;
|
||||
}
|
||||
void reply.sendFile("index.html");
|
||||
}
|
||||
void reply.sendFile("index.html");
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@@ -1033,7 +1033,8 @@ export const registerRoutes = async (
|
||||
secretApprovalRequestDAL,
|
||||
secretApprovalRequestSecretDAL,
|
||||
secretV2BridgeService,
|
||||
secretApprovalRequestService
|
||||
secretApprovalRequestService,
|
||||
licenseService
|
||||
});
|
||||
|
||||
const secretSharingService = secretSharingServiceFactory({
|
||||
|
@@ -36,6 +36,12 @@ const SecretReferenceNodeTree: z.ZodType<TSecretReferenceNode> = SecretReference
|
||||
children: z.lazy(() => SecretReferenceNodeTree.array())
|
||||
});
|
||||
|
||||
const SecretNameSchema = z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1)
|
||||
.refine((el) => !el.includes(" "), "Secret name cannot contain spaces.");
|
||||
|
||||
export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
method: "POST",
|
||||
@@ -51,7 +57,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
secretName: z.string().trim().describe(SECRETS.ATTACH_TAGS.secretName)
|
||||
secretName: SecretNameSchema.describe(SECRETS.ATTACH_TAGS.secretName)
|
||||
}),
|
||||
body: z.object({
|
||||
projectSlug: z.string().trim().describe(SECRETS.ATTACH_TAGS.projectSlug),
|
||||
@@ -114,7 +120,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
secretName: z.string().trim().describe(SECRETS.DETACH_TAGS.secretName)
|
||||
secretName: z.string().describe(SECRETS.DETACH_TAGS.secretName)
|
||||
}),
|
||||
body: z.object({
|
||||
projectSlug: z.string().trim().describe(SECRETS.DETACH_TAGS.projectSlug),
|
||||
@@ -442,7 +448,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
secretName: z.string().trim().describe(RAW_SECRETS.CREATE.secretName)
|
||||
secretName: SecretNameSchema.describe(RAW_SECRETS.CREATE.secretName)
|
||||
}),
|
||||
body: z.object({
|
||||
workspaceId: z.string().trim().describe(RAW_SECRETS.CREATE.workspaceId),
|
||||
@@ -549,7 +555,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
secretName: z.string().trim().describe(RAW_SECRETS.UPDATE.secretName)
|
||||
secretName: SecretNameSchema.describe(RAW_SECRETS.UPDATE.secretName)
|
||||
}),
|
||||
body: z.object({
|
||||
workspaceId: z.string().trim().describe(RAW_SECRETS.UPDATE.workspaceId),
|
||||
@@ -575,7 +581,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
.optional()
|
||||
.nullable()
|
||||
.describe(RAW_SECRETS.UPDATE.secretReminderRepeatDays),
|
||||
newSecretName: z.string().min(1).optional().describe(RAW_SECRETS.UPDATE.newSecretName),
|
||||
newSecretName: SecretNameSchema.optional().describe(RAW_SECRETS.UPDATE.newSecretName),
|
||||
secretComment: z.string().optional().describe(RAW_SECRETS.UPDATE.secretComment)
|
||||
}),
|
||||
response: {
|
||||
@@ -660,7 +666,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
secretName: z.string().trim().describe(RAW_SECRETS.DELETE.secretName)
|
||||
secretName: z.string().min(1).describe(RAW_SECRETS.DELETE.secretName)
|
||||
}),
|
||||
body: z.object({
|
||||
workspaceId: z.string().trim().describe(RAW_SECRETS.DELETE.workspaceId),
|
||||
@@ -1855,7 +1861,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
.describe(RAW_SECRETS.CREATE.secretPath),
|
||||
secrets: z
|
||||
.object({
|
||||
secretKey: z.string().trim().describe(RAW_SECRETS.CREATE.secretName),
|
||||
secretKey: SecretNameSchema.describe(RAW_SECRETS.CREATE.secretName),
|
||||
secretValue: z
|
||||
.string()
|
||||
.transform((val) => (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim()))
|
||||
@@ -1956,14 +1962,14 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
.describe(RAW_SECRETS.UPDATE.secretPath),
|
||||
secrets: z
|
||||
.object({
|
||||
secretKey: z.string().trim().describe(RAW_SECRETS.UPDATE.secretName),
|
||||
secretKey: SecretNameSchema.describe(RAW_SECRETS.UPDATE.secretName),
|
||||
secretValue: z
|
||||
.string()
|
||||
.transform((val) => (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim()))
|
||||
.describe(RAW_SECRETS.UPDATE.secretValue),
|
||||
secretComment: z.string().trim().optional().describe(RAW_SECRETS.UPDATE.secretComment),
|
||||
skipMultilineEncoding: z.boolean().optional().describe(RAW_SECRETS.UPDATE.skipMultilineEncoding),
|
||||
newSecretName: z.string().min(1).optional().describe(RAW_SECRETS.UPDATE.newSecretName),
|
||||
newSecretName: SecretNameSchema.optional().describe(RAW_SECRETS.UPDATE.newSecretName),
|
||||
tagIds: z.string().array().optional().describe(RAW_SECRETS.UPDATE.tagIds),
|
||||
secretReminderNote: z.string().optional().nullable().describe(RAW_SECRETS.UPDATE.secretReminderNote),
|
||||
secretMetadata: ResourceMetadataSchema.optional(),
|
||||
@@ -2062,7 +2068,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
.describe(RAW_SECRETS.DELETE.secretPath),
|
||||
secrets: z
|
||||
.object({
|
||||
secretKey: z.string().trim().describe(RAW_SECRETS.DELETE.secretName),
|
||||
secretKey: z.string().describe(RAW_SECRETS.DELETE.secretName),
|
||||
type: z.nativeEnum(SecretType).default(SecretType.Shared)
|
||||
})
|
||||
.array()
|
||||
|
@@ -2772,13 +2772,23 @@ const syncSecretsAzureDevops = async ({
|
||||
* Sync/push [secrets] to GitLab repo with name [integration.app]
|
||||
*/
|
||||
const syncSecretsGitLab = async ({
|
||||
createManySecretsRawFn,
|
||||
integrationAuth,
|
||||
integration,
|
||||
secrets,
|
||||
accessToken
|
||||
}: {
|
||||
createManySecretsRawFn: (params: TCreateManySecretsRawFn) => Promise<Array<{ id: string }>>;
|
||||
integrationAuth: TIntegrationAuths;
|
||||
integration: TIntegrations;
|
||||
integration: TIntegrations & {
|
||||
projectId: string;
|
||||
environment: {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
};
|
||||
secretPath: string;
|
||||
};
|
||||
secrets: Record<string, { value: string; comment?: string }>;
|
||||
accessToken: string;
|
||||
}) => {
|
||||
@@ -2835,6 +2845,81 @@ const syncSecretsGitLab = async ({
|
||||
return isValid;
|
||||
});
|
||||
|
||||
if (!integration.lastUsed) {
|
||||
const secretsToAddToInfisical: { [key: string]: GitLabSecret } = {};
|
||||
const secretsToRemoveInGitlab: GitLabSecret[] = [];
|
||||
|
||||
if (!metadata.initialSyncBehavior) {
|
||||
metadata.initialSyncBehavior = IntegrationInitialSyncBehavior.OVERWRITE_TARGET;
|
||||
}
|
||||
|
||||
getSecretsRes.forEach((gitlabSecret) => {
|
||||
// first time using integration
|
||||
// -> apply initial sync behavior
|
||||
switch (metadata.initialSyncBehavior) {
|
||||
// Override all the secrets in GitLab
|
||||
case IntegrationInitialSyncBehavior.OVERWRITE_TARGET: {
|
||||
if (!(gitlabSecret.key in secrets)) {
|
||||
secretsToRemoveInGitlab.push(gitlabSecret);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case IntegrationInitialSyncBehavior.PREFER_SOURCE: {
|
||||
// if the secret is not in infisical, we need to add it to infisical
|
||||
if (!(gitlabSecret.key in secrets)) {
|
||||
secrets[gitlabSecret.key] = {
|
||||
value: gitlabSecret.value
|
||||
};
|
||||
// need to remove prefix and suffix from what we're saving to Infisical
|
||||
const prefix = metadata?.secretPrefix || "";
|
||||
const suffix = metadata?.secretSuffix || "";
|
||||
let processedKey = gitlabSecret.key;
|
||||
|
||||
// Remove prefix if it exists at the start
|
||||
if (prefix && processedKey.startsWith(prefix)) {
|
||||
processedKey = processedKey.slice(prefix.length);
|
||||
}
|
||||
|
||||
// Remove suffix if it exists at the end
|
||||
if (suffix && processedKey.endsWith(suffix)) {
|
||||
processedKey = processedKey.slice(0, -suffix.length);
|
||||
}
|
||||
|
||||
secretsToAddToInfisical[processedKey] = gitlabSecret;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new Error(`Invalid initial sync behavior: ${metadata.initialSyncBehavior}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (Object.keys(secretsToAddToInfisical).length) {
|
||||
await createManySecretsRawFn({
|
||||
projectId: integration.projectId,
|
||||
environment: integration.environment.slug,
|
||||
path: integration.secretPath,
|
||||
secrets: Object.keys(secretsToAddToInfisical).map((key) => ({
|
||||
secretName: key,
|
||||
secretValue: secretsToAddToInfisical[key].value,
|
||||
type: SecretType.Shared
|
||||
}))
|
||||
});
|
||||
}
|
||||
|
||||
for await (const gitlabSecret of secretsToRemoveInGitlab) {
|
||||
await request.delete(
|
||||
`${gitLabApiUrl}/v4/projects/${integration?.appId}/variables/${gitlabSecret.key}?filter[environment_scope]=${integration.targetEnvironment}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for await (const key of Object.keys(secrets)) {
|
||||
const existingSecret = getSecretsRes.find((s) => s.key === key);
|
||||
if (!existingSecret) {
|
||||
@@ -4554,7 +4639,8 @@ export const syncIntegrationSecrets = async ({
|
||||
integrationAuth,
|
||||
integration,
|
||||
secrets,
|
||||
accessToken
|
||||
accessToken,
|
||||
createManySecretsRawFn
|
||||
});
|
||||
break;
|
||||
case Integrations.RENDER:
|
||||
|
@@ -1,3 +1,5 @@
|
||||
import { AxiosResponse } from "axios";
|
||||
|
||||
import { request } from "@app/lib/config/request";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
|
||||
@@ -11,19 +13,27 @@ const getTeamsGitLab = async ({ url, accessToken }: { url: string; accessToken:
|
||||
const gitLabApiUrl = url ? `${url}/api` : IntegrationUrls.GITLAB_API_URL;
|
||||
|
||||
let teams: Team[] = [];
|
||||
const res = (
|
||||
await request.get<{ name: string; id: string }[]>(`${gitLabApiUrl}/v4/groups`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
"Accept-Encoding": "application/json"
|
||||
let page: number = 1;
|
||||
while (page > 0) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const { data, headers }: AxiosResponse<{ name: string; id: string }[]> = await request.get(
|
||||
`${gitLabApiUrl}/v4/groups?page=${page}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
"Accept-Encoding": "application/json"
|
||||
}
|
||||
}
|
||||
})
|
||||
).data;
|
||||
);
|
||||
|
||||
teams = res.map((t) => ({
|
||||
name: t.name,
|
||||
id: t.id.toString()
|
||||
}));
|
||||
page = Number(headers["x-next-page"] ?? "");
|
||||
teams = teams.concat(
|
||||
data.map((t) => ({
|
||||
name: t.name,
|
||||
id: t.id.toString()
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
return teams;
|
||||
};
|
||||
|
@@ -11,6 +11,7 @@ import {
|
||||
SecretsSchema,
|
||||
SecretType
|
||||
} from "@app/db/schemas";
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||
import { TSecretApprovalPolicyServiceFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-service";
|
||||
@@ -69,6 +70,7 @@ import {
|
||||
TDeleteSecretRawDTO,
|
||||
TGetASecretDTO,
|
||||
TGetASecretRawDTO,
|
||||
TGetSecretAccessListDTO,
|
||||
TGetSecretsDTO,
|
||||
TGetSecretsRawDTO,
|
||||
TGetSecretVersionsDTO,
|
||||
@@ -94,7 +96,7 @@ type TSecretServiceFactoryDep = {
|
||||
>;
|
||||
secretV2BridgeService: TSecretV2BridgeServiceFactory;
|
||||
secretBlindIndexDAL: TSecretBlindIndexDALFactory;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getProjectPermissions">;
|
||||
snapshotService: Pick<TSecretSnapshotServiceFactory, "performSnapshot">;
|
||||
secretQueueService: Pick<
|
||||
TSecretQueueFactory,
|
||||
@@ -113,6 +115,7 @@ type TSecretServiceFactoryDep = {
|
||||
TSecretApprovalRequestSecretDALFactory,
|
||||
"insertMany" | "insertApprovalSecretTags"
|
||||
>;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
};
|
||||
|
||||
export type TSecretServiceFactory = ReturnType<typeof secretServiceFactory>;
|
||||
@@ -134,7 +137,8 @@ export const secretServiceFactory = ({
|
||||
secretApprovalRequestDAL,
|
||||
secretApprovalRequestSecretDAL,
|
||||
secretV2BridgeService,
|
||||
secretApprovalRequestService
|
||||
secretApprovalRequestService,
|
||||
licenseService
|
||||
}: TSecretServiceFactoryDep) => {
|
||||
const getSecretReference = async (projectId: string) => {
|
||||
// if bot key missing means e2e still exist
|
||||
@@ -1153,6 +1157,71 @@ export const secretServiceFactory = ({
|
||||
return secretV2BridgeService.getSecretReferenceTree(dto);
|
||||
};
|
||||
|
||||
const getSecretAccessList = async (dto: TGetSecretAccessListDTO) => {
|
||||
const { environment, secretPath, secretName, projectId } = dto;
|
||||
const plan = await licenseService.getPlan(dto.actorOrgId);
|
||||
if (!plan.secretAccessInsights) {
|
||||
throw new BadRequestError({
|
||||
message: "Failed to fetch secret access list due to plan restriction. Upgrade your plan."
|
||||
});
|
||||
}
|
||||
|
||||
const secret = await secretV2BridgeService.getSecretByName({
|
||||
actor: dto.actor,
|
||||
actorId: dto.actorId,
|
||||
actorOrgId: dto.actorOrgId,
|
||||
actorAuthMethod: dto.actorAuthMethod,
|
||||
projectId,
|
||||
secretName,
|
||||
path: secretPath,
|
||||
environment,
|
||||
type: "shared"
|
||||
});
|
||||
|
||||
const { userPermissions, identityPermissions, groupPermissions } = await permissionService.getProjectPermissions(
|
||||
dto.projectId
|
||||
);
|
||||
|
||||
const attachAllowedActions = (
|
||||
entityPermission:
|
||||
| (typeof userPermissions)[number]
|
||||
| (typeof identityPermissions)[number]
|
||||
| (typeof groupPermissions)[number]
|
||||
) => {
|
||||
const allowedActions = [
|
||||
ProjectPermissionActions.Read,
|
||||
ProjectPermissionActions.Delete,
|
||||
ProjectPermissionActions.Create,
|
||||
ProjectPermissionActions.Edit
|
||||
].filter((action) =>
|
||||
entityPermission.permission.can(
|
||||
action,
|
||||
subject(ProjectPermissionSub.Secrets, {
|
||||
environment,
|
||||
secretPath,
|
||||
secretName,
|
||||
secretTags: secret?.tags?.map((el) => el.slug)
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
return {
|
||||
...entityPermission,
|
||||
allowedActions
|
||||
};
|
||||
};
|
||||
|
||||
const usersWithAccess = userPermissions.map(attachAllowedActions).filter((user) => user.allowedActions.length > 0);
|
||||
const identitiesWithAccess = identityPermissions
|
||||
.map(attachAllowedActions)
|
||||
.filter((identity) => identity.allowedActions.length > 0);
|
||||
const groupsWithAccess = groupPermissions
|
||||
.map(attachAllowedActions)
|
||||
.filter((group) => group.allowedActions.length > 0);
|
||||
|
||||
return { users: usersWithAccess, identities: identitiesWithAccess, groups: groupsWithAccess };
|
||||
};
|
||||
|
||||
const getSecretsRaw = async ({
|
||||
projectId,
|
||||
path,
|
||||
@@ -2946,6 +3015,7 @@ export const secretServiceFactory = ({
|
||||
getSecretsCountMultiEnv,
|
||||
getSecretsRawMultiEnv,
|
||||
getSecretReferenceTree,
|
||||
getSecretsRawByFolderMappings
|
||||
getSecretsRawByFolderMappings,
|
||||
getSecretAccessList
|
||||
};
|
||||
};
|
||||
|
@@ -190,6 +190,12 @@ export type TGetSecretsRawDTO = {
|
||||
keys?: string[];
|
||||
} & TProjectPermission;
|
||||
|
||||
export type TGetSecretAccessListDTO = {
|
||||
environment: string;
|
||||
secretPath: string;
|
||||
secretName: string;
|
||||
} & TProjectPermission;
|
||||
|
||||
export type TGetASecretRawDTO = {
|
||||
secretName: string;
|
||||
path: string;
|
||||
|
@@ -419,22 +419,23 @@ func executeCommandWithWatchMode(commandFlag string, args []string, watchModeInt
|
||||
|
||||
for {
|
||||
<-recheckSecretsChannel
|
||||
watchMutex.Lock()
|
||||
func() {
|
||||
watchMutex.Lock()
|
||||
defer watchMutex.Unlock()
|
||||
|
||||
newEnvironmentVariables, err := fetchAndFormatSecretsForShell(request, projectConfigDir, secretOverriding, token)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("[HOT RELOAD] Failed to fetch secrets")
|
||||
continue
|
||||
}
|
||||
newEnvironmentVariables, err := fetchAndFormatSecretsForShell(request, projectConfigDir, secretOverriding, token)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("[HOT RELOAD] Failed to fetch secrets")
|
||||
return
|
||||
}
|
||||
|
||||
if newEnvironmentVariables.ETag != currentETag {
|
||||
runCommandWithWatcher(newEnvironmentVariables)
|
||||
} else {
|
||||
log.Debug().Msg("[HOT RELOAD] No changes detected in secrets, not reloading process")
|
||||
}
|
||||
|
||||
watchMutex.Unlock()
|
||||
if newEnvironmentVariables.ETag != currentETag {
|
||||
runCommandWithWatcher(newEnvironmentVariables)
|
||||
} else {
|
||||
log.Debug().Msg("[HOT RELOAD] No changes detected in secrets, not reloading process")
|
||||
}
|
||||
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -4,6 +4,27 @@ title: "Changelog"
|
||||
|
||||
The changelog below reflects new product developments and updates on a monthly basis.
|
||||
|
||||
## December 2024
|
||||
- Added [GCP KMS](https://infisical.com/docs/documentation/platform/kms/overview) integration support.
|
||||
- Added support for [K8s CSI integration](https://infisical.com/docs/integrations/platforms/kubernetes-csi) and ability to point K8s operator to specific secret versions.
|
||||
- Fixed [Java SDK](https://github.com/Infisical/java-sdk) compatibility issues with Alpine Linux.
|
||||
- Fixed SCIM group role assignment issues.
|
||||
- Added Group View Page for improved team management.
|
||||
- Added instance URL to email verification for Infisical accounts.
|
||||
- Added ability to copy full path of nested folders.
|
||||
- Added custom templating support for K8s operator, allowing flexible secret key mapping and additional fields
|
||||
- Optimized secrets versions table performance.
|
||||
|
||||
## November 2024
|
||||
- Improved EnvKey migration functionality with support for Blocks, Inheritance, and Branches.
|
||||
- Added [Hardware Security Module (HSM) Encryption](https://infisical.com/docs/documentation/platform/kms/hsm-integration) support.
|
||||
- Updated permissions handling in [Infisical Terraform Provider](https://registry.terraform.io/providers/Infisical/infisical/latest/docs) to use lists instead of sets.
|
||||
- Enhanced [SCIM](https://infisical.com/docs/documentation/platform/scim/overview) implementation to remove SAML dependency.
|
||||
- Enhanced [OIDC Authentication](https://infisical.com/docs/documentation/platform/identities/oidc-auth/general) implementation and added Default Org Slug support.
|
||||
- Added support for multiple authentication methods per identity.
|
||||
- Added AWS Parameter Store integration sync improvements.
|
||||
- Added new screen and API for managing additional privileges.
|
||||
- Added Dynamic Secrets support for SQL Server.
|
||||
|
||||
## October 2024
|
||||
- Significantly improved performance of audit log operations in UI.
|
||||
|
@@ -4,6 +4,13 @@ sidebarTitle: "Overview"
|
||||
description: "Learn how to generate secrets dynamically on-demand."
|
||||
---
|
||||
|
||||
<Info>
|
||||
Note that Dynamic Secrets is a paid feature.
|
||||
|
||||
If you're using Infisical Cloud, then it is available under the **Enterprise Tier**
|
||||
If you're self-hosting Infisical, then you should contact sales@infisical.com to purchase an enterprise license to use it.
|
||||
</Info>
|
||||
|
||||
## Introduction
|
||||
|
||||
Contrary to static key-value secrets, which require manual input of data into the secure Infisical storage, **dynamic secrets are generated on-demand upon access**.
|
||||
|
@@ -3,6 +3,13 @@ title: "Approval Workflows"
|
||||
description: "Learn how to enable a set of policies to manage changes to sensitive secrets and environments."
|
||||
---
|
||||
|
||||
<Info>
|
||||
Approval Workflows is a paid feature.
|
||||
|
||||
If you're using Infisical Cloud, then it is available under the **Pro Tier** and **Enterprise Tire**.
|
||||
If you're self-hosting Infisical, then you should contact sales@infisical.com to purchase an enterprise license to use it.
|
||||
</Info>
|
||||
|
||||
## Problem at hand
|
||||
|
||||
Updating secrets in high-stakes environments (e.g., production) can have a number of problematic issues:
|
||||
@@ -40,4 +47,4 @@ When a user submits a change to an enviropnment that is under a particular polic
|
||||
|
||||
Approvers are notified by email and/or Slack as soon as the request is initiated. In the Infisical Dashboard, they will be able to `approve` and `merge` (or `deny`) a request for a change in a particular environment. After that, depending on the workflows setup, the change will be automatically propagated to the right applications (e.g., using [Infisical Kubernetes Operator](https://infisical.com/docs/integrations/platforms/kubernetes)).
|
||||
|
||||

|
||||

|
||||
|
@@ -15,7 +15,7 @@ Prerequisites:
|
||||
|
||||
<Steps>
|
||||
<Step title="Create a SCIM token in Infisical">
|
||||
In Infisical, head to your Organization Settings > Authentication > SCIM Configuration and
|
||||
In Infisical, head to your Organization Settings > Security > SCIM Configuration and
|
||||
press the **Enable SCIM provisioning** toggle to allow Azure to provision/deprovision users for your organization.
|
||||
|
||||

|
||||
|
@@ -15,7 +15,7 @@ Prerequisites:
|
||||
|
||||
<Steps>
|
||||
<Step title="Create a SCIM token in Infisical">
|
||||
In Infisical, head to your Organization Settings > Authentication > SCIM Configuration and
|
||||
In Infisical, head to your Organization Settings > Security > SCIM Configuration and
|
||||
press the **Enable SCIM provisioning** toggle to allow JumpCloud to provision/deprovision users and user groups for your organization.
|
||||
|
||||

|
||||
|
@@ -15,7 +15,7 @@ Prerequisites:
|
||||
|
||||
<Steps>
|
||||
<Step title="Create a SCIM token in Infisical">
|
||||
In Infisical, head to your Organization Settings > Authentication > SCIM Configuration and
|
||||
In Infisical, head to your Organization Settings > Security > SCIM Configuration and
|
||||
press the **Enable SCIM provisioning** toggle to allow Okta to provision/deprovision users and user groups for your organization.
|
||||
|
||||

|
||||
|
93
docs/documentation/platform/sso/auth0-saml.mdx
Normal file
@@ -0,0 +1,93 @@
|
||||
---
|
||||
title: "Auth0 SAML"
|
||||
description: "Learn how to configure Auth0 SAML for Infisical SSO."
|
||||
---
|
||||
|
||||
<Info>
|
||||
Auth0 SAML SSO feature is a paid feature. If you're using Infisical Cloud,
|
||||
then it is available under the **Pro Tier**. If you're self-hosting Infisical,
|
||||
then you should contact sales@infisical.com to purchase an enterprise license
|
||||
to use it.
|
||||
</Info>
|
||||
|
||||
<Steps>
|
||||
<Step title="Prepare the SAML SSO configuration in Infisical">
|
||||
In Infisical, head to Organization Settings > Security and click **Connect** for SAML under the Connect to an Identity Provider section. Select Auth0, then click **Connect** again.
|
||||
|
||||
Next, note the **Application Callback URL** and **Audience** to use when configuring the Auth0 SAML application.
|
||||
|
||||

|
||||
|
||||
</Step>
|
||||
<Step title="Create a SAML application in Auth0">
|
||||
2.1. In your Auth0 account, head to Applications and create an application.
|
||||
|
||||

|
||||
|
||||
Select **Regular Web Application** and press **Create**.
|
||||
|
||||

|
||||
|
||||
2.2. In the Application head to Settings > Application URIs and add the **Application Callback URL** from step 1 into the **Allowed Callback URLs** field.
|
||||
|
||||

|
||||
|
||||
2.3. In the Application head to Addons > SAML2 Web App and copy the **Issuer**, **Identity Provider Login URL**, and **Identity Provider Certificate** from the **Usage** tab.
|
||||
|
||||

|
||||
|
||||
2.4. Back in Infisical, set **Issuer**, **Identity Provider Login URL**, and **Certificate** to the corresponding items from step 2.3.
|
||||
|
||||

|
||||
|
||||
2.5. Back in Auth0, in the **Settings** tab, set the **Application Callback URL** to the **Application Callback URL** from step 1
|
||||
and update the **Settings** field with the JSON under the picture below (replacing `<audience-from-infisical>` with the **Audience** from step 1).
|
||||
|
||||

|
||||
|
||||
```json
|
||||
{
|
||||
"audience": "<audience-from-infisical>",
|
||||
"mappings": {
|
||||
"email": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/email",
|
||||
"given_name": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/firstName",
|
||||
"family_name": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/lastName"
|
||||
},
|
||||
"signatureAlgorithm": "rsa-sha256",
|
||||
"digestAlgorithm": "sha256",
|
||||
"signResponse": true
|
||||
}
|
||||
```
|
||||
|
||||
Click **Save**.
|
||||
</Step>
|
||||
<Step title="Enable SAML SSO in Infisical">
|
||||
Enabling SAML SSO allows members in your organization to log into Infisical via Auth0.
|
||||
|
||||

|
||||
</Step>
|
||||
<Step title="Enforce SAML SSO in Infisical">
|
||||
Enforcing SAML SSO ensures that members in your organization can only access Infisical
|
||||
by logging into the organization via Auth0.
|
||||
|
||||
To enforce SAML SSO, you're required to test out the SAML connection by successfully authenticating at least one Auth0 user with Infisical;
|
||||
Once you've completed this requirement, you can toggle the **Enforce SAML SSO** button to enforce SAML SSO.
|
||||
</Step>
|
||||
|
||||
</Steps>
|
||||
|
||||
<Tip>
|
||||
If you are only using one organization on your Infisical instance, you can configure a default organization in the [Server Admin Console](../admin-panel/server-admin#default-organization) to expedite SAML login.
|
||||
</Tip>
|
||||
|
||||
<Note>
|
||||
If you're configuring SAML SSO on a self-hosted instance of Infisical, make
|
||||
sure to set the `AUTH_SECRET` and `SITE_URL` environment variable for it to
|
||||
work:
|
||||
<div class="height:1px;"/>
|
||||
- `AUTH_SECRET`: A secret key used for signing and verifying JWT. This
|
||||
can be a random 32-byte base64 string generated with `openssl rand -base64
|
||||
32`.
|
||||
<div class="height:1px;"/>
|
||||
- `SITE_URL`: The absolute URL of your self-hosted instance of Infisical including the protocol (e.g. https://app.infisical.com)
|
||||
</Note>
|
@@ -12,7 +12,7 @@ description: "Learn how to configure Microsoft Entra ID for Infisical SSO."
|
||||
|
||||
<Steps>
|
||||
<Step title="Prepare the SAML SSO configuration in Infisical">
|
||||
In Infisical, head to your Organization Settings > Authentication > SAML SSO Configuration and select **Set up SAML SSO**.
|
||||
In Infisical, head to Organization Settings > Security and click **Connect** for SAML under the Connect to an Identity Provider section. Select Azure / Entra, then click **Connect** again.
|
||||
|
||||
Next, copy the **Reply URL (Assertion Consumer Service URL)** and **Identifier (Entity ID)** to use when configuring the Azure SAML application.
|
||||
|
||||
|
@@ -12,7 +12,7 @@ description: "Learn how to configure Google SAML for Infisical SSO."
|
||||
|
||||
<Steps>
|
||||
<Step title="Prepare the SAML SSO configuration in Infisical">
|
||||
In Infisical, head to your Organization Settings > Authentication > SAML SSO Configuration and select **Set up SAML SSO**.
|
||||
In Infisical, head to Organization Settings > Security and click **Connect** for SAML under the Connect to an Identity Provider section. Select Google, then click **Connect** again.
|
||||
|
||||
Next, note the **ACS URL** and **SP Entity ID** to use when configuring the Google SAML application.
|
||||
|
||||
|
@@ -12,7 +12,7 @@ description: "Learn how to configure JumpCloud SAML for Infisical SSO."
|
||||
|
||||
<Steps>
|
||||
<Step title="Prepare the SAML SSO configuration in Infisical">
|
||||
In Infisical, head to your Organization Settings > Authentication > SAML SSO Configuration and select **Set up SAML SSO**.
|
||||
In Infisical, head to Organization Settings > Security and click **Connect** for SAML under the Connect to an Identity Provider section. Select JumpCloud, then click **Connect** again.
|
||||
|
||||
Next, copy the **ACS URL** and **SP Entity ID** to use when configuring the JumpCloud SAML application.
|
||||
|
||||
|
@@ -12,7 +12,7 @@ description: "Learn how to configure Keycloak SAML for Infisical SSO."
|
||||
|
||||
<Steps>
|
||||
<Step title="Prepare the SAML SSO configuration in Infisical">
|
||||
In Infisical, head to your Organization Settings > Authentication > SAML SSO Configuration and select **Manage**.
|
||||
In Infisical, head to Organization Settings > Security and click **Connect** for SAML under the Connect to an Identity Provider section. Select Keycloak, then click **Connect** again.
|
||||
|
||||

|
||||
|
||||
|
@@ -12,7 +12,7 @@ description: "Learn how to configure Okta SAML 2.0 for Infisical SSO."
|
||||
|
||||
<Steps>
|
||||
<Step title="Prepare the SAML SSO configuration in Infisical">
|
||||
In Infisical, head to your Organization Settings > Authentication > SAML SSO Configuration and select **Set up SAML SSO**.
|
||||
In Infisical, head to Organization Settings > Security and click **Connect** for SAML under the Connect to an Identity Provider section. Select Okta, then click **Connect** again.
|
||||
|
||||
Next, copy the **Single sign-on URL** and **Audience URI (SP Entity ID)** to use when configuring the Okta SAML 2.0 application.
|
||||

|
||||
|
@@ -28,6 +28,7 @@ Infisical supports these and many other identity providers:
|
||||
- [JumpCloud SAML](/documentation/platform/sso/jumpcloud)
|
||||
- [Keycloak SAML](/documentation/platform/sso/keycloak-saml)
|
||||
- [Google SAML](/documentation/platform/sso/google-saml)
|
||||
- [Auth0 SAML](/documentation/platform/sso/auth0-saml)
|
||||
- [Keycloak OIDC](/documentation/platform/sso/keycloak-oidc)
|
||||
- [Auth0 OIDC](/documentation/platform/sso/auth0-oidc)
|
||||
- [General OIDC](/documentation/platform/sso/general-oidc)
|
||||
|
BIN
docs/images/sso/auth0-saml/auth0-config-2.png
Normal file
After Width: | Height: | Size: 612 KiB |
BIN
docs/images/sso/auth0-saml/auth0-config-3.png
Normal file
After Width: | Height: | Size: 366 KiB |
BIN
docs/images/sso/auth0-saml/auth0-config.png
Normal file
After Width: | Height: | Size: 431 KiB |
BIN
docs/images/sso/auth0-saml/create-application-2.png
Normal file
After Width: | Height: | Size: 352 KiB |
BIN
docs/images/sso/auth0-saml/create-application.png
Normal file
After Width: | Height: | Size: 253 KiB |
BIN
docs/images/sso/auth0-saml/enable-saml.png
Normal file
After Width: | Height: | Size: 605 KiB |
BIN
docs/images/sso/auth0-saml/infisical-config.png
Normal file
After Width: | Height: | Size: 605 KiB |
BIN
docs/images/sso/auth0-saml/init-config.png
Normal file
After Width: | Height: | Size: 539 KiB |
@@ -0,0 +1,437 @@
|
||||
---
|
||||
sidebarTitle: "InfisicalDynamicSecret CRD"
|
||||
title: "InfisicalDynamicSecret CRD"
|
||||
description: "Learn how to generate dynamic secret leases in Infisical and sync them to your Kubernetes cluster."
|
||||
---
|
||||
## Overview
|
||||
|
||||
The **InfisicalDynamicSecret** CRD allows you to easily create and manage dynamic secret leases in Infisical and automatically sync them to your Kubernetes cluster as native **Kubernetes Secret** resources.
|
||||
This means any Pod, Deployment, or other Kubernetes resource can make use of dynamic secrets from Infisical just like any other K8s secret.
|
||||
|
||||
This CRD offers the following features:
|
||||
- **Generate a dynamic secret lease** in Infisical and track its lifecycle.
|
||||
- **Write** the dynamic secret from Infisical to your cluster as native Kubernetes secret.
|
||||
- **Automatically rotate** the dyanmic secret value before it expires to make sure your cluster always has valid credentials.
|
||||
- **Optionally trigger redeployments** of any workloads that consume the secret if you enable auto-reload.
|
||||
|
||||
### Prerequisites
|
||||
- The operator is installed on to your Kubernetes cluster
|
||||
- You have already configured a dynamic secret in Infisical
|
||||
|
||||
## Configure Dynamic Secret CRD
|
||||
|
||||
The example below shows a sample **InfisicalDynamicSecret** CRD that creates a dynamic secret lease in Infisical, and syncs the lease to your Kubernetes cluster.
|
||||
|
||||
```yaml dynamic-secret-crd.yaml
|
||||
apiVersion: secrets.infisical.com/v1alpha1
|
||||
kind: InfisicalDynamicSecret
|
||||
metadata:
|
||||
name: infisicaldynamicsecret
|
||||
spec:
|
||||
hostAPI: https://app.infisical.com/api # Optional, defaults to https://app.infisical.com/api
|
||||
|
||||
dynamicSecret:
|
||||
secretName: <dynamic-secret-name>
|
||||
projectId: <project-id>
|
||||
secretsPath: <path/to/dynamic-secret> # Root directory is /
|
||||
environmentSlug: <env-slug>
|
||||
|
||||
# Lease revocation policy defines what should happen to leases created by the operator if the CRD is deleted.
|
||||
# If set to "Revoke", leases will be revoked when the InfisicalDynamicSecret CRD is deleted.
|
||||
leaseRevocationPolicy: Revoke
|
||||
|
||||
# Lease TTL defines how long the lease should last for the dynamic secret.
|
||||
# This value must be less than 1 day, and if a max TTL is defined on the dynamic secret, it must be below the max TTL.
|
||||
leaseTTL: 1m
|
||||
|
||||
# A reference to the secret that the dynamic secret lease should be stored in.
|
||||
# If the secret doesn't exist, it will automatically be created.
|
||||
managedSecretReference:
|
||||
secretName: <secret-name>
|
||||
secretNamespace: default # Must be the same namespace as the InfisicalDynamicSecret CRD.
|
||||
creationPolicy: Orphan
|
||||
|
||||
# Only have one authentication method defined or you are likely to run into authentication issues.
|
||||
# Remove all except one authentication method.
|
||||
authentication:
|
||||
awsIamAuth:
|
||||
identityId: <machine-identity-id>
|
||||
azureAuth:
|
||||
identityId: <machine-identity-id>
|
||||
gcpIamAuth:
|
||||
identityId: <machine-identity-id>
|
||||
serviceAccountKeyFilePath: </path-to-service-account-key-file.json>
|
||||
gcpIdTokenAuth:
|
||||
identityId: <machine-identity-id>
|
||||
kubernetesAuth:
|
||||
identityId: <machine-identity-id>
|
||||
serviceAccountRef:
|
||||
name: <secret-name>
|
||||
namespace: <secret-namespace>
|
||||
universalAuth:
|
||||
credentialsRef:
|
||||
secretName: <secret-name> # universal-auth-credentials
|
||||
secretNamespace: <secret-namespace> # default
|
||||
```
|
||||
|
||||
Apply the InfisicalDynamicSecret CRD to your cluster.
|
||||
```bash
|
||||
kubectl apply -f dynamic-secret-crd.yaml
|
||||
```
|
||||
|
||||
After applying the InfisicalDynamicSecret CRD, you should notice that the dynamic secret lease has been created in Infisical and synced to your Kubernetes cluster. You can verify that the lease has been created by doing:
|
||||
```bash
|
||||
kubectl get secret <managed-secret-name> -o yaml
|
||||
```
|
||||
|
||||
After getting the secret, you should should see that the secret has data that contains the lease credentials.
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
data:
|
||||
DB_PASSWORD: VHhETjZ4c2xsTXpOSWdPYW5LLlRyNEc2alVKYml6WiQjQS0tNTdodyREM3ZLZWtYSi4hTkdyS0F+TVFsLU9CSA==
|
||||
DB_USERNAME: cHg4Z0dJTUVBcHdtTW1aYnV3ZWRsekJRRll6cW4wFEE=
|
||||
kind: Secret
|
||||
# .....
|
||||
```
|
||||
|
||||
### InfisicalDynamicSecret CRD properties
|
||||
|
||||
<Accordion title="hostAPI">
|
||||
If you are fetching secrets from a self-hosted instance of Infisical set the value of `hostAPI` to
|
||||
` https://your-self-hosted-instace.com/api`
|
||||
|
||||
When `hostAPI` is not defined the operator fetches secrets from Infisical Cloud.
|
||||
|
||||
<Accordion title="Advanced use case">
|
||||
If you have installed your Infisical instance within the same cluster as the Infisical operator, you can optionally access the Infisical backend's service directly without having to route through the public internet.
|
||||
To achieve this, use the following address for the hostAPI field:
|
||||
|
||||
``` bash
|
||||
http://<backend-svc-name>.<namespace>.svc.cluster.local:4000/api
|
||||
```
|
||||
|
||||
Make sure to replace `<backend-svc-name>` and `<namespace>` with the appropriate values for your backend service and namespace.
|
||||
|
||||
</Accordion>
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="leaseTTL">
|
||||
The `leaseTTL` is a string-formatted duration that defines the time the lease should last for the dynamic secret.
|
||||
|
||||
The format of the field is `[duration][unit]` where `duration` is a number and `unit` is a string representing the unit of time.
|
||||
|
||||
The following units are supported:
|
||||
- `s` for seconds (must be at least 5 seconds)
|
||||
- `m` for minutes
|
||||
- `h` for hours
|
||||
- `d` for days
|
||||
|
||||
<Note>
|
||||
The lease duration at most be 1 day (24 hours). And the TTL must be less than the max TTL defined on the dynamic secret.
|
||||
</Note>
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="managedSecretReference">
|
||||
The `managedSecretReference` field is used to define the Kubernetes secret where the dynamic secret lease should be stored. The required fields are `secretName` and `secretNamespace`.
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
managedSecretReference:
|
||||
secretName: <secret-name>
|
||||
secretNamespace: default
|
||||
```
|
||||
|
||||
<Accordion title="managedSecretReference.secretName">
|
||||
The name of the Kubernetes secret where the dynamic secret lease should be stored.
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="managedSecretReference.secretNamespace">
|
||||
The namespace of the Kubernetes secret where the dynamic secret lease should be stored.
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="managedSecretReference.creationPolicy">
|
||||
Creation polices allow you to control whether or not owner references should be added to the managed Kubernetes secret that is generated by the Infisical operator.
|
||||
This is useful for tools such as ArgoCD, where every resource requires an owner reference; otherwise, it will be pruned automatically.
|
||||
|
||||
#### Available options
|
||||
- `Orphan` (default)
|
||||
- `Owner`
|
||||
|
||||
<Tip>
|
||||
When creation policy is set to `Owner`, the `InfisicalSecret` CRD must be in
|
||||
the same namespace as where the managed kubernetes secret.
|
||||
</Tip>
|
||||
|
||||
This field is optional.
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="managedSecretReference.secretType">
|
||||
Override the default Opaque type for managed secrets with this field. Useful for creating kubernetes.io/dockerconfigjson secrets.
|
||||
|
||||
This field is optional.
|
||||
</Accordion>
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="leaseRevocationPolicy">
|
||||
|
||||
The field is optional and will default to `None` if not defined.
|
||||
|
||||
The lease revocation policy defines what the operator should do with the leases created by the operator, when the InfisicalDynamicSecret CRD is deleted.
|
||||
|
||||
Valid values are `None` and `Revoke`.
|
||||
|
||||
Behavior of each policy:
|
||||
- `None`: The operator will not override existing secrets in Infisical. If a secret with the same key already exists, the operator will skip pushing that secret, and the secret will not be managed by the operator.
|
||||
- `Revoke`: The operator will revoke the leases created by the operator when the InfisicalDynamicSecret CRD is deleted.
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
leaseRevocationPolicy: Revoke
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="dynamicSecret">
|
||||
The `dynamicSecret` field is used to specify which dynamic secret to create leases for. The required fields are `secretName`, `projectId`, `secretsPath`, and `environmentSlug`.
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
dynamicSecret:
|
||||
secretName: <dynamic-secret-name>
|
||||
projectId: <project-id>
|
||||
environmentSlug: <env-slug>
|
||||
secretsPath: <secrets-path>
|
||||
```
|
||||
|
||||
<Accordion title="dynamicSecret.secretName">
|
||||
The name of the dynamic secret.
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="dynamicSecret.projectId">
|
||||
The project ID of where the dynamic secret is stored in Infisical.
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="dynamicSecret.environmentSlug">
|
||||
The environment slug of where the dynamic secret is stored in Infisical.
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="dynamicSecret.secretsPath">
|
||||
The path of where the dynamic secret is stored in Infisical. The root path is `/`.
|
||||
</Accordion>
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="authentication">
|
||||
|
||||
The `authentication` field dictates which authentication method to use when pushing secrets to Infisical.
|
||||
The available authentication methods are `universalAuth`, `kubernetesAuth`, `awsIamAuth`, `azureAuth`, `gcpIdTokenAuth`, and `gcpIamAuth`.
|
||||
|
||||
|
||||
<Accordion title="universalAuth">
|
||||
The universal authentication method is one of the easiest ways to get started with Infisical. Universal Auth works anywhere and is not tied to any specific cloud provider.
|
||||
[Read more about Universal Auth](/documentation/platform/identities/universal-auth).
|
||||
|
||||
Valid fields:
|
||||
- `identityId`: The identity ID of the machine identity you created.
|
||||
- `credentialsRef`: The name and namespace of the Kubernetes secret that stores the service token.
|
||||
- `credentialsRef.secretName`: The name of the Kubernetes secret.
|
||||
- `credentialsRef.secretNamespace`: The namespace of the Kubernetes secret.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
# infisical-push-secret.yaml
|
||||
spec:
|
||||
universalAuth:
|
||||
credentialsRef:
|
||||
secretName: <secret-name>
|
||||
secretNamespace: <secret-namespace>
|
||||
```
|
||||
|
||||
```yaml
|
||||
# machine-identity-credentials.yaml
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: universal-auth-credentials
|
||||
type: Opaque
|
||||
stringData:
|
||||
clientId: <machine-identity-client-id>
|
||||
clientSecret: <machine-identity-client-secret>
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="kubernetesAuth">
|
||||
The Kubernetes machine identity authentication method is used to authenticate with Infisical. The identity ID is stored in a field in the InfisicalSecret resource. This authentication method can only be used within a Kubernetes environment.
|
||||
[Read more about Kubernetes Auth](/documentation/platform/identities/kubernetes-auth).
|
||||
Valid fields:
|
||||
- `identityId`: The identity ID of the machine identity you created.
|
||||
- `serviceAccountRef`: The name and namespace of the service account that will be used to authenticate with Infisical.
|
||||
- `serviceAccountRef.name`: The name of the service account.
|
||||
- `serviceAccountRef.namespace`: The namespace of the service account.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
kubernetesAuth:
|
||||
identityId: <machine-identity-id>
|
||||
serviceAccountRef:
|
||||
name: <secret-name>
|
||||
namespace: <secret-namespace>
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="awsIamAuth">
|
||||
The AWS IAM machine identity authentication method is used to authenticate with Infisical.
|
||||
[Read more about AWS IAM Auth](/documentation/platform/identities/aws-auth).
|
||||
|
||||
Valid fields:
|
||||
- `identityId`: The identity ID of the machine identity you created.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
authentication:
|
||||
awsIamAuth:
|
||||
identityId: <machine-identity-id>
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="azureAuth">
|
||||
The AWS IAM machine identity authentication method is used to authenticate with Infisical. Azure Auth can only be used from within an Azure environment.
|
||||
[Read more about Azure Auth](/documentation/platform/identities/azure-auth).
|
||||
|
||||
Valid fields:
|
||||
- `identityId`: The identity ID of the machine identity you created.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
authentication:
|
||||
azureAuth:
|
||||
identityId: <machine-identity-id>
|
||||
```
|
||||
</Accordion>
|
||||
<Accordion title="gcpIamAuth">
|
||||
The GCP IAM machine identity authentication method is used to authenticate with Infisical. The identity ID is stored in a field in the InfisicalSecret resource. This authentication method can only be used both within and outside GCP environments.
|
||||
[Read more about Azure Auth](/documentation/platform/identities/gcp-auth).
|
||||
|
||||
|
||||
Valid fields:
|
||||
- `identityId`: The identity ID of the machine identity you created.
|
||||
- `serviceAccountKeyFilePath`: The path to the GCP service account key file.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
gcpIamAuth:
|
||||
identityId: <machine-identity-id>
|
||||
serviceAccountKeyFilePath: </path-to-service-account-key-file.json>
|
||||
```
|
||||
</Accordion>
|
||||
<Accordion title="gcpIdTokenAuth">
|
||||
The GCP ID Token machine identity authentication method is used to authenticate with Infisical. The identity ID is stored in a field in the InfisicalSecret resource. This authentication method can only be used within GCP environments.
|
||||
[Read more about Azure Auth](/documentation/platform/identities/gcp-auth).
|
||||
|
||||
Valid fields:
|
||||
- `identityId`: The identity ID of the machine identity you created.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
gcpIdTokenAuth:
|
||||
identityId: <machine-identity-id>
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
</Accordion>
|
||||
|
||||
|
||||
<Accordion title="tls">
|
||||
This block defines the TLS settings to use for connecting to the Infisical
|
||||
instance.
|
||||
|
||||
Fields:
|
||||
<Accordion title="caRef">
|
||||
This block defines the reference to the CA certificate to use for connecting to the Infisical instance with SSL/TLS.
|
||||
|
||||
Valid fields:
|
||||
- `secretName`: The name of the Kubernetes secret containing the CA certificate to use for connecting to the Infisical instance with SSL/TLS.
|
||||
- `secretNamespace`: The namespace of the Kubernetes secret containing the CA certificate to use for connecting to the Infisical instance with SSL/TLS.
|
||||
- `key`: The name of the key in the Kubernetes secret which contains the value of the CA certificate to use for connecting to the Infisical instance with SSL/TLS.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
tls:
|
||||
caRef:
|
||||
secretName: custom-ca-certificate
|
||||
secretNamespace: default
|
||||
key: ca.crt
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
</Accordion>
|
||||
|
||||
|
||||
### Applying the InfisicalDynamicSecret CRD to your cluster
|
||||
|
||||
Once you have configured the `InfisicalDynamicSecret` CRD with the required fields, you can apply it to your cluster. After applying, you should notice that a lease has been created in Infisical and synced to your Kubernetes cluster.
|
||||
|
||||
```bash
|
||||
kubectl apply -f dynamic-secret-crd.yaml
|
||||
```
|
||||
|
||||
## Auto redeployment
|
||||
|
||||
Deployments referring to Kubernetes secrets containing Infisical dynamic secrets don't automatically reload when the dynamic secret lease expires. This means your deployment may use expired dynamic secrets unless manually redeployed.
|
||||
To address this, we've added functionality to automatically redeploy your deployment when the associated Kubernetes secret containing your Infisical dynamic secret updates.
|
||||
|
||||
#### Enabling auto redeploy
|
||||
|
||||
To enable auto redeployment you simply have to add the following annotation to the deployment that consumes a managed secret
|
||||
|
||||
```yaml
|
||||
secrets.infisical.com/auto-reload: "true"
|
||||
```
|
||||
|
||||
<Accordion title="Deployment example with auto redeploy enabled">
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: nginx-deployment
|
||||
labels:
|
||||
app: nginx
|
||||
annotations:
|
||||
secrets.infisical.com/auto-reload: "true" # <- redeployment annotation
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: nginx
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:1.14.2
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: managed-secret # The name of your managed secret, the same that you're using in your InfisicalDynamicSecret CRD (spec.managedSecretReference.secretName)
|
||||
ports:
|
||||
- containerPort: 80
|
||||
```
|
||||
</Accordion>
|
||||
<Info>
|
||||
#### How it works
|
||||
When the lease changes, the operator will check to see which deployments are using the operator-managed Kubernetes secret that received the update.
|
||||
Then, for each deployment that has this annotation present, a rolling update will be triggered. A redeployment won't happen if the lease is renewed, only if it's recreated.
|
||||
</Info>
|
@@ -0,0 +1,400 @@
|
||||
---
|
||||
sidebarTitle: "InfisicalPushSecret CRD"
|
||||
title: "Using the InfisicalPushSecret CRD"
|
||||
description: "Learn how to use the InfisicalPushSecret CRD to push and manage secrets in Infisical."
|
||||
---
|
||||
|
||||
|
||||
## Push Secrets to Infisical
|
||||
|
||||
|
||||
### Example usage
|
||||
|
||||
Below is a sample InfisicalPushSecret CRD that pushes secrets defined in a Kubernetes secret to Infisical.
|
||||
|
||||
After filling out the fields in the InfisicalPushSecret CRD, you can apply it directly to your cluster.
|
||||
|
||||
Before applying the InfisicalPushSecret CRD, you need to create a Kubernetes secret containing the secrets you want to push to Infisical. An example can be seen below the InfisicalPushSecret CRD.
|
||||
|
||||
```yaml infisical-push-secret.yaml
|
||||
apiVersion: secrets.infisical.com/v1alpha1
|
||||
kind: InfisicalPushSecret
|
||||
metadata:
|
||||
name: infisical-push-secret-demo
|
||||
spec:
|
||||
resyncInterval: 1m
|
||||
hostAPI: https://app.infisical.com/api
|
||||
|
||||
# Optional, defaults to no replacement.
|
||||
updatePolicy: Replace # If set to replace, existing secrets inside Infisical will be replaced by the value of the PushSecret on sync.
|
||||
|
||||
# Optional, defaults to no deletion.
|
||||
deletionPolicy: Delete # If set to delete, the secret(s) inside Infisical managed by the operator, will be deleted if the InfisicalPushSecret CRD is deleted.
|
||||
|
||||
destination:
|
||||
projectId: <project-id>
|
||||
environmentSlug: <env-slug>
|
||||
secretsPath: <secret-path>
|
||||
|
||||
push:
|
||||
secret:
|
||||
secretName: push-secret-demo # Secret CRD
|
||||
secretNamespace: default
|
||||
|
||||
# Only have one authentication method defined or you are likely to run into authentication issues.
|
||||
# Remove all except one authentication method.
|
||||
authentication:
|
||||
awsIamAuth:
|
||||
identityId: <machine-identity-id>
|
||||
azureAuth:
|
||||
identityId: <machine-identity-id>
|
||||
gcpIamAuth:
|
||||
identityId: <machine-identity-id>
|
||||
serviceAccountKeyFilePath: </path-to-service-account-key-file.json>
|
||||
gcpIdTokenAuth:
|
||||
identityId: <machine-identity-id>
|
||||
kubernetesAuth:
|
||||
identityId: <machine-identity-id>
|
||||
serviceAccountRef:
|
||||
name: <secret-name>
|
||||
namespace: <secret-namespace>
|
||||
universalAuth:
|
||||
credentialsRef:
|
||||
secretName: <secret-name> # universal-auth-credentials
|
||||
secretNamespace: <secret-namespace> # default
|
||||
```
|
||||
|
||||
```yaml source-secret.yaml
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: push-secret-demo
|
||||
namespace: default
|
||||
stringData: # can also be "data", but needs to be base64 encoded
|
||||
API_KEY: some-api-key
|
||||
DATABASE_URL: postgres://127.0.0.1:5432
|
||||
ENCRYPTION_KEY: fabcc12-a22-facbaa4-11aa568aab
|
||||
```
|
||||
|
||||
```bash
|
||||
kubectl apply -f source-secret.yaml
|
||||
```
|
||||
|
||||
After applying the soruce-secret.yaml file, you are ready to apply the InfisicalPushSecret CRD.
|
||||
|
||||
```bash
|
||||
kubectl apply -f infisical-push-secret.yaml
|
||||
```
|
||||
|
||||
After applying the InfisicalPushSecret CRD, you should notice that the secrets you have defined in your source-secret.yaml file have been pushed to your specified destination in Infisical.
|
||||
|
||||
|
||||
### InfisicalPushSecret CRD properties
|
||||
|
||||
<Accordion title="hostAPI">
|
||||
If you are fetching secrets from a self-hosted instance of Infisical set the value of `hostAPI` to
|
||||
` https://your-self-hosted-instace.com/api`
|
||||
|
||||
When `hostAPI` is not defined the operator fetches secrets from Infisical Cloud.
|
||||
|
||||
<Accordion title="Advanced use case">
|
||||
If you have installed your Infisical instance within the same cluster as the Infisical operator, you can optionally access the Infisical backend's service directly without having to route through the public internet.
|
||||
To achieve this, use the following address for the hostAPI field:
|
||||
|
||||
``` bash
|
||||
http://<backend-svc-name>.<namespace>.svc.cluster.local:4000/api
|
||||
```
|
||||
|
||||
Make sure to replace `<backend-svc-name>` and `<namespace>` with the appropriate values for your backend service and namespace.
|
||||
|
||||
</Accordion>
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="resyncInterval">
|
||||
|
||||
The `resyncInterval` is a string-formatted duration that defines the time between each resync.
|
||||
|
||||
The format of the field is `[duration][unit]` where `duration` is a number and `unit` is a string representing the unit of time.
|
||||
|
||||
The following units are supported:
|
||||
- `s` for seconds (must be at least 5 seconds)
|
||||
- `m` for minutes
|
||||
- `h` for hours
|
||||
- `d` for days
|
||||
- `w` for weeks
|
||||
|
||||
The default value is `1m` (1 minute).
|
||||
|
||||
Valid intervals examples:
|
||||
```yaml
|
||||
resyncInterval: 5s # 10 seconds
|
||||
resyncInterval: 10s # 10 seconds
|
||||
resyncInterval: 5m # 5 minutes
|
||||
resyncInterval: 1h # 1 hour
|
||||
resyncInterval: 1d # 1 day
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="updatePolicy">
|
||||
|
||||
The field is optional and will default to `None` if not defined.
|
||||
|
||||
The update policy defines how the operator should handle conflicting secrets when pushing secrets to Infisical.
|
||||
|
||||
Valid values are `None` and `Replace`.
|
||||
|
||||
Behavior of each policy:
|
||||
- `None`: The operator will not override existing secrets in Infisical. If a secret with the same key already exists, the operator will skip pushing that secret, and the secret will not be managed by the operator.
|
||||
- `Replace`: The operator will replace existing secrets in Infisical with the new secrets. If a secret with the same key already exists, the operator will update the secret with the new value.
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
updatePolicy: Replace
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="deletionPolicy">
|
||||
|
||||
This field is optional and will default to `None` if not defined.
|
||||
|
||||
The deletion policy defines what the operator should do in case the InfisicalPushSecret CRD is deleted.
|
||||
|
||||
Valid values are `None` and `Delete`.
|
||||
|
||||
Behavior of each policy:
|
||||
- `None`: The operator will not delete the secrets in Infisical when the InfisicalPushSecret CRD is deleted.
|
||||
- `Delete`: The operator will delete the secrets in Infisical that are managed by the operator when the InfisicalPushSecret CRD is deleted.
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
deletionPolicy: Delete
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="destination">
|
||||
The `destination` field is used to specify where you want to create the secrets in Infisical. The required fields are `projectId`, `environmentSlug`, and `secretsPath`.
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
destination:
|
||||
projectId: <project-id>
|
||||
environmentSlug: <env-slug>
|
||||
secretsPath: <secrets-path>
|
||||
```
|
||||
|
||||
<Accordion title="destination.projectId">
|
||||
The project ID where you want to create the secrets in Infisical.
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="destination.environmentSlug">
|
||||
The environment slug where you want to create the secrets in Infisical.
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="destination.secretsPath">
|
||||
The path where you want to create the secrets in Infisical. The root path is `/`.
|
||||
</Accordion>
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="push">
|
||||
The `push` field is used to define what you want to push to Infisical. Currently the operator only supports pushing Kubernetes secrets to Infisical. An example of the `push` field is shown below.
|
||||
|
||||
|
||||
|
||||
<Accordion title="secret">
|
||||
The `secret` field is used to define the Kubernetes secret you want to push to Infisical. The required fields are `secretName` and `secretNamespace`.
|
||||
|
||||
|
||||
|
||||
Example usage of the `push.secret` field:
|
||||
|
||||
```yaml infisical-push-secret.yaml
|
||||
push:
|
||||
secret:
|
||||
secretName: push-secret-demo
|
||||
secretNamespace: default
|
||||
```
|
||||
|
||||
```yaml push-secret-demo.yaml
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: push-secret-demo
|
||||
namespace: default
|
||||
# Pass in the secrets you wish to push to Infisical
|
||||
stringData:
|
||||
API_KEY: some-api-key
|
||||
DATABASE_URL: postgres://127.0.0.1:5432
|
||||
ENCRYPTION_KEY: fabcc12-a22-facbaa4-11aa568aab
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="authentication">
|
||||
|
||||
The `authentication` field dictates which authentication method to use when pushing secrets to Infisical.
|
||||
The available authentication methods are `universalAuth`, `kubernetesAuth`, `awsIamAuth`, `azureAuth`, `gcpIdTokenAuth`, and `gcpIamAuth`.
|
||||
|
||||
|
||||
<Accordion title="universalAuth">
|
||||
The universal authentication method is one of the easiest ways to get started with Infisical. Universal Auth works anywhere and is not tied to any specific cloud provider.
|
||||
[Read more about Universal Auth](/documentation/platform/identities/universal-auth).
|
||||
|
||||
Valid fields:
|
||||
- `identityId`: The identity ID of the machine identity you created.
|
||||
- `credentialsRef`: The name and namespace of the Kubernetes secret that stores the service token.
|
||||
- `credentialsRef.secretName`: The name of the Kubernetes secret.
|
||||
- `credentialsRef.secretNamespace`: The namespace of the Kubernetes secret.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
# infisical-push-secret.yaml
|
||||
spec:
|
||||
universalAuth:
|
||||
credentialsRef:
|
||||
secretName: <secret-name>
|
||||
secretNamespace: <secret-namespace>
|
||||
```
|
||||
|
||||
```yaml
|
||||
# machine-identity-credentials.yaml
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: universal-auth-credentials
|
||||
type: Opaque
|
||||
stringData:
|
||||
clientId: <machine-identity-client-id>
|
||||
clientSecret: <machine-identity-client-secret>
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="kubernetesAuth">
|
||||
The Kubernetes machine identity authentication method is used to authenticate with Infisical. The identity ID is stored in a field in the InfisicalSecret resource. This authentication method can only be used within a Kubernetes environment.
|
||||
[Read more about Kubernetes Auth](/documentation/platform/identities/kubernetes-auth).
|
||||
Valid fields:
|
||||
- `identityId`: The identity ID of the machine identity you created.
|
||||
- `serviceAccountRef`: The name and namespace of the service account that will be used to authenticate with Infisical.
|
||||
- `serviceAccountRef.name`: The name of the service account.
|
||||
- `serviceAccountRef.namespace`: The namespace of the service account.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
kubernetesAuth:
|
||||
identityId: <machine-identity-id>
|
||||
serviceAccountRef:
|
||||
name: <secret-name>
|
||||
namespace: <secret-namespace>
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="awsIamAuth">
|
||||
The AWS IAM machine identity authentication method is used to authenticate with Infisical.
|
||||
[Read more about AWS IAM Auth](/documentation/platform/identities/aws-auth).
|
||||
|
||||
Valid fields:
|
||||
- `identityId`: The identity ID of the machine identity you created.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
authentication:
|
||||
awsIamAuth:
|
||||
identityId: <machine-identity-id>
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="azureAuth">
|
||||
The AWS IAM machine identity authentication method is used to authenticate with Infisical. Azure Auth can only be used from within an Azure environment.
|
||||
[Read more about Azure Auth](/documentation/platform/identities/azure-auth).
|
||||
|
||||
Valid fields:
|
||||
- `identityId`: The identity ID of the machine identity you created.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
authentication:
|
||||
azureAuth:
|
||||
identityId: <machine-identity-id>
|
||||
```
|
||||
</Accordion>
|
||||
<Accordion title="gcpIamAuth">
|
||||
The GCP IAM machine identity authentication method is used to authenticate with Infisical. The identity ID is stored in a field in the InfisicalSecret resource. This authentication method can only be used both within and outside GCP environments.
|
||||
[Read more about Azure Auth](/documentation/platform/identities/gcp-auth).
|
||||
|
||||
|
||||
Valid fields:
|
||||
- `identityId`: The identity ID of the machine identity you created.
|
||||
- `serviceAccountKeyFilePath`: The path to the GCP service account key file.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
gcpIamAuth:
|
||||
identityId: <machine-identity-id>
|
||||
serviceAccountKeyFilePath: </path-to-service-account-key-file.json>
|
||||
```
|
||||
</Accordion>
|
||||
<Accordion title="gcpIdTokenAuth">
|
||||
The GCP ID Token machine identity authentication method is used to authenticate with Infisical. The identity ID is stored in a field in the InfisicalSecret resource. This authentication method can only be used within GCP environments.
|
||||
[Read more about Azure Auth](/documentation/platform/identities/gcp-auth).
|
||||
|
||||
Valid fields:
|
||||
- `identityId`: The identity ID of the machine identity you created.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
gcpIdTokenAuth:
|
||||
identityId: <machine-identity-id>
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
</Accordion>
|
||||
|
||||
|
||||
<Accordion title="tls">
|
||||
This block defines the TLS settings to use for connecting to the Infisical
|
||||
instance.
|
||||
|
||||
Fields:
|
||||
<Accordion title="caRef">
|
||||
This block defines the reference to the CA certificate to use for connecting to the Infisical instance with SSL/TLS.
|
||||
|
||||
Valid fields:
|
||||
- `secretName`: The name of the Kubernetes secret containing the CA certificate to use for connecting to the Infisical instance with SSL/TLS.
|
||||
- `secretNamespace`: The namespace of the Kubernetes secret containing the CA certificate to use for connecting to the Infisical instance with SSL/TLS.
|
||||
- `key`: The name of the key in the Kubernetes secret which contains the value of the CA certificate to use for connecting to the Infisical instance with SSL/TLS.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
tls:
|
||||
caRef:
|
||||
secretName: custom-ca-certificate
|
||||
secretNamespace: default
|
||||
key: ca.crt
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
</Accordion>
|
||||
|
||||
|
||||
### Applying the InfisicalPushSecret CRD to your cluster
|
||||
|
||||
Once you have configured the `InfisicalPushSecret` CRD with the required fields, you can apply it to your cluster.
|
||||
After applying, you should notice that the secrets have been pushed to Infisical.
|
||||
|
||||
```bash
|
||||
kubectl apply -f source-push-secret.yaml # The secret that you're referencing in the InfisicalPushSecret CRD push.secret field
|
||||
kubectl apply -f example-infisical-push-secret-crd.yaml # The InfisicalPushSecret CRD itself
|
||||
```
|
@@ -1,109 +1,9 @@
|
||||
---
|
||||
title: "Kubernetes Operator"
|
||||
description: "How to use Infisical to inject secrets into Kubernetes clusters."
|
||||
sidebarTitle: "InfisicalSecret CRD"
|
||||
title: "InfisicalSecret CRD"
|
||||
description: "Learn how to use the InfisicalSecret CRD to fetch secrets from Infisical and store them as native Kubernetes secret resource"
|
||||
---
|
||||
|
||||

|
||||
|
||||
The Infisical Secrets Operator is a Kubernetes controller that retrieves secrets from Infisical and stores them in a designated cluster.
|
||||
It uses an `InfisicalSecret` resource to specify authentication and storage methods.
|
||||
The operator continuously updates secrets and can also reload dependent deployments automatically.
|
||||
|
||||
<Note>
|
||||
If you are already using the External Secrets operator, you can view the
|
||||
integration documentation for it
|
||||
[here](https://external-secrets.io/latest/provider/infisical/).
|
||||
</Note>
|
||||
|
||||
## Install Operator
|
||||
|
||||
The operator can be install via [Helm](https://helm.sh) or [kubectl](https://github.com/kubernetes/kubectl)
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Helm (recommended)">
|
||||
**Install the latest Infisical Helm repository**
|
||||
```bash
|
||||
helm repo add infisical-helm-charts 'https://dl.cloudsmith.io/public/infisical/helm-charts/helm/charts/'
|
||||
|
||||
helm repo update
|
||||
```
|
||||
|
||||
**Install the Helm chart**
|
||||
|
||||
To select a specific version, view the application versions [here](https://hub.docker.com/r/infisical/kubernetes-operator/tags) and chart versions [here](https://cloudsmith.io/~infisical/repos/helm-charts/packages/detail/helm/secrets-operator/#versions)
|
||||
|
||||
```bash
|
||||
helm install --generate-name infisical-helm-charts/secrets-operator
|
||||
```
|
||||
|
||||
```bash
|
||||
# Example installing app version v0.2.0 and chart version 0.1.4
|
||||
helm install --generate-name infisical-helm-charts/secrets-operator --version=0.1.4 --set controllerManager.manager.image.tag=v0.2.0
|
||||
```
|
||||
|
||||
**Namespace-scoped Installation**
|
||||
|
||||
The operator can be configured to watch and manage secrets in a specific namespace instead of having cluster-wide access. This is useful for:
|
||||
|
||||
- **Enhanced Security**: Limit the operator's permissions to only specific namespaces instead of cluster-wide access
|
||||
- **Multi-tenant Clusters**: Run separate operator instances for different teams or applications
|
||||
- **Resource Isolation**: Ensure operators in different namespaces don't interfere with each other
|
||||
- **Development & Testing**: Run development and production operators side by side in isolated namespaces
|
||||
|
||||
**Note**: For multiple namespace-scoped installations, only the first installation should install CRDs. Subsequent installations should set `installCRDs: false` to avoid conflicts.
|
||||
|
||||
```bash
|
||||
# First namespace installation (with CRDs)
|
||||
helm install operator-namespace1 infisical-helm-charts/secrets-operator \
|
||||
--namespace first-namespace \
|
||||
--set scopedNamespace=first-namespace \
|
||||
--set scopedRBAC=true
|
||||
|
||||
# Subsequent namespace installations
|
||||
helm install operator-namespace2 infisical-helm-charts/secrets-operator \
|
||||
--namespace another-namespace \
|
||||
--set scopedNamespace=another-namespace \
|
||||
--set scopedRBAC=true \
|
||||
--set installCRDs=false
|
||||
```
|
||||
|
||||
When scoped to a namespace, the operator will:
|
||||
|
||||
- Only watch InfisicalSecrets in the specified namespace
|
||||
- Only create/update Kubernetes secrets in that namespace
|
||||
- Only access deployments in that namespace
|
||||
|
||||
The default configuration gives cluster-wide access:
|
||||
|
||||
```yaml
|
||||
installCRDs: true # Install CRDs (set to false for additional namespace installations)
|
||||
scopedNamespace: "" # Empty for cluster-wide access
|
||||
scopedRBAC: false # Cluster-wide permissions
|
||||
```
|
||||
|
||||
If you want to install operators in multiple namespaces simultaneously:
|
||||
- Make sure to set `installCRDs: false` for all but one of the installations to avoid conflicts, as CRDs are cluster-wide resources.
|
||||
- Use unique release names for each installation (e.g., operator-namespace1, operator-namespace2).
|
||||
|
||||
</Tab>
|
||||
<Tab title="Kubectl">
|
||||
For production deployments, it is highly recommended to set the version of the Kubernetes operator manually instead of pointing to the latest version.
|
||||
Doing so will help you avoid accidental updates to the newest release which may introduce unintended breaking changes. View all application versions [here](https://hub.docker.com/r/infisical/kubernetes-operator/tags).
|
||||
|
||||
The command below will install the most recent version of the Kubernetes operator.
|
||||
However, to set the version manually, download the manifest and set the image tag version of `infisical/kubernetes-operator` according to your desired version.
|
||||
|
||||
Once you apply the manifest, the operator will be installed in `infisical-operator-system` namespace.
|
||||
|
||||
```
|
||||
kubectl apply -f https://raw.githubusercontent.com/Infisical/infisical/main/k8-operator/kubectl-install/install-secrets-operator.yaml
|
||||
```
|
||||
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Sync Infisical Secrets to your cluster
|
||||
|
||||
Once you have installed the operator to your cluster, you'll need to create a `InfisicalSecret` custom resource definition (CRD).
|
||||
|
||||
```yaml example-infisical-secret-crd.yaml
|
||||
@@ -789,48 +689,6 @@ This is useful for tools such as ArgoCD, where every resource requires an owner
|
||||
|
||||
</Accordion>
|
||||
|
||||
### Propagating labels & annotations
|
||||
|
||||
The operator will transfer all labels & annotations present on the `InfisicalSecret` CRD to the managed Kubernetes secret to be created.
|
||||
Thus, if a specific label is required on the resulting secret, it can be applied as demonstrated in the following example:
|
||||
|
||||
<Accordion title="Example propagation">
|
||||
```yaml
|
||||
apiVersion: secrets.infisical.com/v1alpha1
|
||||
kind: InfisicalSecret
|
||||
metadata:
|
||||
name: infisicalsecret-sample
|
||||
labels:
|
||||
label-to-be-passed-to-managed-secret: sample-value
|
||||
annotations:
|
||||
example.com/annotation-to-be-passed-to-managed-secret: "sample-value"
|
||||
spec:
|
||||
..
|
||||
authentication:
|
||||
...
|
||||
managedSecretReference:
|
||||
...
|
||||
```
|
||||
|
||||
This would result in the following managed secret to be created:
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
data: ...
|
||||
kind: Secret
|
||||
metadata:
|
||||
annotations:
|
||||
example.com/annotation-to-be-passed-to-managed-secret: sample-value
|
||||
secrets.infisical.com/version: W/"3f1-ZyOSsrCLGSkAhhCkY2USPu2ivRw"
|
||||
labels:
|
||||
label-to-be-passed-to-managed-secret: sample-value
|
||||
name: managed-token
|
||||
namespace: default
|
||||
type: Opaque
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
### Apply the InfisicalSecret CRD to your cluster
|
||||
|
||||
Once you have configured the InfisicalSecret CRD with the required fields, you can apply it to your cluster.
|
||||
@@ -854,7 +712,7 @@ kubectl get secrets -n <namespace of managed secret>
|
||||
1 minutes.
|
||||
</Info>
|
||||
|
||||
### Using managed secret in your deployment
|
||||
## Using managed secret in your deployment
|
||||
|
||||
Incorporating the managed secret created by the operator into your deployment can be achieved through several methods.
|
||||
Here, we will highlight three of the most common ways to utilize it. Learn more about Kubernetes secrets [here](https://kubernetes.io/docs/concepts/configuration/secret/)
|
||||
@@ -996,25 +854,6 @@ spec:
|
||||
|
||||
</Accordion>
|
||||
|
||||
### Connecting to instances with private/self-signed certificate
|
||||
|
||||
To connect to Infisical instances behind a private/self-signed certificate, you can configure the TLS settings in the `InfisicalSecret` CRD
|
||||
to point to a CA certificate stored in a Kubernetes secret resource.
|
||||
|
||||
```yaml
|
||||
---
|
||||
spec:
|
||||
hostAPI: https://app.infisical.com/api
|
||||
resyncInterval: 10
|
||||
tls:
|
||||
caRef:
|
||||
secretName: custom-ca-certificate
|
||||
secretNamespace: default
|
||||
key: ca.crt
|
||||
authentication:
|
||||
---
|
||||
```
|
||||
|
||||
The definition file of the Kubernetes secret for the CA certificate can be structured like the following:
|
||||
|
||||
```yaml
|
||||
@@ -1081,503 +920,44 @@ spec:
|
||||
Then, for each deployment that has this annotation present, a rolling update will be triggered.
|
||||
</Info>
|
||||
|
||||
## Push Secrets to Infisical
|
||||
## Propagating labels & annotations
|
||||
|
||||
The operator will transfer all labels & annotations present on the `InfisicalSecret` CRD to the managed Kubernetes secret to be created.
|
||||
Thus, if a specific label is required on the resulting secret, it can be applied as demonstrated in the following example:
|
||||
|
||||
### Example usage
|
||||
|
||||
Below is a sample InfisicalPushSecret CRD that pushes secrets defined in a Kubernetes secret to Infisical.
|
||||
|
||||
After filling out the fields in the InfisicalPushSecret CRD, you can apply it directly to your cluster.
|
||||
|
||||
Before applying the InfisicalPushSecret CRD, you need to create a Kubernetes secret containing the secrets you want to push to Infisical. An example can be seen below the InfisicalPushSecret CRD.
|
||||
|
||||
```bash
|
||||
kubectl apply -f source-secret.yaml
|
||||
```
|
||||
|
||||
After applying the soruce-secret.yaml file, you are ready to apply the InfisicalPushSecret CRD.
|
||||
|
||||
```bash
|
||||
kubectl apply -f infisical-push-secret.yaml
|
||||
```
|
||||
|
||||
After applying the InfisicalPushSecret CRD, you should notice that the secrets you have defined in your source-secret.yaml file have been pushed to your specified destination in Infisical.
|
||||
|
||||
```yaml infisical-push-secret.yaml
|
||||
apiVersion: secrets.infisical.com/v1alpha1
|
||||
kind: InfisicalPushSecret
|
||||
metadata:
|
||||
name: infisical-push-secret-demo
|
||||
spec:
|
||||
resyncInterval: 1m
|
||||
hostAPI: https://app.infisical.com/api
|
||||
|
||||
# Optional, defaults to no replacement.
|
||||
updatePolicy: Replace # If set to replace, existing secrets inside Infisical will be replaced by the value of the PushSecret on sync.
|
||||
|
||||
# Optional, defaults to no deletion.
|
||||
deletionPolicy: Delete # If set to delete, the secret(s) inside Infisical managed by the operator, will be deleted if the InfisicalPushSecret CRD is deleted.
|
||||
|
||||
destination:
|
||||
projectId: <project-id>
|
||||
environmentSlug: <env-slug>
|
||||
secretsPath: <secret-path>
|
||||
|
||||
push:
|
||||
secret:
|
||||
secretName: push-secret-demo # Secret CRD
|
||||
secretNamespace: default
|
||||
|
||||
# Only have one authentication method defined or you are likely to run into authentication issues.
|
||||
# Remove all except one authentication method.
|
||||
authentication:
|
||||
awsIamAuth:
|
||||
identityId: <machine-identity-id>
|
||||
azureAuth:
|
||||
identityId: <machine-identity-id>
|
||||
gcpIamAuth:
|
||||
identityId: <machine-identity-id>
|
||||
serviceAccountKeyFilePath: </path-to-service-account-key-file.json>
|
||||
gcpIdTokenAuth:
|
||||
identityId: <machine-identity-id>
|
||||
kubernetesAuth:
|
||||
identityId: <machine-identity-id>
|
||||
serviceAccountRef:
|
||||
name: <secret-name>
|
||||
namespace: <secret-namespace>
|
||||
universalAuth:
|
||||
credentialsRef:
|
||||
secretName: <secret-name> # universal-auth-credentials
|
||||
secretNamespace: <secret-namespace> # default
|
||||
```
|
||||
|
||||
```yaml source-secret.yaml
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: push-secret-demo
|
||||
namespace: default
|
||||
stringData: # can also be "data", but needs to be base64 encoded
|
||||
API_KEY: some-api-key
|
||||
DATABASE_URL: postgres://127.0.0.1:5432
|
||||
ENCRYPTION_KEY: fabcc12-a22-facbaa4-11aa568aab
|
||||
```
|
||||
|
||||
### InfisicalPushSecret CRD properties
|
||||
|
||||
<Accordion title="hostAPI">
|
||||
If you are fetching secrets from a self-hosted instance of Infisical set the value of `hostAPI` to
|
||||
` https://your-self-hosted-instace.com/api`
|
||||
|
||||
When `hostAPI` is not defined the operator fetches secrets from Infisical Cloud.
|
||||
|
||||
<Accordion title="Advanced use case">
|
||||
If you have installed your Infisical instance within the same cluster as the Infisical operator, you can optionally access the Infisical backend's service directly without having to route through the public internet.
|
||||
To achieve this, use the following address for the hostAPI field:
|
||||
|
||||
``` bash
|
||||
http://<backend-svc-name>.<namespace>.svc.cluster.local:4000/api
|
||||
```
|
||||
|
||||
Make sure to replace `<backend-svc-name>` and `<namespace>` with the appropriate values for your backend service and namespace.
|
||||
|
||||
</Accordion>
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="resyncInterval">
|
||||
|
||||
The `resyncInterval` is a string-formatted duration that defines the time between each resync.
|
||||
|
||||
The format of the field is `[duration][unit]` where `duration` is a number and `unit` is a string representing the unit of time.
|
||||
|
||||
The following units are supported:
|
||||
- `s` for seconds (must be at least 5 seconds)
|
||||
- `m` for minutes
|
||||
- `h` for hours
|
||||
- `d` for days
|
||||
- `w` for weeks
|
||||
|
||||
The default value is `1m` (1 minute).
|
||||
|
||||
Valid intervals examples:
|
||||
```yaml
|
||||
resyncInterval: 5s # 10 seconds
|
||||
resyncInterval: 10s # 10 seconds
|
||||
resyncInterval: 5m # 5 minutes
|
||||
resyncInterval: 1h # 1 hour
|
||||
resyncInterval: 1d # 1 day
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="updatePolicy">
|
||||
|
||||
The field is optional and will default to `None` if not defined.
|
||||
|
||||
The update policy defines how the operator should handle conflicting secrets when pushing secrets to Infisical.
|
||||
|
||||
Valid values are `None` and `Replace`.
|
||||
|
||||
Behavior of each policy:
|
||||
- `None`: The operator will not override existing secrets in Infisical. If a secret with the same key already exists, the operator will skip pushing that secret, and the secret will not be managed by the operator.
|
||||
- `Replace`: The operator will replace existing secrets in Infisical with the new secrets. If a secret with the same key already exists, the operator will update the secret with the new value.
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
updatePolicy: Replace
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="deletionPolicy">
|
||||
|
||||
This field is optional and will default to `None` if not defined.
|
||||
|
||||
The deletion policy defines what the operator should do in case the InfisicalPushSecret CRD is deleted.
|
||||
|
||||
Valid values are `None` and `Delete`.
|
||||
|
||||
Behavior of each policy:
|
||||
- `None`: The operator will not delete the secrets in Infisical when the InfisicalPushSecret CRD is deleted.
|
||||
- `Delete`: The operator will delete the secrets in Infisical that are managed by the operator when the InfisicalPushSecret CRD is deleted.
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
deletionPolicy: Delete
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="destination">
|
||||
The `destination` field is used to specify where you want to create the secrets in Infisical. The required fields are `projectId`, `environmentSlug`, and `secretsPath`.
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
destination:
|
||||
projectId: <project-id>
|
||||
environmentSlug: <env-slug>
|
||||
secretsPath: <secrets-path>
|
||||
```
|
||||
|
||||
<Accordion title="destination.projectId">
|
||||
The project ID where you want to create the secrets in Infisical.
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="destination.environmentSlug">
|
||||
The environment slug where you want to create the secrets in Infisical.
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="destination.secretsPath">
|
||||
The path where you want to create the secrets in Infisical. The root path is `/`.
|
||||
</Accordion>
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="push">
|
||||
The `push` field is used to define what you want to push to Infisical. Currently the operator only supports pushing Kubernetes secrets to Infisical. An example of the `push` field is shown below.
|
||||
|
||||
|
||||
|
||||
<Accordion title="secret">
|
||||
The `secret` field is used to define the Kubernetes secret you want to push to Infisical. The required fields are `secretName` and `secretNamespace`.
|
||||
|
||||
|
||||
|
||||
Example usage of the `push.secret` field:
|
||||
|
||||
```yaml infisical-push-secret.yaml
|
||||
push:
|
||||
secret:
|
||||
secretName: push-secret-demo
|
||||
secretNamespace: default
|
||||
```
|
||||
|
||||
```yaml push-secret-demo.yaml
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: push-secret-demo
|
||||
namespace: default
|
||||
# Pass in the secrets you wish to push to Infisical
|
||||
stringData:
|
||||
API_KEY: some-api-key
|
||||
DATABASE_URL: postgres://127.0.0.1:5432
|
||||
ENCRYPTION_KEY: fabcc12-a22-facbaa4-11aa568aab
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="authentication">
|
||||
|
||||
The `authentication` field dictates which authentication method to use when pushing secrets to Infisical.
|
||||
The available authentication methods are `universalAuth`, `kubernetesAuth`, `awsIamAuth`, `azureAuth`, `gcpIdTokenAuth`, and `gcpIamAuth`.
|
||||
|
||||
|
||||
<Accordion title="universalAuth">
|
||||
The universal authentication method is one of the easiest ways to get started with Infisical. Universal Auth works anywhere and is not tied to any specific cloud provider.
|
||||
[Read more about Universal Auth](/documentation/platform/identities/universal-auth).
|
||||
|
||||
Valid fields:
|
||||
- `identityId`: The identity ID of the machine identity you created.
|
||||
- `credentialsRef`: The name and namespace of the Kubernetes secret that stores the service token.
|
||||
- `credentialsRef.secretName`: The name of the Kubernetes secret.
|
||||
- `credentialsRef.secretNamespace`: The namespace of the Kubernetes secret.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
# infisical-push-secret.yaml
|
||||
spec:
|
||||
universalAuth:
|
||||
credentialsRef:
|
||||
secretName: <secret-name>
|
||||
secretNamespace: <secret-namespace>
|
||||
```
|
||||
|
||||
```yaml
|
||||
# machine-identity-credentials.yaml
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: universal-auth-credentials
|
||||
type: Opaque
|
||||
stringData:
|
||||
clientId: <machine-identity-client-id>
|
||||
clientSecret: <machine-identity-client-secret>
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="kubernetesAuth">
|
||||
The Kubernetes machine identity authentication method is used to authenticate with Infisical. The identity ID is stored in a field in the InfisicalSecret resource. This authentication method can only be used within a Kubernetes environment.
|
||||
[Read more about Kubernetes Auth](/documentation/platform/identities/kubernetes-auth).
|
||||
Valid fields:
|
||||
- `identityId`: The identity ID of the machine identity you created.
|
||||
- `serviceAccountRef`: The name and namespace of the service account that will be used to authenticate with Infisical.
|
||||
- `serviceAccountRef.name`: The name of the service account.
|
||||
- `serviceAccountRef.namespace`: The namespace of the service account.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
kubernetesAuth:
|
||||
identityId: <machine-identity-id>
|
||||
serviceAccountRef:
|
||||
name: <secret-name>
|
||||
namespace: <secret-namespace>
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="awsIamAuth">
|
||||
The AWS IAM machine identity authentication method is used to authenticate with Infisical.
|
||||
[Read more about AWS IAM Auth](/documentation/platform/identities/aws-auth).
|
||||
|
||||
Valid fields:
|
||||
- `identityId`: The identity ID of the machine identity you created.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
authentication:
|
||||
awsIamAuth:
|
||||
identityId: <machine-identity-id>
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="azureAuth">
|
||||
The AWS IAM machine identity authentication method is used to authenticate with Infisical. Azure Auth can only be used from within an Azure environment.
|
||||
[Read more about Azure Auth](/documentation/platform/identities/azure-auth).
|
||||
|
||||
Valid fields:
|
||||
- `identityId`: The identity ID of the machine identity you created.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
authentication:
|
||||
azureAuth:
|
||||
identityId: <machine-identity-id>
|
||||
```
|
||||
</Accordion>
|
||||
<Accordion title="gcpIamAuth">
|
||||
The GCP IAM machine identity authentication method is used to authenticate with Infisical. The identity ID is stored in a field in the InfisicalSecret resource. This authentication method can only be used both within and outside GCP environments.
|
||||
[Read more about Azure Auth](/documentation/platform/identities/gcp-auth).
|
||||
|
||||
|
||||
Valid fields:
|
||||
- `identityId`: The identity ID of the machine identity you created.
|
||||
- `serviceAccountKeyFilePath`: The path to the GCP service account key file.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
gcpIamAuth:
|
||||
identityId: <machine-identity-id>
|
||||
serviceAccountKeyFilePath: </path-to-service-account-key-file.json>
|
||||
```
|
||||
</Accordion>
|
||||
<Accordion title="gcpIdTokenAuth">
|
||||
The GCP ID Token machine identity authentication method is used to authenticate with Infisical. The identity ID is stored in a field in the InfisicalSecret resource. This authentication method can only be used within GCP environments.
|
||||
[Read more about Azure Auth](/documentation/platform/identities/gcp-auth).
|
||||
|
||||
Valid fields:
|
||||
- `identityId`: The identity ID of the machine identity you created.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
gcpIdTokenAuth:
|
||||
identityId: <machine-identity-id>
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
</Accordion>
|
||||
|
||||
|
||||
<Accordion title="tls">
|
||||
This block defines the TLS settings to use for connecting to the Infisical
|
||||
instance.
|
||||
|
||||
Fields:
|
||||
<Accordion title="caRef">
|
||||
This block defines the reference to the CA certificate to use for connecting to the Infisical instance with SSL/TLS.
|
||||
|
||||
Valid fields:
|
||||
- `secretName`: The name of the Kubernetes secret containing the CA certificate to use for connecting to the Infisical instance with SSL/TLS.
|
||||
- `secretNamespace`: The namespace of the Kubernetes secret containing the CA certificate to use for connecting to the Infisical instance with SSL/TLS.
|
||||
- `key`: The name of the key in the Kubernetes secret which contains the value of the CA certificate to use for connecting to the Infisical instance with SSL/TLS.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
tls:
|
||||
caRef:
|
||||
secretName: custom-ca-certificate
|
||||
secretNamespace: default
|
||||
key: ca.crt
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
</Accordion>
|
||||
|
||||
|
||||
### Applying the InfisicalPushSecret CRD to your cluster
|
||||
|
||||
Once you have configured the `InfisicalPushSecret` CRD with the required fields, you can apply it to your cluster.
|
||||
After applying, you should notice that the secrets have been pushed to Infisical.
|
||||
|
||||
```bash
|
||||
kubectl apply -f source-push-secret.yaml # The secret that you're referencing in the InfisicalPushSecret CRD push.secret field
|
||||
kubectl apply -f example-infisical-push-secret-crd.yaml # The InfisicalPushSecret CRD itself
|
||||
```
|
||||
|
||||
### Connecting to instances with private/self-signed certificate
|
||||
|
||||
To connect to Infisical instances behind a private/self-signed certificate, you can configure the TLS settings in the `InfisicalPushSecret` CRD
|
||||
to point to a CA certificate stored in a Kubernetes secret resource.
|
||||
|
||||
<Accordion title="Example propagation">
|
||||
```yaml
|
||||
apiVersion: secrets.infisical.com/v1alpha1
|
||||
kind: InfisicalSecret
|
||||
metadata:
|
||||
name: infisicalsecret-sample
|
||||
labels:
|
||||
label-to-be-passed-to-managed-secret: sample-value
|
||||
annotations:
|
||||
example.com/annotation-to-be-passed-to-managed-secret: "sample-value"
|
||||
spec:
|
||||
hostAPI: https://app.infisical.com/api
|
||||
resyncInterval: 30s
|
||||
tls:
|
||||
caRef:
|
||||
secretName: custom-ca-certificate
|
||||
secretNamespace: default
|
||||
key: ca.crt
|
||||
..
|
||||
authentication:
|
||||
# ...
|
||||
...
|
||||
managedSecretReference:
|
||||
...
|
||||
```
|
||||
|
||||
|
||||
## Global configuration
|
||||
|
||||
To configure global settings that will apply to all instances of `InfisicalSecret`, you can define these configurations in a Kubernetes ConfigMap.
|
||||
For example, you can configure all `InfisicalSecret` instances to fetch secrets from a single backend API without specifying the `hostAPI` parameter for each instance.
|
||||
|
||||
### Available global properties
|
||||
|
||||
| Property | Description | Default value |
|
||||
| -------- | --------------------------------------------------------------------------------- | ----------------------------- |
|
||||
| hostAPI | If `hostAPI` in `InfisicalSecret` instance is left empty, this value will be used | https://app.infisical.com/api |
|
||||
|
||||
### Applying global configurations
|
||||
|
||||
All global configurations must reside in a Kubernetes ConfigMap named `infisical-config` in the namespace `infisical-operator-system`.
|
||||
To apply global configuration to the operator, copy the following yaml into `infisical-config.yaml` file.
|
||||
|
||||
```yaml infisical-config.yaml
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: infisical-operator-system
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: infisical-config
|
||||
namespace: infisical-operator-system
|
||||
data:
|
||||
hostAPI: https://example.com/api # <-- global hostAPI
|
||||
```
|
||||
|
||||
Then apply this change via kubectl by running the following
|
||||
|
||||
```bash
|
||||
kubectl apply -f infisical-config.yaml
|
||||
```
|
||||
|
||||
## Troubleshoot operator
|
||||
|
||||
If the operator is unable to fetch secrets from the API, it will not affect the managed Kubernetes secret.
|
||||
It will continue attempting to reconnect to the API indefinitely.
|
||||
The InfisicalSecret resource uses the `status.conditions` field to report its current state and any errors encountered.
|
||||
This would result in the following managed secret to be created:
|
||||
|
||||
```yaml
|
||||
$ kubectl get infisicalSecrets
|
||||
NAME AGE
|
||||
infisicalsecret-sample 12s
|
||||
|
||||
$ kubectl describe infisicalSecret infisicalsecret-sample
|
||||
...
|
||||
Spec:
|
||||
...
|
||||
Status:
|
||||
Conditions:
|
||||
Last Transition Time: 2022-12-18T04:29:09Z
|
||||
Message: Infisical controller has located the Infisical token in provided Kubernetes secret
|
||||
Reason: OK
|
||||
Status: True
|
||||
Type: secrets.infisical.com/LoadedInfisicalToken
|
||||
Last Transition Time: 2022-12-18T04:29:10Z
|
||||
Message: Failed to update secret because: 400 Bad Request
|
||||
Reason: Error
|
||||
Status: False
|
||||
Type: secrets.infisical.com/ReadyToSyncSecrets
|
||||
Events: <none>
|
||||
apiVersion: v1
|
||||
data: ...
|
||||
kind: Secret
|
||||
metadata:
|
||||
annotations:
|
||||
example.com/annotation-to-be-passed-to-managed-secret: sample-value
|
||||
secrets.infisical.com/version: W/"3f1-ZyOSsrCLGSkAhhCkY2USPu2ivRw"
|
||||
labels:
|
||||
label-to-be-passed-to-managed-secret: sample-value
|
||||
name: managed-token
|
||||
namespace: default
|
||||
type: Opaque
|
||||
```
|
||||
|
||||
## Uninstall Operator
|
||||
|
||||
The managed secret created by the operator will not be deleted when the operator is uninstalled.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Helm">
|
||||
Install Infisical Helm repository
|
||||
```bash
|
||||
helm uninstall <release name>
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Kubectl">
|
||||
```
|
||||
kubectl delete -f https://raw.githubusercontent.com/Infisical/infisical/main/k8-operator/kubectl-install/install-secrets-operator.yaml
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Useful Articles
|
||||
|
||||
- [Managing secrets in OpenShift with Infisical](https://xphyr.net/post/infisical_ocp/)
|
||||
</Accordion>
|
195
docs/integrations/platforms/kubernetes/overview.mdx
Normal file
@@ -0,0 +1,195 @@
|
||||
---
|
||||
title: "Kubernetes Operator"
|
||||
sidebarTitle: "Overview"
|
||||
description: "How to use Infisical to inject, push, and manage secrets within Kubernetes clusters"
|
||||
---
|
||||
|
||||
The Infisical Operator is a collection of Kubernetes controllers that streamline how secrets are managed between Infisical and your Kubernetes cluster.
|
||||
It provides multiple Custom Resource Definitions (CRDs) which enable you to:
|
||||
|
||||
- **Sync** secrets from Infisical into Kubernetes (`InfisicalSecret`).
|
||||
- **Push** new secrets from Kubernetes to Infisical (`InfisicalPushSecret`).
|
||||
- **Manage** dynamic secrets and automatically create time-bound leases (`InfisicalDynamicSecret`).
|
||||
|
||||
When these CRDs are configured, the Infisical Operator will continuously monitors for changes and performs necessary updates to keep your Kubernetes secrets up to date.
|
||||
It can also automatically reload dependent Deployments resources whenever relevant secrets are updated.
|
||||
|
||||
<Note>
|
||||
If you are already using the External Secrets operator, you can view the
|
||||
integration documentation for it
|
||||
[here](https://external-secrets.io/latest/provider/infisical/).
|
||||
</Note>
|
||||
|
||||
## Install
|
||||
|
||||
The operator can be install via [Helm](https://helm.sh). Helm is a package manager for Kubernetes that allows you to define, install, and upgrade Kubernetes applications.
|
||||
|
||||
**Install the latest Helm repository**
|
||||
```bash
|
||||
helm repo add infisical-helm-charts 'https://dl.cloudsmith.io/public/infisical/helm-charts/helm/charts/'
|
||||
```
|
||||
|
||||
```bash
|
||||
helm repo update
|
||||
```
|
||||
|
||||
The operator can be installed either cluster-wide or restricted to a specific namespace.
|
||||
If you require stronger isolation and stricter access controls, a namespace-scoped installation may make more sense.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Cluster Wide Installation">
|
||||
```bash
|
||||
helm install --generate-name infisical-helm-charts/secrets-operator
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Namespace Scoped Installation">
|
||||
The operator can be configured to watch and manage secrets in a specific namespace instead of having cluster-wide access. This is useful for:
|
||||
|
||||
- **Enhanced Security**: Limit the operator's permissions to only specific namespaces instead of cluster-wide access
|
||||
- **Multi-tenant Clusters**: Run separate operator instances for different teams or applications
|
||||
- **Resource Isolation**: Ensure operators in different namespaces don't interfere with each other
|
||||
- **Development & Testing**: Run development and production operators side by side in isolated namespaces
|
||||
|
||||
**Note**: For multiple namespace-scoped installations, only the first installation should install CRDs. Subsequent installations should set `installCRDs: false` to avoid conflicts.
|
||||
|
||||
```bash
|
||||
# First namespace installation (with CRDs)
|
||||
helm install operator-namespace1 infisical-helm-charts/secrets-operator \
|
||||
--namespace first-namespace \
|
||||
--set scopedNamespace=first-namespace \
|
||||
--set scopedRBAC=true
|
||||
|
||||
# Subsequent namespace installations
|
||||
helm install operator-namespace2 infisical-helm-charts/secrets-operator \
|
||||
--namespace another-namespace \
|
||||
--set scopedNamespace=another-namespace \
|
||||
--set scopedRBAC=true \
|
||||
--set installCRDs=false
|
||||
```
|
||||
|
||||
When scoped to a namespace, the operator will:
|
||||
|
||||
- Only watch InfisicalSecrets in the specified namespace
|
||||
- Only create/update Kubernetes secrets in that namespace
|
||||
- Only access deployments in that namespace
|
||||
|
||||
The default configuration gives cluster-wide access:
|
||||
|
||||
```yaml
|
||||
installCRDs: true # Install CRDs (set to false for additional namespace installations)
|
||||
scopedNamespace: "" # Empty for cluster-wide access
|
||||
scopedRBAC: false # Cluster-wide permissions
|
||||
```
|
||||
|
||||
If you want to install operators in multiple namespaces simultaneously:
|
||||
- Make sure to set `installCRDs: false` for all but one of the installations to avoid conflicts, as CRDs are cluster-wide resources.
|
||||
- Use unique release names for each installation (e.g., operator-namespace1, operator-namespace2).
|
||||
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Custom Resource Definitions
|
||||
|
||||
Currently the operator supports the following CRD's. We are constantly expanding the functionality of the operator, and this list will be updated as new CRD's are added.
|
||||
|
||||
1. [InfisicalSecret](/integrations/platforms/kubernetes/infisical-secret-crd): Sync secrets from Infisical to a Kubernetes secret.
|
||||
2. [InfisicalPushSecret](/integrations/platforms/kubernetes/infisical-push-secret-crd): Push secrets from a Kubernetes secret to Infisical.
|
||||
3. [InfisicalDynamicSecret](/integrations/platforms/kubernetes/infisical-dynamic-secret-crd): Sync dynamic secrets and create leases automatically in Kubernetes.
|
||||
|
||||
## General Configuration
|
||||
### Private/self-signed certificate
|
||||
To connect to Infisical instances behind a private/self-signed certificate, you can configure the TLS settings in the CRD
|
||||
to point to a CA certificate stored in a Kubernetes secret resource.
|
||||
|
||||
```yaml
|
||||
---
|
||||
spec:
|
||||
hostAPI: https://app.infisical.com/api
|
||||
tls:
|
||||
caRef:
|
||||
secretName: custom-ca-certificate
|
||||
secretNamespace: default
|
||||
key: ca.crt
|
||||
---
|
||||
```
|
||||
|
||||
|
||||
## Global configuration
|
||||
|
||||
To configure global settings that will apply to all instances of `InfisicalSecret`, you can define these configurations in a Kubernetes ConfigMap.
|
||||
For example, you can configure all `InfisicalSecret` instances to fetch secrets from a single backend API without specifying the `hostAPI` parameter for each instance.
|
||||
|
||||
### Available global properties
|
||||
|
||||
| Property | Description | Default value |
|
||||
| -------- | --------------------------------------------------------------------------------- | ----------------------------- |
|
||||
| hostAPI | If `hostAPI` in `InfisicalSecret` instance is left empty, this value will be used | https://app.infisical.com/api |
|
||||
|
||||
### Applying global configurations
|
||||
|
||||
All global configurations must reside in a Kubernetes ConfigMap named `infisical-config` in the namespace `infisical-operator-system`.
|
||||
To apply global configuration to the operator, copy the following yaml into `infisical-config.yaml` file.
|
||||
|
||||
```yaml infisical-config.yaml
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: infisical-operator-system
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: infisical-config
|
||||
namespace: infisical-operator-system
|
||||
data:
|
||||
hostAPI: https://example.com/api # <-- global hostAPI
|
||||
```
|
||||
|
||||
Then apply this change via kubectl by running the following
|
||||
|
||||
```bash
|
||||
kubectl apply -f infisical-config.yaml
|
||||
```
|
||||
|
||||
## Troubleshoot operator
|
||||
|
||||
If the operator is unable to fetch secrets from the API, it will not affect the managed Kubernetes secret.
|
||||
It will continue attempting to reconnect to the API indefinitely.
|
||||
The InfisicalSecret resource uses the `status.conditions` field to report its current state and any errors encountered.
|
||||
|
||||
```yaml
|
||||
$ kubectl get infisicalSecrets
|
||||
NAME AGE
|
||||
infisicalsecret-sample 12s
|
||||
|
||||
$ kubectl describe infisicalSecret infisicalsecret-sample
|
||||
...
|
||||
Spec:
|
||||
...
|
||||
Status:
|
||||
Conditions:
|
||||
Last Transition Time: 2022-12-18T04:29:09Z
|
||||
Message: Infisical controller has located the Infisical token in provided Kubernetes secret
|
||||
Reason: OK
|
||||
Status: True
|
||||
Type: secrets.infisical.com/LoadedInfisicalToken
|
||||
Last Transition Time: 2022-12-18T04:29:10Z
|
||||
Message: Failed to update secret because: 400 Bad Request
|
||||
Reason: Error
|
||||
Status: False
|
||||
Type: secrets.infisical.com/ReadyToSyncSecrets
|
||||
Events: <none>
|
||||
```
|
||||
|
||||
## Uninstall Operator
|
||||
|
||||
The managed secret created by the operator will not be deleted when the operator is uninstalled.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Helm">
|
||||
Install Infisical Helm repository
|
||||
```bash
|
||||
helm uninstall <release name>
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
@@ -248,6 +248,7 @@
|
||||
"documentation/platform/sso/jumpcloud",
|
||||
"documentation/platform/sso/keycloak-saml",
|
||||
"documentation/platform/sso/google-saml",
|
||||
"documentation/platform/sso/auth0-saml",
|
||||
"documentation/platform/sso/keycloak-oidc",
|
||||
"documentation/platform/sso/auth0-oidc",
|
||||
"documentation/platform/sso/general-oidc"
|
||||
@@ -348,7 +349,15 @@
|
||||
{
|
||||
"group": "Container orchestrators",
|
||||
"pages": [
|
||||
"integrations/platforms/kubernetes",
|
||||
{
|
||||
"group": "Kubernetes",
|
||||
"pages": [
|
||||
"integrations/platforms/kubernetes/overview",
|
||||
"integrations/platforms/kubernetes/infisical-secret-crd",
|
||||
"integrations/platforms/kubernetes/infisical-push-secret-crd",
|
||||
"integrations/platforms/kubernetes/infisical-dynamic-secret-crd"
|
||||
]
|
||||
},
|
||||
"integrations/platforms/kubernetes-csi",
|
||||
"integrations/platforms/docker-swarm-with-agent",
|
||||
"integrations/platforms/ecs-with-agent"
|
||||
|
@@ -101,6 +101,10 @@ export const ROUTE_PATHS = Object.freeze({
|
||||
"/secret-manager/$projectId/integrations/azure-devops/create",
|
||||
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/azure-devops/create"
|
||||
),
|
||||
AzureKeyVaultAuthorizePage: setRoute(
|
||||
"/secret-manager/$projectId/integrations/azure-key-vault/authorize",
|
||||
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/azure-key-vault/authorize"
|
||||
),
|
||||
AzureKeyVaultOauthCallbackPage: setRoute(
|
||||
"/secret-manager/$projectId/integrations/azure-key-vault/oauth2/callback",
|
||||
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/azure-key-vault/oauth2/callback"
|
||||
|
@@ -10,6 +10,7 @@ import { useToggle } from "@app/hooks/useToggle";
|
||||
import { ERROR_NOT_ALLOWED_READ_SECRETS } from "./constants";
|
||||
import {
|
||||
GetSecretVersionsDTO,
|
||||
SecretAccessListEntry,
|
||||
SecretType,
|
||||
SecretV3Raw,
|
||||
SecretV3RawResponse,
|
||||
@@ -18,6 +19,7 @@ import {
|
||||
TGetProjectSecretsAllEnvDTO,
|
||||
TGetProjectSecretsDTO,
|
||||
TGetProjectSecretsKey,
|
||||
TGetSecretAccessListDTO,
|
||||
TGetSecretReferenceTreeDTO,
|
||||
TSecretReferenceTraceNode
|
||||
} from "./types";
|
||||
@@ -27,6 +29,13 @@ export const secretKeys = {
|
||||
getProjectSecret: ({ workspaceId, environment, secretPath }: TGetProjectSecretsKey) =>
|
||||
[{ workspaceId, environment, secretPath }, "secrets"] as const,
|
||||
getSecretVersion: (secretId: string) => [{ secretId }, "secret-versions"] as const,
|
||||
getSecretAccessList: ({
|
||||
workspaceId,
|
||||
environment,
|
||||
secretPath,
|
||||
secretKey
|
||||
}: TGetSecretAccessListDTO) =>
|
||||
["secret-access-list", { workspaceId, environment, secretPath, secretKey }] as const,
|
||||
getSecretReferenceTree: (dto: TGetSecretReferenceTreeDTO) => ["secret-reference-tree", dto]
|
||||
};
|
||||
|
||||
@@ -232,6 +241,27 @@ export const useGetSecretVersion = (dto: GetSecretVersionsDTO) =>
|
||||
}, [])
|
||||
});
|
||||
|
||||
export const useGetSecretAccessList = (dto: TGetSecretAccessListDTO) =>
|
||||
useQuery({
|
||||
enabled: Boolean(dto.secretKey),
|
||||
queryKey: secretKeys.getSecretAccessList(dto),
|
||||
queryFn: async () => {
|
||||
const { data } = await apiRequest.get<{
|
||||
groups: SecretAccessListEntry[];
|
||||
identities: SecretAccessListEntry[];
|
||||
users: SecretAccessListEntry[];
|
||||
}>(`/api/v1/secrets/${dto.secretKey}/access-list`, {
|
||||
params: {
|
||||
workspaceId: dto.workspaceId,
|
||||
environment: dto.environment,
|
||||
secretPath: dto.secretPath
|
||||
}
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
});
|
||||
|
||||
const fetchSecretReferenceTree = async ({
|
||||
secretPath,
|
||||
projectId,
|
||||
|
@@ -1,3 +1,5 @@
|
||||
import { ProjectPermissionActions } from "@app/context";
|
||||
|
||||
import type { WsTag } from "../tags/types";
|
||||
|
||||
export enum SecretType {
|
||||
@@ -125,6 +127,13 @@ export type GetSecretVersionsDTO = {
|
||||
offset: number;
|
||||
};
|
||||
|
||||
export type TGetSecretAccessListDTO = {
|
||||
workspaceId: string;
|
||||
environment: string;
|
||||
secretPath: string;
|
||||
secretKey: string;
|
||||
};
|
||||
|
||||
export type TCreateSecretsV3DTO = {
|
||||
secretKey: string;
|
||||
secretValue: string;
|
||||
@@ -231,3 +240,10 @@ export type TSecretReferenceTraceNode = {
|
||||
secretPath: string;
|
||||
children: TSecretReferenceTraceNode[];
|
||||
};
|
||||
|
||||
export type SecretAccessListEntry = {
|
||||
allowedActions: ProjectPermissionActions[];
|
||||
id: string;
|
||||
membershipId: string;
|
||||
name: string;
|
||||
};
|
||||
|
@@ -23,6 +23,7 @@ export type SubscriptionPlan = {
|
||||
workspacesUsed: number;
|
||||
environmentLimit: number;
|
||||
samlSSO: boolean;
|
||||
secretAccessInsights: boolean;
|
||||
hsm: boolean;
|
||||
oidcSSO: boolean;
|
||||
scim: boolean;
|
||||
|
@@ -48,7 +48,8 @@ export const InitialStep = ({ setStep, email, setEmail, password, setPassword }:
|
||||
const redirectUrl = `/api/v1/sso/redirect/saml2/organizations/${orgSlug}${
|
||||
callbackPort ? `?callback_port=${callbackPort}` : ""
|
||||
}`;
|
||||
navigate({ to: redirectUrl });
|
||||
|
||||
window.location.assign(redirectUrl);
|
||||
};
|
||||
|
||||
const redirectToOidc = (orgSlug: string) => {
|
||||
@@ -56,7 +57,8 @@ export const InitialStep = ({ setStep, email, setEmail, password, setPassword }:
|
||||
const redirectUrl = `/api/v1/sso/oidc/login?orgSlug=${orgSlug}${
|
||||
callbackPort ? `&callbackPort=${callbackPort}` : ""
|
||||
}`;
|
||||
navigate({ to: redirectUrl });
|
||||
|
||||
window.location.assign(redirectUrl);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
@@ -27,7 +27,7 @@ export const OrgGroupsSection = () => {
|
||||
if (!subscription?.groups) {
|
||||
handlePopUpOpen("upgradePlan", {
|
||||
description:
|
||||
"You can manage users more efficiently with groups if you upgrade your Infisical plan."
|
||||
"You can manage users more efficiently with groups if you upgrade your Infisical plan to an Enterprise license."
|
||||
});
|
||||
} else {
|
||||
handlePopUpOpen("group");
|
||||
|
@@ -5,9 +5,9 @@ import { z } from "zod";
|
||||
import { GitHubOAuthCallbackPage } from "./GithubOauthCallbackPage";
|
||||
|
||||
const GitHubOAuthCallbackPageQueryParamsSchema = z.object({
|
||||
code: z.string().catch(""),
|
||||
code: z.coerce.string().catch(""),
|
||||
state: z.string().catch(""),
|
||||
installation_id: z.coerce.string().catch("")
|
||||
installation_id: z.coerce.string().optional().catch("")
|
||||
});
|
||||
|
||||
export const Route = createFileRoute(
|
||||
|
@@ -25,7 +25,8 @@ enum AuthProvider {
|
||||
AZURE_SAML = "azure-saml",
|
||||
JUMPCLOUD_SAML = "jumpcloud-saml",
|
||||
KEYCLOAK_SAML = "keycloak-saml",
|
||||
GOOGLE_SAML = "google-saml"
|
||||
GOOGLE_SAML = "google-saml",
|
||||
AUTH0_SAML = "auth0-saml"
|
||||
}
|
||||
|
||||
const ssoAuthProviders = [
|
||||
@@ -33,7 +34,8 @@ const ssoAuthProviders = [
|
||||
{ label: "Azure / Entra SAML", value: AuthProvider.AZURE_SAML },
|
||||
{ label: "JumpCloud SAML", value: AuthProvider.JUMPCLOUD_SAML },
|
||||
{ label: "Keycloak SAML", value: AuthProvider.KEYCLOAK_SAML },
|
||||
{ label: "Google SAML", value: AuthProvider.GOOGLE_SAML }
|
||||
{ label: "Google SAML", value: AuthProvider.GOOGLE_SAML },
|
||||
{ label: "Auth0 SAML", value: AuthProvider.AUTH0_SAML }
|
||||
];
|
||||
|
||||
const schema = z
|
||||
@@ -191,6 +193,15 @@ export const SSOModal = ({ popUp, handlePopUpClose, handlePopUpToggle, hideDelet
|
||||
issuer: "Issuer",
|
||||
issuerPlaceholder: window.origin
|
||||
};
|
||||
case AuthProvider.AUTH0_SAML:
|
||||
return {
|
||||
acsUrl: "Application Callback URL",
|
||||
entityId: "Audience",
|
||||
entryPoint: "Identity Provider Login URL",
|
||||
entryPointPlaceholder: "https://xxx.auth0.com/samlp/xxx",
|
||||
issuer: "Issuer",
|
||||
issuerPlaceholder: "urn:xxx-xxx.us.auth0.com"
|
||||
};
|
||||
default:
|
||||
return {
|
||||
acsUrl: "ACS URL",
|
||||
|
@@ -33,7 +33,7 @@ export const GroupsSection = () => {
|
||||
if (!subscription?.groups) {
|
||||
handlePopUpOpen("upgradePlan", {
|
||||
description:
|
||||
"You can manage users more efficiently with groups if you upgrade your Infisical plan."
|
||||
"You can manage users more efficiently with groups if you upgrade your Infisical plan to an Enterprise license."
|
||||
});
|
||||
} else {
|
||||
handlePopUpOpen("group");
|
||||
|
@@ -25,7 +25,7 @@ import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@a
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { useDeleteProjectRole, useGetProjectRoles } from "@app/hooks/api";
|
||||
import { TProjectRole } from "@app/hooks/api/roles/types";
|
||||
import { RoleModal } from "@app/pages/organization/RoleByIDPage/components";
|
||||
import { RoleModal } from "@app/pages/project/RoleDetailsBySlugPage/components/RoleModal";
|
||||
|
||||
export const ProjectRoleList = () => {
|
||||
const navigate = useNavigate();
|
||||
|
@@ -201,7 +201,7 @@ export const IdentityProjectAdditionalPrivilegeModifySection = ({
|
||||
`permissions.${selectedSubject}`,
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore-error akhilmhdh: this is because of ts collision with both
|
||||
[...rootPolicyValue, ...[]],
|
||||
[...rootPolicyValue, {}],
|
||||
{ shouldDirty: true, shouldTouch: true }
|
||||
);
|
||||
} else {
|
||||
|
@@ -173,7 +173,7 @@ export const MembershipProjectAdditionalPrivilegeModifySection = ({
|
||||
`permissions.${selectedSubject}`,
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore-error akhilmhdh: this is because of ts collision with both
|
||||
[...rootPolicyValue, ...[]],
|
||||
[...rootPolicyValue, {}],
|
||||
{ shouldDirty: true, shouldTouch: true }
|
||||
);
|
||||
} else {
|
||||
|
@@ -98,7 +98,7 @@ export const RolePermissionsSection = ({ roleSlug, isDisabled }: Props) => {
|
||||
`permissions.${selectedSubject}`,
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore-error akhilmhdh: this is because of ts collision with both
|
||||
[...rootPolicyValue, ...[]],
|
||||
[...rootPolicyValue, {}],
|
||||
{ shouldDirty: true, shouldTouch: true }
|
||||
);
|
||||
} else {
|
||||
|
@@ -80,8 +80,16 @@ export const redirectForProviderAuth = (
|
||||
createIntegrationMissingEnvVarsNotification(integrationOption.slug);
|
||||
return;
|
||||
}
|
||||
const link = `https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=${integrationOption.clientId}&response_type=code&redirect_uri=${window.location.origin}/integrations/azure-key-vault/oauth2/callback&response_mode=query&scope=https://vault.azure.net/.default openid offline_access&state=${state}`;
|
||||
window.location.assign(link);
|
||||
navigate({
|
||||
to: "/secret-manager/$projectId/integrations/azure-key-vault/authorize",
|
||||
params: {
|
||||
projectId
|
||||
},
|
||||
search: {
|
||||
clientId: integrationOption.clientId,
|
||||
state,
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "azure-app-configuration": {
|
||||
|
@@ -340,7 +340,14 @@ export const OverviewPage = () => {
|
||||
const pathSegment = secretPath.split("/").filter(Boolean);
|
||||
const parentPath = `/${pathSegment.slice(0, -1).join("/")}`;
|
||||
const folderName = pathSegment.at(-1);
|
||||
if (folderName && parentPath) {
|
||||
const canCreateFolder = permission.can(
|
||||
ProjectPermissionActions.Create,
|
||||
subject(ProjectPermissionSub.SecretFolders, {
|
||||
environment: env,
|
||||
secretPath: parentPath
|
||||
})
|
||||
);
|
||||
if (folderName && parentPath && canCreateFolder) {
|
||||
await createFolder({
|
||||
projectId: workspaceId,
|
||||
path: parentPath,
|
||||
|
@@ -13,8 +13,10 @@ import {
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { Link } from "@tanstack/react-router";
|
||||
import { format } from "date-fns";
|
||||
|
||||
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||
import {
|
||||
@@ -36,10 +38,17 @@ import {
|
||||
Tooltip
|
||||
} from "@app/components/v2";
|
||||
import { InfisicalSecretInput } from "@app/components/v2/InfisicalSecretInput";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub, useProjectPermission } from "@app/context";
|
||||
import { useToggle } from "@app/hooks";
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionSub,
|
||||
useProjectPermission,
|
||||
useWorkspace
|
||||
} from "@app/context";
|
||||
import { usePopUp, useToggle } from "@app/hooks";
|
||||
import { useGetSecretVersion } from "@app/hooks/api";
|
||||
import { useGetSecretAccessList } from "@app/hooks/api/secrets/queries";
|
||||
import { SecretV3RawSanitized, WsTag } from "@app/hooks/api/types";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
|
||||
import { CreateReminderForm } from "./CreateReminderForm";
|
||||
import { formSchema, SecretActionType, TFormSchema } from "./SecretListView.utils";
|
||||
@@ -87,7 +96,12 @@ export const SecretDetailSidebar = ({
|
||||
values: secret
|
||||
});
|
||||
|
||||
const { handlePopUpToggle, popUp, handlePopUpOpen } = usePopUp([
|
||||
"secretAccessUpgradePlan"
|
||||
] as const);
|
||||
|
||||
const { permission } = useProjectPermission();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
|
||||
const tagFields = useFieldArray({
|
||||
control,
|
||||
@@ -137,6 +151,13 @@ export const SecretDetailSidebar = ({
|
||||
secretId: secret?.id
|
||||
});
|
||||
|
||||
const { data: secretAccessList, isPending } = useGetSecretAccessList({
|
||||
workspaceId: currentWorkspace.id,
|
||||
environment,
|
||||
secretPath,
|
||||
secretKey
|
||||
});
|
||||
|
||||
const handleOverrideClick = () => {
|
||||
if (isOverridden) {
|
||||
// override need not be flagged delete if it was never saved in server
|
||||
@@ -191,6 +212,13 @@ export const SecretDetailSidebar = ({
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<UpgradePlanModal
|
||||
isOpen={popUp.secretAccessUpgradePlan.isOpen}
|
||||
onOpenChange={(isUpgradeModalOpen) =>
|
||||
handlePopUpToggle("secretAccessUpgradePlan", isUpgradeModalOpen)
|
||||
}
|
||||
text="Secret access analysis is only available on Infisical's Pro plan and above."
|
||||
/>
|
||||
<Drawer
|
||||
onOpenChange={(state) => {
|
||||
if (isOpen && isDirty) {
|
||||
@@ -553,8 +581,117 @@ export const SecretDetailSidebar = ({
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="dark mb-4 flex-grow text-sm text-bunker-300">
|
||||
<div className="mb-2">
|
||||
Access List
|
||||
<Tooltip
|
||||
content="Lists all users, machine identities, and groups that have been granted any permission level (read, create, edit, or delete) for this secret."
|
||||
className="z-[100]"
|
||||
>
|
||||
<FontAwesomeIcon icon={faCircleQuestion} className="ml-1" size="sm" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
{isPending && (
|
||||
<Button className="w-full px-2 py-1" variant="outline_bg" isDisabled>
|
||||
Analyze Access
|
||||
</Button>
|
||||
)}
|
||||
{!isPending && secretAccessList === undefined && (
|
||||
<Button
|
||||
className="w-full px-2 py-1"
|
||||
variant="outline_bg"
|
||||
onClick={() => {
|
||||
handlePopUpOpen("secretAccessUpgradePlan");
|
||||
}}
|
||||
>
|
||||
Analyze Access
|
||||
</Button>
|
||||
)}
|
||||
{!isPending && secretAccessList && (
|
||||
<div className="flex max-h-72 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]">
|
||||
{secretAccessList.users.length > 0 && (
|
||||
<div className="pb-3">
|
||||
<div className="mb-2 font-bold">Users</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{secretAccessList.users.map((user) => (
|
||||
<div className="rounded-md bg-bunker-500 px-1">
|
||||
<Tooltip content={user.allowedActions.join(", ")} className="z-[100]">
|
||||
<Link
|
||||
to={
|
||||
`/${ProjectType.SecretManager}/$projectId/members/$membershipId` as const
|
||||
}
|
||||
params={{
|
||||
projectId: currentWorkspace.id,
|
||||
membershipId: user.membershipId
|
||||
}}
|
||||
className="text-secondary/80 text-sm hover:text-primary"
|
||||
>
|
||||
{user.name}
|
||||
</Link>
|
||||
</Tooltip>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{secretAccessList.identities.length > 0 && (
|
||||
<div className="pb-3">
|
||||
<div className="mb-2 font-bold">Identities</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{secretAccessList.identities.map((identity) => (
|
||||
<div className="rounded-md bg-bunker-500 px-1">
|
||||
<Tooltip
|
||||
content={identity.allowedActions.join(", ")}
|
||||
className="z-[100]"
|
||||
>
|
||||
<Link
|
||||
to={
|
||||
`/${ProjectType.SecretManager}/$projectId/identities/$identityId` as const
|
||||
}
|
||||
params={{
|
||||
projectId: currentWorkspace.id,
|
||||
identityId: identity.id
|
||||
}}
|
||||
className="text-secondary/80 text-sm hover:text-primary"
|
||||
>
|
||||
{identity.name}
|
||||
</Link>
|
||||
</Tooltip>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{secretAccessList.groups.length > 0 && (
|
||||
<div className="pb-3">
|
||||
<div className="mb-2 font-bold">Groups</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{secretAccessList.groups.map((group) => (
|
||||
<div className="rounded-md bg-bunker-500 px-1">
|
||||
<Tooltip
|
||||
content={group.allowedActions.join(", ")}
|
||||
className="z-[100]"
|
||||
>
|
||||
<Link
|
||||
to={"/organization/groups/$groupId" as const}
|
||||
params={{
|
||||
groupId: group.id
|
||||
}}
|
||||
className="text-secondary/80 text-sm hover:text-primary"
|
||||
>
|
||||
{group.name}
|
||||
</Link>
|
||||
</Tooltip>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col space-y-4">
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="mb-2 flex items-center space-x-4">
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Edit}
|
||||
a={subject(ProjectPermissionSub.Secrets, {
|
||||
|
@@ -303,7 +303,7 @@ export const AwsSecretManagerConfigurePage = () => {
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
>
|
||||
<SecretPathInput {...field} />
|
||||
<SecretPathInput {...field} environment={selectedSourceEnvironment} />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
@@ -76,6 +76,7 @@ export const AzureAppConfigurationConfigurePage = () => {
|
||||
}
|
||||
});
|
||||
|
||||
const selectedEnvironment = watch("sourceEnvironment");
|
||||
const { mutateAsync } = useCreateIntegration();
|
||||
|
||||
const integrationAuthId = useSearch({
|
||||
@@ -248,7 +249,7 @@ export const AzureAppConfigurationConfigurePage = () => {
|
||||
name="secretPath"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl label="Secrets Path" errorText={error?.message} isError={Boolean(error)}>
|
||||
<SecretPathInput {...field} />
|
||||
<SecretPathInput {...field} environment={selectedEnvironment} />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
@@ -6,7 +6,7 @@ import { AzureAppConfigurationOauthCallbackPage } from "./AzureAppConfigurationO
|
||||
|
||||
export const AzureAppConfigurationOauthCallbackPageQueryParamsSchema = z.object({
|
||||
state: z.string().catch(""),
|
||||
code: z.string()
|
||||
code: z.coerce.string().catch("")
|
||||
});
|
||||
|
||||
export const Route = createFileRoute(
|
||||
|
@@ -0,0 +1,97 @@
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
|
||||
import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import z from "zod";
|
||||
|
||||
import { Button, Card, CardTitle, FormControl, Input } from "@app/components/v2";
|
||||
import { Helmet } from "react-helmet";
|
||||
import { useSearch } from "@tanstack/react-router";
|
||||
import { ROUTE_PATHS } from "@app/const/routes";
|
||||
|
||||
const schema = z.object({
|
||||
tenantId: z.string().trim().optional()
|
||||
});
|
||||
|
||||
type FormData = z.infer<typeof schema>;
|
||||
|
||||
export function AzureKeyVaultAuthorizePage() {
|
||||
const { state, clientId } = useSearch({
|
||||
from: ROUTE_PATHS.SecretManager.Integratons.AzureKeyVaultAuthorizePage.id,
|
||||
});
|
||||
|
||||
const { control, handleSubmit } = useForm<FormData>({
|
||||
resolver: zodResolver(schema)
|
||||
});
|
||||
|
||||
const onFormSubmit = async ({ tenantId }: FormData) => {
|
||||
const link = `https://login.microsoftonline.com/${
|
||||
tenantId ?? "common"
|
||||
}/oauth2/v2.0/authorize?client_id=${clientId}&response_type=code&redirect_uri=${
|
||||
window.location.origin
|
||||
}/integrations/azure-key-vault/oauth2/callback&response_mode=query&scope=https://vault.azure.net/.default openid offline_access&state=${state}`;
|
||||
|
||||
window.location.assign(link);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Helmet>
|
||||
<title>Authorize Azure Key Vault Integration</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Helmet>
|
||||
<Card className="mb-12 max-w-lg rounded-md border border-mineshaft-600">
|
||||
<CardTitle
|
||||
className="px-6 text-left text-xl"
|
||||
subTitle="Authenticate with a specific tenant ID or let OAuth handle it automatically."
|
||||
>
|
||||
<div className="flex flex-row items-center">
|
||||
<div className="flex items-center pb-0.5">
|
||||
<img src="/images/integrations/GitHub.png" height={30} width={30} alt="Github logo" />
|
||||
</div>
|
||||
<span className="ml-2.5">Azure Key Vault Integration </span>
|
||||
<a
|
||||
href="https://infisical.com/docs/integrations/cloud/azure-key-vault"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<div className="ml-2 mb-1 inline-block cursor-default rounded-md bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] text-sm text-yellow opacity-80 hover:opacity-100">
|
||||
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5" />
|
||||
Docs
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowUpRightFromSquare}
|
||||
className="ml-1.5 mb-[0.07rem] text-xxs"
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</CardTitle>
|
||||
<form onSubmit={handleSubmit(onFormSubmit)} className="px-6 pb-8 text-right">
|
||||
<Controller
|
||||
control={control}
|
||||
name="tenantId"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Tenant ID (optional)"
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
>
|
||||
<Input {...field} placeholder="2e39537c-9a01-4bd6-a7b8-c3b88cbb8db9" />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Button
|
||||
colorSchema="primary"
|
||||
variant="outline_bg"
|
||||
className="mt-2 w-min"
|
||||
size="sm"
|
||||
type="submit"
|
||||
>
|
||||
Connect to Azure Key Vault
|
||||
</Button>
|
||||
</form>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
@@ -0,0 +1,17 @@
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import z from 'zod'
|
||||
|
||||
import { AzureKeyVaultAuthorizePage } from "./AzureKeyVaultAuthorizePage";
|
||||
|
||||
|
||||
const PageQueryParamsSchema = z.object({
|
||||
state: z.string(),
|
||||
clientId: z.string().optional(),
|
||||
});
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/azure-key-vault/authorize"
|
||||
)({
|
||||
component: AzureKeyVaultAuthorizePage,
|
||||
validateSearch: PageQueryParamsSchema
|
||||
});
|
@@ -6,7 +6,7 @@ import { AzureKeyVaultOauthCallbackPage } from "./AzureKeyVaultOauthCallback";
|
||||
|
||||
export const AzureKeyVaultOauthCallbackQueryParamsSchema = z.object({
|
||||
state: z.string().catch(""),
|
||||
code: z.string()
|
||||
code: z.coerce.string().catch("")
|
||||
});
|
||||
|
||||
export const Route = createFileRoute(
|
||||
|
@@ -6,7 +6,7 @@ import { BitbucketOauthCallbackPage } from "./BitbucketOauthCallbackPage";
|
||||
|
||||
export const BitbucketOauthCallbackQueryParamsSchema = z.object({
|
||||
state: z.string().catch(""),
|
||||
code: z.string()
|
||||
code: z.coerce.string().catch("")
|
||||
});
|
||||
|
||||
export const Route = createFileRoute(
|
||||
|
@@ -62,6 +62,7 @@ export const CircleCIConfigurePage = () => {
|
||||
|
||||
const selectedScope = watch("scope");
|
||||
const selectedOrg = watch("targetOrg");
|
||||
const selectedEnvironment = watch("sourceEnvironment");
|
||||
|
||||
const { data: circleCIOrganizations, isPending: isCircleCIOrganizationsLoading } =
|
||||
useGetIntegrationAuthCircleCIOrganizations(integrationAuthId);
|
||||
@@ -186,7 +187,7 @@ export const CircleCIConfigurePage = () => {
|
||||
name="secretPath"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl label="Secrets Path" errorText={error?.message} isError={Boolean(error)}>
|
||||
<SecretPathInput {...field} />
|
||||
<SecretPathInput {...field} environment={selectedEnvironment.slug}/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
@@ -6,7 +6,7 @@ import { GcpSecretManagerOauthCallbackPage } from "./GcpSecretManagerOauthCallba
|
||||
|
||||
export const GcpSecretManagerOAuthCallbackPageQueryParamsSchema = z.object({
|
||||
state: z.string().catch(""),
|
||||
code: z.string()
|
||||
code: z.coerce.string().catch("")
|
||||
});
|
||||
|
||||
export const Route = createFileRoute(
|
||||
|
@@ -6,8 +6,8 @@ import { GithubOauthCallbackPage } from "./GithubOauthCallbackPage";
|
||||
|
||||
export const GithubOAuthCallbackPageQueryParamsSchema = z.object({
|
||||
state: z.string().catch(""),
|
||||
installation_id: z.coerce.string().catch(""),
|
||||
code: z.string().catch("")
|
||||
installation_id: z.coerce.string().optional().catch(""),
|
||||
code: z.coerce.string().catch("")
|
||||
});
|
||||
|
||||
export const Route = createFileRoute(
|
||||
|
@@ -72,7 +72,7 @@ export const GitlabAuthorizePage = () => {
|
||||
>
|
||||
<div className="flex flex-row items-center">
|
||||
<div className="flex items-center pb-0.5">
|
||||
<img src="/images/integrations/Gitlab.png" height={28} width={28} alt="Gitlab logo" />
|
||||
<img src="/images/integrations/GitLab.png" height={28} width={28} alt="Gitlab logo" />
|
||||
</div>
|
||||
<span className="ml-2.5">GitLab Integration </span>
|
||||
<a
|
||||
|
@@ -34,6 +34,7 @@ import {
|
||||
useGetIntegrationAuthById,
|
||||
useGetIntegrationAuthTeams
|
||||
} from "@app/hooks/api/integrationAuth";
|
||||
import { IntegrationSyncBehavior } from "@app/hooks/api/integrations/types";
|
||||
|
||||
const gitLabEntities = [
|
||||
{ name: "Individual", value: "individual" },
|
||||
@@ -45,6 +46,14 @@ enum TabSections {
|
||||
Options = "options"
|
||||
}
|
||||
|
||||
const initialSyncBehaviors = [
|
||||
{
|
||||
label: "No Import - Overwrite all values in GitLab",
|
||||
value: IntegrationSyncBehavior.OVERWRITE_TARGET
|
||||
},
|
||||
{ label: "Import - Prefer values from Infisical", value: IntegrationSyncBehavior.PREFER_SOURCE }
|
||||
];
|
||||
|
||||
const schema = z.object({
|
||||
targetEntity: z.enum([gitLabEntities[0].value, gitLabEntities[1].value]),
|
||||
targetTeamId: z.string().optional(),
|
||||
@@ -55,7 +64,8 @@ const schema = z.object({
|
||||
secretPrefix: z.string().optional(),
|
||||
secretSuffix: z.string().optional(),
|
||||
shouldMaskSecrets: z.boolean().optional(),
|
||||
shouldProtectSecrets: z.boolean()
|
||||
shouldProtectSecrets: z.boolean().default(false),
|
||||
initialSyncBehavior: z.nativeEnum(IntegrationSyncBehavior)
|
||||
});
|
||||
|
||||
type FormData = z.infer<typeof schema>;
|
||||
@@ -74,7 +84,8 @@ export const GitlabConfigurePage = () => {
|
||||
secretPath: "/",
|
||||
secretPrefix: "",
|
||||
secretSuffix: "",
|
||||
selectedSourceEnvironment: currentWorkspace.environments[0].slug
|
||||
selectedSourceEnvironment: currentWorkspace.environments[0].slug,
|
||||
initialSyncBehavior: IntegrationSyncBehavior.PREFER_SOURCE
|
||||
}
|
||||
});
|
||||
const selectedSourceEnvironment = watch("selectedSourceEnvironment");
|
||||
@@ -135,7 +146,8 @@ export const GitlabConfigurePage = () => {
|
||||
secretPrefix,
|
||||
secretSuffix,
|
||||
shouldMaskSecrets,
|
||||
shouldProtectSecrets
|
||||
shouldProtectSecrets,
|
||||
initialSyncBehavior
|
||||
}: FormData) => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
@@ -155,7 +167,8 @@ export const GitlabConfigurePage = () => {
|
||||
secretPrefix,
|
||||
secretSuffix,
|
||||
shouldMaskSecrets,
|
||||
shouldProtectSecrets
|
||||
shouldProtectSecrets,
|
||||
initialSyncBehavior
|
||||
}
|
||||
});
|
||||
|
||||
@@ -178,7 +191,11 @@ export const GitlabConfigurePage = () => {
|
||||
integrationAuthTeams ? (
|
||||
<form
|
||||
onSubmit={handleSubmit((data: FormData) => {
|
||||
if (!data.secretPrefix && !data.secretSuffix) {
|
||||
if (
|
||||
!data.secretPrefix &&
|
||||
!data.secretSuffix &&
|
||||
data.initialSyncBehavior === IntegrationSyncBehavior.OVERWRITE_TARGET
|
||||
) {
|
||||
handlePopUpOpen("confirmIntegration", data);
|
||||
return;
|
||||
}
|
||||
@@ -197,7 +214,7 @@ export const GitlabConfigurePage = () => {
|
||||
>
|
||||
<div className="flex flex-row items-center">
|
||||
<div className="flex items-center pb-0.5">
|
||||
<img src="/images/integrations/Gitlab.png" height={28} width={28} alt="Gitlab logo" />
|
||||
<img src="/images/integrations/GitLab.png" height={28} width={28} alt="Gitlab logo" />
|
||||
</div>
|
||||
<span className="ml-2.5">GitLab Integration </span>
|
||||
<a
|
||||
@@ -378,6 +395,36 @@ export const GitlabConfigurePage = () => {
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="initialSyncBehavior"
|
||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Initial Sync Behavior"
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
>
|
||||
<Select
|
||||
defaultValue={field.value}
|
||||
onValueChange={(e) => onChange(e)}
|
||||
className="w-full border border-mineshaft-500"
|
||||
dropdownContainerClassName="max-w-full"
|
||||
>
|
||||
{initialSyncBehaviors.map((b) => {
|
||||
return (
|
||||
<SelectItem
|
||||
value={b.value}
|
||||
key={`sync-behavior-${b.value}`}
|
||||
className="w-full"
|
||||
>
|
||||
{b.label}
|
||||
</SelectItem>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</motion.div>
|
||||
</TabPanel>
|
||||
<TabPanel value={TabSections.Options}>
|
||||
@@ -462,11 +509,6 @@ export const GitlabConfigurePage = () => {
|
||||
Create Integration
|
||||
</Button>
|
||||
</Card>
|
||||
{/* <div className="border-t border-mineshaft-800 w-full max-w-md mt-6"/>
|
||||
<div className="flex flex-col bg-mineshaft-800 border border-mineshaft-600 w-full p-4 max-w-lg mt-6 rounded-md">
|
||||
<div className="flex flex-row items-center"><FontAwesomeIcon icon={faCircleInfo} className="text-mineshaft-200 text-xl"/> <span className="ml-3 text-md text-mineshaft-100">Pro Tip</span></div>
|
||||
<span className="text-mineshaft-300 text-sm mt-4">After creating an integration, your secrets will start syncing immediately. This might cause an unexpected override of current secrets in GitLab with secrets from Infisical.</span>
|
||||
</div> */}
|
||||
<Modal
|
||||
isOpen={popUp.confirmIntegration?.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("confirmIntegration", isOpen)}
|
||||
|
@@ -6,7 +6,7 @@ import { GitLabOAuthCallbackPage } from "./GitlabOauthCallbackPage";
|
||||
|
||||
export const GitlabOAuthCallbackPageQueryParamsSchema = z.object({
|
||||
state: z.string().catch(""),
|
||||
code: z.string()
|
||||
code: z.coerce.string().catch("")
|
||||
});
|
||||
|
||||
export const Route = createFileRoute(
|
||||
|
@@ -6,7 +6,7 @@ import { HerokuOAuthCallbackPage } from "./HerokuOauthCallbackPage";
|
||||
|
||||
export const HerokuOAuthCallbackPageQueryParamsSchema = z.object({
|
||||
state: z.string().catch(""),
|
||||
code: z.string()
|
||||
code: z.coerce.string().catch("")
|
||||
});
|
||||
|
||||
export const Route = createFileRoute(
|
||||
|
@@ -6,7 +6,7 @@ import { NetlifyOauthCallbackPage } from "./NetlifyOauthCallbackPage";
|
||||
|
||||
export const NetlifyOAuthCallbackPageQueryParamsSchema = z.object({
|
||||
state: z.string().catch(""),
|
||||
code: z.string()
|
||||
code: z.coerce.string().catch("")
|
||||
});
|
||||
|
||||
export const Route = createFileRoute(
|
||||
|
@@ -6,7 +6,7 @@ import { VercelOauthCallbackPage } from "./VercelOauthCallbackPage";
|
||||
|
||||
export const VercelOAuthCallbackPageQueryParamsSchema = z.object({
|
||||
state: z.string().catch(""),
|
||||
code: z.string()
|
||||
code: z.coerce.string().catch("")
|
||||
});
|
||||
|
||||
export const Route = createFileRoute(
|
||||
|
@@ -159,6 +159,7 @@ import { Route as secretManagerIntegrationsChecklyConfigurePageRouteImport } fro
|
||||
import { Route as secretManagerIntegrationsChecklyAuthorizePageRouteImport } from './pages/secret-manager/integrations/ChecklyAuthorizePage/route'
|
||||
import { Route as secretManagerIntegrationsBitbucketConfigurePageRouteImport } from './pages/secret-manager/integrations/BitbucketConfigurePage/route'
|
||||
import { Route as secretManagerIntegrationsAzureKeyVaultConfigurePageRouteImport } from './pages/secret-manager/integrations/AzureKeyVaultConfigurePage/route'
|
||||
import { Route as secretManagerIntegrationsAzureKeyVaultAuthorizePageRouteImport } from './pages/secret-manager/integrations/AzureKeyVaultAuthorizePage/route'
|
||||
import { Route as secretManagerIntegrationsAzureDevopsConfigurePageRouteImport } from './pages/secret-manager/integrations/AzureDevopsConfigurePage/route'
|
||||
import { Route as secretManagerIntegrationsAzureDevopsAuthorizePageRouteImport } from './pages/secret-manager/integrations/AzureDevopsAuthorizePage/route'
|
||||
import { Route as secretManagerIntegrationsAzureAppConfigurationConfigurePageRouteImport } from './pages/secret-manager/integrations/AzureAppConfigurationConfigurePage/route'
|
||||
@@ -1352,6 +1353,14 @@ const secretManagerIntegrationsAzureKeyVaultConfigurePageRouteRoute =
|
||||
AuthenticateInjectOrgDetailsSecretManagerProjectIdSecretManagerLayoutIntegrationsRoute,
|
||||
} as any)
|
||||
|
||||
const secretManagerIntegrationsAzureKeyVaultAuthorizePageRouteRoute =
|
||||
secretManagerIntegrationsAzureKeyVaultAuthorizePageRouteImport.update({
|
||||
id: '/azure-key-vault/authorize',
|
||||
path: '/azure-key-vault/authorize',
|
||||
getParentRoute: () =>
|
||||
AuthenticateInjectOrgDetailsSecretManagerProjectIdSecretManagerLayoutIntegrationsRoute,
|
||||
} as any)
|
||||
|
||||
const secretManagerIntegrationsAzureDevopsConfigurePageRouteRoute =
|
||||
secretManagerIntegrationsAzureDevopsConfigurePageRouteImport.update({
|
||||
id: '/azure-devops/create',
|
||||
@@ -2251,6 +2260,13 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof secretManagerIntegrationsAzureDevopsConfigurePageRouteImport
|
||||
parentRoute: typeof AuthenticateInjectOrgDetailsSecretManagerProjectIdSecretManagerLayoutIntegrationsImport
|
||||
}
|
||||
'/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/azure-key-vault/authorize': {
|
||||
id: '/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/azure-key-vault/authorize'
|
||||
path: '/azure-key-vault/authorize'
|
||||
fullPath: '/secret-manager/$projectId/integrations/azure-key-vault/authorize'
|
||||
preLoaderRoute: typeof secretManagerIntegrationsAzureKeyVaultAuthorizePageRouteImport
|
||||
parentRoute: typeof AuthenticateInjectOrgDetailsSecretManagerProjectIdSecretManagerLayoutIntegrationsImport
|
||||
}
|
||||
'/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/azure-key-vault/create': {
|
||||
id: '/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/azure-key-vault/create'
|
||||
path: '/azure-key-vault/create'
|
||||
@@ -2944,6 +2960,7 @@ interface AuthenticateInjectOrgDetailsSecretManagerProjectIdSecretManagerLayoutI
|
||||
secretManagerIntegrationsAzureAppConfigurationConfigurePageRouteRoute: typeof secretManagerIntegrationsAzureAppConfigurationConfigurePageRouteRoute
|
||||
secretManagerIntegrationsAzureDevopsAuthorizePageRouteRoute: typeof secretManagerIntegrationsAzureDevopsAuthorizePageRouteRoute
|
||||
secretManagerIntegrationsAzureDevopsConfigurePageRouteRoute: typeof secretManagerIntegrationsAzureDevopsConfigurePageRouteRoute
|
||||
secretManagerIntegrationsAzureKeyVaultAuthorizePageRouteRoute: typeof secretManagerIntegrationsAzureKeyVaultAuthorizePageRouteRoute
|
||||
secretManagerIntegrationsAzureKeyVaultConfigurePageRouteRoute: typeof secretManagerIntegrationsAzureKeyVaultConfigurePageRouteRoute
|
||||
secretManagerIntegrationsBitbucketConfigurePageRouteRoute: typeof secretManagerIntegrationsBitbucketConfigurePageRouteRoute
|
||||
secretManagerIntegrationsChecklyAuthorizePageRouteRoute: typeof secretManagerIntegrationsChecklyAuthorizePageRouteRoute
|
||||
@@ -3034,6 +3051,8 @@ const AuthenticateInjectOrgDetailsSecretManagerProjectIdSecretManagerLayoutInteg
|
||||
secretManagerIntegrationsAzureDevopsAuthorizePageRouteRoute,
|
||||
secretManagerIntegrationsAzureDevopsConfigurePageRouteRoute:
|
||||
secretManagerIntegrationsAzureDevopsConfigurePageRouteRoute,
|
||||
secretManagerIntegrationsAzureKeyVaultAuthorizePageRouteRoute:
|
||||
secretManagerIntegrationsAzureKeyVaultAuthorizePageRouteRoute,
|
||||
secretManagerIntegrationsAzureKeyVaultConfigurePageRouteRoute:
|
||||
secretManagerIntegrationsAzureKeyVaultConfigurePageRouteRoute,
|
||||
secretManagerIntegrationsBitbucketConfigurePageRouteRoute:
|
||||
@@ -3513,6 +3532,7 @@ export interface FileRoutesByFullPath {
|
||||
'/secret-manager/$projectId/integrations/azure-app-configuration/create': typeof secretManagerIntegrationsAzureAppConfigurationConfigurePageRouteRoute
|
||||
'/secret-manager/$projectId/integrations/azure-devops/authorize': typeof secretManagerIntegrationsAzureDevopsAuthorizePageRouteRoute
|
||||
'/secret-manager/$projectId/integrations/azure-devops/create': typeof secretManagerIntegrationsAzureDevopsConfigurePageRouteRoute
|
||||
'/secret-manager/$projectId/integrations/azure-key-vault/authorize': typeof secretManagerIntegrationsAzureKeyVaultAuthorizePageRouteRoute
|
||||
'/secret-manager/$projectId/integrations/azure-key-vault/create': typeof secretManagerIntegrationsAzureKeyVaultConfigurePageRouteRoute
|
||||
'/secret-manager/$projectId/integrations/bitbucket/create': typeof secretManagerIntegrationsBitbucketConfigurePageRouteRoute
|
||||
'/secret-manager/$projectId/integrations/checkly/authorize': typeof secretManagerIntegrationsChecklyAuthorizePageRouteRoute
|
||||
@@ -3676,6 +3696,7 @@ export interface FileRoutesByTo {
|
||||
'/secret-manager/$projectId/integrations/azure-app-configuration/create': typeof secretManagerIntegrationsAzureAppConfigurationConfigurePageRouteRoute
|
||||
'/secret-manager/$projectId/integrations/azure-devops/authorize': typeof secretManagerIntegrationsAzureDevopsAuthorizePageRouteRoute
|
||||
'/secret-manager/$projectId/integrations/azure-devops/create': typeof secretManagerIntegrationsAzureDevopsConfigurePageRouteRoute
|
||||
'/secret-manager/$projectId/integrations/azure-key-vault/authorize': typeof secretManagerIntegrationsAzureKeyVaultAuthorizePageRouteRoute
|
||||
'/secret-manager/$projectId/integrations/azure-key-vault/create': typeof secretManagerIntegrationsAzureKeyVaultConfigurePageRouteRoute
|
||||
'/secret-manager/$projectId/integrations/bitbucket/create': typeof secretManagerIntegrationsBitbucketConfigurePageRouteRoute
|
||||
'/secret-manager/$projectId/integrations/checkly/authorize': typeof secretManagerIntegrationsChecklyAuthorizePageRouteRoute
|
||||
@@ -3854,6 +3875,7 @@ export interface FileRoutesById {
|
||||
'/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/azure-app-configuration/create': typeof secretManagerIntegrationsAzureAppConfigurationConfigurePageRouteRoute
|
||||
'/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/azure-devops/authorize': typeof secretManagerIntegrationsAzureDevopsAuthorizePageRouteRoute
|
||||
'/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/azure-devops/create': typeof secretManagerIntegrationsAzureDevopsConfigurePageRouteRoute
|
||||
'/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/azure-key-vault/authorize': typeof secretManagerIntegrationsAzureKeyVaultAuthorizePageRouteRoute
|
||||
'/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/azure-key-vault/create': typeof secretManagerIntegrationsAzureKeyVaultConfigurePageRouteRoute
|
||||
'/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/bitbucket/create': typeof secretManagerIntegrationsBitbucketConfigurePageRouteRoute
|
||||
'/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/checkly/authorize': typeof secretManagerIntegrationsChecklyAuthorizePageRouteRoute
|
||||
@@ -4024,6 +4046,7 @@ export interface FileRouteTypes {
|
||||
| '/secret-manager/$projectId/integrations/azure-app-configuration/create'
|
||||
| '/secret-manager/$projectId/integrations/azure-devops/authorize'
|
||||
| '/secret-manager/$projectId/integrations/azure-devops/create'
|
||||
| '/secret-manager/$projectId/integrations/azure-key-vault/authorize'
|
||||
| '/secret-manager/$projectId/integrations/azure-key-vault/create'
|
||||
| '/secret-manager/$projectId/integrations/bitbucket/create'
|
||||
| '/secret-manager/$projectId/integrations/checkly/authorize'
|
||||
@@ -4186,6 +4209,7 @@ export interface FileRouteTypes {
|
||||
| '/secret-manager/$projectId/integrations/azure-app-configuration/create'
|
||||
| '/secret-manager/$projectId/integrations/azure-devops/authorize'
|
||||
| '/secret-manager/$projectId/integrations/azure-devops/create'
|
||||
| '/secret-manager/$projectId/integrations/azure-key-vault/authorize'
|
||||
| '/secret-manager/$projectId/integrations/azure-key-vault/create'
|
||||
| '/secret-manager/$projectId/integrations/bitbucket/create'
|
||||
| '/secret-manager/$projectId/integrations/checkly/authorize'
|
||||
@@ -4362,6 +4386,7 @@ export interface FileRouteTypes {
|
||||
| '/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/azure-app-configuration/create'
|
||||
| '/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/azure-devops/authorize'
|
||||
| '/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/azure-devops/create'
|
||||
| '/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/azure-key-vault/authorize'
|
||||
| '/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/azure-key-vault/create'
|
||||
| '/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/bitbucket/create'
|
||||
| '/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/checkly/authorize'
|
||||
@@ -4925,6 +4950,7 @@ export const routeTree = rootRoute
|
||||
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/azure-app-configuration/create",
|
||||
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/azure-devops/authorize",
|
||||
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/azure-devops/create",
|
||||
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/azure-key-vault/authorize",
|
||||
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/azure-key-vault/create",
|
||||
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/bitbucket/create",
|
||||
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/checkly/authorize",
|
||||
@@ -5105,6 +5131,10 @@ export const routeTree = rootRoute
|
||||
"filePath": "secret-manager/integrations/AzureDevopsConfigurePage/route.tsx",
|
||||
"parent": "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations"
|
||||
},
|
||||
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/azure-key-vault/authorize": {
|
||||
"filePath": "secret-manager/integrations/AzureKeyVaultAuthorizePage/route.tsx",
|
||||
"parent": "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations"
|
||||
},
|
||||
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/azure-key-vault/create": {
|
||||
"filePath": "secret-manager/integrations/AzureKeyVaultConfigurePage/route.tsx",
|
||||
"parent": "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations"
|
||||
|
@@ -75,6 +75,10 @@ const secretManagerRoutes = route("/secret-manager/$projectId", [
|
||||
"/azure-devops/create",
|
||||
"secret-manager/integrations/AzureDevopsConfigurePage/route.tsx"
|
||||
),
|
||||
route(
|
||||
"/azure-key-vault/authorize",
|
||||
"secret-manager/integrations/AzureKeyVaultAuthorizePage/route.tsx"
|
||||
),
|
||||
route(
|
||||
"/azure-key-vault/oauth2/callback",
|
||||
"secret-manager/integrations/AzureKeyVaultOauthCallbackPage/route.tsx"
|
||||
|
@@ -13,9 +13,9 @@ type: application
|
||||
# This is the chart version. This version number should be incremented each time you make changes
|
||||
# to the chart and its templates, including the app version.
|
||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||
version: v0.8.0
|
||||
version: v0.8.1
|
||||
# This is the version number of the application being deployed. This version number should be
|
||||
# incremented each time you make changes to the application. Versions are not expected to
|
||||
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||
# It is recommended to use it with quotes.
|
||||
appVersion: "v0.8.0"
|
||||
appVersion: "v0.8.1"
|
||||
|
@@ -0,0 +1,310 @@
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: infisicaldynamicsecrets.secrets.infisical.com
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.10.0
|
||||
labels:
|
||||
{{- include "secrets-operator.labels" . | nindent 4 }}
|
||||
spec:
|
||||
group: secrets.infisical.com
|
||||
names:
|
||||
kind: InfisicalDynamicSecret
|
||||
listKind: InfisicalDynamicSecretList
|
||||
plural: infisicaldynamicsecrets
|
||||
singular: infisicaldynamicsecret
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: InfisicalDynamicSecret is the Schema for the infisicaldynamicsecrets
|
||||
API.
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: InfisicalDynamicSecretSpec defines the desired state of InfisicalDynamicSecret.
|
||||
properties:
|
||||
authentication:
|
||||
properties:
|
||||
awsIamAuth:
|
||||
properties:
|
||||
identityId:
|
||||
type: string
|
||||
required:
|
||||
- identityId
|
||||
type: object
|
||||
azureAuth:
|
||||
properties:
|
||||
identityId:
|
||||
type: string
|
||||
resource:
|
||||
type: string
|
||||
required:
|
||||
- identityId
|
||||
type: object
|
||||
gcpIamAuth:
|
||||
properties:
|
||||
identityId:
|
||||
type: string
|
||||
serviceAccountKeyFilePath:
|
||||
type: string
|
||||
required:
|
||||
- identityId
|
||||
- serviceAccountKeyFilePath
|
||||
type: object
|
||||
gcpIdTokenAuth:
|
||||
properties:
|
||||
identityId:
|
||||
type: string
|
||||
required:
|
||||
- identityId
|
||||
type: object
|
||||
kubernetesAuth:
|
||||
properties:
|
||||
identityId:
|
||||
type: string
|
||||
serviceAccountRef:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- namespace
|
||||
type: object
|
||||
required:
|
||||
- identityId
|
||||
- serviceAccountRef
|
||||
type: object
|
||||
universalAuth:
|
||||
properties:
|
||||
credentialsRef:
|
||||
properties:
|
||||
secretName:
|
||||
description: The name of the Kubernetes Secret
|
||||
type: string
|
||||
secretNamespace:
|
||||
description: The name space where the Kubernetes Secret
|
||||
is located
|
||||
type: string
|
||||
required:
|
||||
- secretName
|
||||
- secretNamespace
|
||||
type: object
|
||||
required:
|
||||
- credentialsRef
|
||||
type: object
|
||||
type: object
|
||||
dynamicSecret:
|
||||
properties:
|
||||
environmentSlug:
|
||||
type: string
|
||||
projectId:
|
||||
type: string
|
||||
secretName:
|
||||
type: string
|
||||
secretsPath:
|
||||
type: string
|
||||
required:
|
||||
- environmentSlug
|
||||
- projectId
|
||||
- secretName
|
||||
- secretsPath
|
||||
type: object
|
||||
hostAPI:
|
||||
type: string
|
||||
leaseRevocationPolicy:
|
||||
type: string
|
||||
leaseTTL:
|
||||
type: string
|
||||
managedSecretReference:
|
||||
properties:
|
||||
creationPolicy:
|
||||
default: Orphan
|
||||
description: 'The Kubernetes Secret creation policy. Enum with values:
|
||||
''Owner'', ''Orphan''. Owner creates the secret and sets .metadata.ownerReferences
|
||||
of the InfisicalSecret CRD that created it. Orphan will not set
|
||||
the secret owner. This will result in the secret being orphaned
|
||||
and not deleted when the resource is deleted.'
|
||||
type: string
|
||||
secretName:
|
||||
description: The name of the Kubernetes Secret
|
||||
type: string
|
||||
secretNamespace:
|
||||
description: The name space where the Kubernetes Secret is located
|
||||
type: string
|
||||
secretType:
|
||||
default: Opaque
|
||||
description: 'The Kubernetes Secret type (experimental feature).
|
||||
More info: https://kubernetes.io/docs/concepts/configuration/secret/#secret-types'
|
||||
type: string
|
||||
template:
|
||||
description: The template to transform the secret data
|
||||
properties:
|
||||
data:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: The template key values
|
||||
type: object
|
||||
includeAllSecrets:
|
||||
description: This injects all retrieved secrets into the top
|
||||
level of your template. Secrets defined in the template will
|
||||
take precedence over the injected ones.
|
||||
type: boolean
|
||||
type: object
|
||||
required:
|
||||
- secretName
|
||||
- secretNamespace
|
||||
type: object
|
||||
tls:
|
||||
properties:
|
||||
caRef:
|
||||
description: Reference to secret containing CA cert
|
||||
properties:
|
||||
key:
|
||||
description: The name of the secret property with the CA certificate
|
||||
value
|
||||
type: string
|
||||
secretName:
|
||||
description: The name of the Kubernetes Secret
|
||||
type: string
|
||||
secretNamespace:
|
||||
description: The namespace where the Kubernetes Secret is located
|
||||
type: string
|
||||
required:
|
||||
- key
|
||||
- secretName
|
||||
- secretNamespace
|
||||
type: object
|
||||
type: object
|
||||
required:
|
||||
- authentication
|
||||
- dynamicSecret
|
||||
- leaseRevocationPolicy
|
||||
- leaseTTL
|
||||
- managedSecretReference
|
||||
type: object
|
||||
status:
|
||||
description: InfisicalDynamicSecretStatus defines the observed state of
|
||||
InfisicalDynamicSecret.
|
||||
properties:
|
||||
conditions:
|
||||
items:
|
||||
description: "Condition contains details for one aspect of the current
|
||||
state of this API Resource. --- This struct is intended for direct
|
||||
use as an array at the field path .status.conditions. For example,
|
||||
\n type FooStatus struct{ // Represents the observations of a foo's
|
||||
current state. // Known .status.conditions.type are: \"Available\",
|
||||
\"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge
|
||||
// +listType=map // +listMapKey=type Conditions []metav1.Condition
|
||||
`json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\"
|
||||
protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }"
|
||||
properties:
|
||||
lastTransitionTime:
|
||||
description: lastTransitionTime is the last time the condition
|
||||
transitioned from one status to another. This should be when
|
||||
the underlying condition changed. If that is not known, then
|
||||
using the time when the API field changed is acceptable.
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
description: message is a human readable message indicating details
|
||||
about the transition. This may be an empty string.
|
||||
maxLength: 32768
|
||||
type: string
|
||||
observedGeneration:
|
||||
description: observedGeneration represents the .metadata.generation
|
||||
that the condition was set based upon. For instance, if .metadata.generation
|
||||
is currently 12, but the .status.conditions[x].observedGeneration
|
||||
is 9, the condition is out of date with respect to the current
|
||||
state of the instance.
|
||||
format: int64
|
||||
minimum: 0
|
||||
type: integer
|
||||
reason:
|
||||
description: reason contains a programmatic identifier indicating
|
||||
the reason for the condition's last transition. Producers of
|
||||
specific condition types may define expected values and meanings
|
||||
for this field, and whether the values are considered a guaranteed
|
||||
API. The value should be a CamelCase string. This field may
|
||||
not be empty.
|
||||
maxLength: 1024
|
||||
minLength: 1
|
||||
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
|
||||
type: string
|
||||
status:
|
||||
description: status of the condition, one of True, False, Unknown.
|
||||
enum:
|
||||
- "True"
|
||||
- "False"
|
||||
- Unknown
|
||||
type: string
|
||||
type:
|
||||
description: type of condition in CamelCase or in foo.example.com/CamelCase.
|
||||
--- Many .condition.type values are consistent across resources
|
||||
like Available, but because arbitrary conditions can be useful
|
||||
(see .node.status.conditions), the ability to deconflict is
|
||||
important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
|
||||
maxLength: 316
|
||||
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
|
||||
type: string
|
||||
required:
|
||||
- lastTransitionTime
|
||||
- message
|
||||
- reason
|
||||
- status
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
dynamicSecretId:
|
||||
type: string
|
||||
lease:
|
||||
properties:
|
||||
creationTimestamp:
|
||||
format: date-time
|
||||
type: string
|
||||
expiresAt:
|
||||
format: date-time
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
version:
|
||||
format: int64
|
||||
type: integer
|
||||
required:
|
||||
- creationTimestamp
|
||||
- expiresAt
|
||||
- id
|
||||
- version
|
||||
type: object
|
||||
maxTTL:
|
||||
description: The MaxTTL can be null, if it's null, there's no max TTL
|
||||
and we should never have to renew.
|
||||
type: string
|
||||
required:
|
||||
- conditions
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
plural: ""
|
||||
conditions: []
|
||||
storedVersions: []
|
@@ -90,7 +90,6 @@ spec:
|
||||
- serviceAccountRef
|
||||
type: object
|
||||
universalAuth:
|
||||
description: PushSecretUniversalAuth defines universal authentication
|
||||
properties:
|
||||
credentialsRef:
|
||||
properties:
|
||||
|
@@ -51,6 +51,32 @@ rules:
|
||||
- list
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- secrets.infisical.com
|
||||
resources:
|
||||
- infisicaldynamicsecrets
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- secrets.infisical.com
|
||||
resources:
|
||||
- infisicaldynamicsecrets/finalizers
|
||||
verbs:
|
||||
- update
|
||||
- apiGroups:
|
||||
- secrets.infisical.com
|
||||
resources:
|
||||
- infisicaldynamicsecrets/status
|
||||
verbs:
|
||||
- get
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- secrets.infisical.com
|
||||
resources:
|
||||
|
@@ -32,7 +32,7 @@ controllerManager:
|
||||
- ALL
|
||||
image:
|
||||
repository: infisical/kubernetes-operator
|
||||
tag: v0.8.0
|
||||
tag: v0.8.1
|
||||
resources:
|
||||
limits:
|
||||
cpu: 500m
|
||||
|
@@ -26,4 +26,13 @@ resources:
|
||||
kind: InfisicalPushSecretSecret
|
||||
path: github.com/Infisical/infisical/k8-operator/api/v1alpha1
|
||||
version: v1alpha1
|
||||
- api:
|
||||
crdVersion: v1
|
||||
namespaced: true
|
||||
controller: true
|
||||
domain: infisical.com
|
||||
group: secrets
|
||||
kind: InfisicalDynamicSecret
|
||||
path: github.com/Infisical/infisical/k8-operator/api/v1alpha1
|
||||
version: v1alpha1
|
||||
version: "3"
|
||||
|
109
k8-operator/api/v1alpha1/common.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package v1alpha1
|
||||
|
||||
type GenericInfisicalAuthentication struct {
|
||||
// +kubebuilder:validation:Optional
|
||||
UniversalAuth GenericUniversalAuth `json:"universalAuth,omitempty"`
|
||||
// +kubebuilder:validation:Optional
|
||||
KubernetesAuth GenericKubernetesAuth `json:"kubernetesAuth,omitempty"`
|
||||
// +kubebuilder:validation:Optional
|
||||
AwsIamAuth GenericAwsIamAuth `json:"awsIamAuth,omitempty"`
|
||||
// +kubebuilder:validation:Optional
|
||||
AzureAuth GenericAzureAuth `json:"azureAuth,omitempty"`
|
||||
// +kubebuilder:validation:Optional
|
||||
GcpIdTokenAuth GenericGcpIdTokenAuth `json:"gcpIdTokenAuth,omitempty"`
|
||||
// +kubebuilder:validation:Optional
|
||||
GcpIamAuth GenericGcpIamAuth `json:"gcpIamAuth,omitempty"`
|
||||
}
|
||||
|
||||
type GenericUniversalAuth struct {
|
||||
// +kubebuilder:validation:Required
|
||||
CredentialsRef KubeSecretReference `json:"credentialsRef"`
|
||||
}
|
||||
|
||||
type GenericAwsIamAuth struct {
|
||||
// +kubebuilder:validation:Required
|
||||
IdentityID string `json:"identityId"`
|
||||
}
|
||||
|
||||
type GenericAzureAuth struct {
|
||||
// +kubebuilder:validation:Required
|
||||
IdentityID string `json:"identityId"`
|
||||
// +kubebuilder:validation:Optional
|
||||
Resource string `json:"resource,omitempty"`
|
||||
}
|
||||
|
||||
type GenericGcpIdTokenAuth struct {
|
||||
// +kubebuilder:validation:Required
|
||||
IdentityID string `json:"identityId"`
|
||||
}
|
||||
|
||||
type GenericGcpIamAuth struct {
|
||||
// +kubebuilder:validation:Required
|
||||
IdentityID string `json:"identityId"`
|
||||
// +kubebuilder:validation:Required
|
||||
ServiceAccountKeyFilePath string `json:"serviceAccountKeyFilePath"`
|
||||
}
|
||||
|
||||
type GenericKubernetesAuth struct {
|
||||
// +kubebuilder:validation:Required
|
||||
IdentityID string `json:"identityId"`
|
||||
// +kubebuilder:validation:Required
|
||||
ServiceAccountRef KubernetesServiceAccountRef `json:"serviceAccountRef"`
|
||||
}
|
||||
|
||||
type TLSConfig struct {
|
||||
// Reference to secret containing CA cert
|
||||
// +kubebuilder:validation:Optional
|
||||
CaRef CaReference `json:"caRef,omitempty"`
|
||||
}
|
||||
|
||||
type CaReference struct {
|
||||
// The name of the Kubernetes Secret
|
||||
// +kubebuilder:validation:Required
|
||||
SecretName string `json:"secretName"`
|
||||
|
||||
// The namespace where the Kubernetes Secret is located
|
||||
// +kubebuilder:validation:Required
|
||||
SecretNamespace string `json:"secretNamespace"`
|
||||
|
||||
// +kubebuilder:validation:Required
|
||||
// The name of the secret property with the CA certificate value
|
||||
SecretKey string `json:"key"`
|
||||
}
|
||||
|
||||
type KubeSecretReference struct {
|
||||
// The name of the Kubernetes Secret
|
||||
// +kubebuilder:validation:Required
|
||||
SecretName string `json:"secretName"`
|
||||
|
||||
// The name space where the Kubernetes Secret is located
|
||||
// +kubebuilder:validation:Required
|
||||
SecretNamespace string `json:"secretNamespace"`
|
||||
}
|
||||
|
||||
type ManagedKubeSecretConfig struct {
|
||||
// The name of the Kubernetes Secret
|
||||
// +kubebuilder:validation:Required
|
||||
SecretName string `json:"secretName"`
|
||||
|
||||
// The name space where the Kubernetes Secret is located
|
||||
// +kubebuilder:validation:Required
|
||||
SecretNamespace string `json:"secretNamespace"`
|
||||
|
||||
// The Kubernetes Secret type (experimental feature). More info: https://kubernetes.io/docs/concepts/configuration/secret/#secret-types
|
||||
// +kubebuilder:validation:Optional
|
||||
// +kubebuilder:default:=Opaque
|
||||
SecretType string `json:"secretType"`
|
||||
|
||||
// The Kubernetes Secret creation policy.
|
||||
// Enum with values: 'Owner', 'Orphan'.
|
||||
// Owner creates the secret and sets .metadata.ownerReferences of the InfisicalSecret CRD that created it.
|
||||
// Orphan will not set the secret owner. This will result in the secret being orphaned and not deleted when the resource is deleted.
|
||||
// +kubebuilder:validation:Optional
|
||||
// +kubebuilder:default:=Orphan
|
||||
CreationPolicy string `json:"creationPolicy"`
|
||||
|
||||
// The template to transform the secret data
|
||||
// +kubebuilder:validation:Optional
|
||||
Template *InfisicalSecretTemplate `json:"template,omitempty"`
|
||||
}
|
99
k8-operator/api/v1alpha1/infisicaldynamicsecret_types.go
Normal file
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
Copyright 2022.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
type InfisicalDynamicSecretLease struct {
|
||||
ID string `json:"id"`
|
||||
Version int64 `json:"version"`
|
||||
CreationTimestamp metav1.Time `json:"creationTimestamp"`
|
||||
ExpiresAt metav1.Time `json:"expiresAt"`
|
||||
}
|
||||
|
||||
type DynamicSecretDetails struct {
|
||||
// +kubebuilder:validation:Required
|
||||
// +kubebuilder:validation:Immutable
|
||||
SecretName string `json:"secretName"`
|
||||
// +kubebuilder:validation:Required
|
||||
// +kubebuilder:validation:Immutable
|
||||
SecretPath string `json:"secretsPath"`
|
||||
// +kubebuilder:validation:Required
|
||||
// +kubebuilder:validation:Immutable
|
||||
EnvironmentSlug string `json:"environmentSlug"`
|
||||
// +kubebuilder:validation:Required
|
||||
// +kubebuilder:validation:Immutable
|
||||
ProjectID string `json:"projectId"`
|
||||
}
|
||||
|
||||
// InfisicalDynamicSecretSpec defines the desired state of InfisicalDynamicSecret.
|
||||
type InfisicalDynamicSecretSpec struct {
|
||||
// +kubebuilder:validation:Required
|
||||
ManagedSecretReference ManagedKubeSecretConfig `json:"managedSecretReference"` // The destination to store the lease in.
|
||||
|
||||
// +kubebuilder:validation:Required
|
||||
Authentication GenericInfisicalAuthentication `json:"authentication"` // The authentication to use for authenticating with Infisical.
|
||||
|
||||
// +kubebuilder:validation:Required
|
||||
DynamicSecret DynamicSecretDetails `json:"dynamicSecret"` // The dynamic secret to create the lease for. Required.
|
||||
|
||||
LeaseRevocationPolicy string `json:"leaseRevocationPolicy"` // Revoke will revoke the lease when the resource is deleted. Optional, will default to no revocation.
|
||||
LeaseTTL string `json:"leaseTTL"` // The TTL of the lease in seconds. Optional, will default to the dynamic secret default TTL.
|
||||
|
||||
// +kubebuilder:validation:Optional
|
||||
HostAPI string `json:"hostAPI"`
|
||||
|
||||
// +kubebuilder:validation:Optional
|
||||
TLS TLSConfig `json:"tls"`
|
||||
}
|
||||
|
||||
// InfisicalDynamicSecretStatus defines the observed state of InfisicalDynamicSecret.
|
||||
type InfisicalDynamicSecretStatus struct {
|
||||
Conditions []metav1.Condition `json:"conditions"`
|
||||
|
||||
Lease *InfisicalDynamicSecretLease `json:"lease,omitempty"`
|
||||
DynamicSecretID string `json:"dynamicSecretId,omitempty"`
|
||||
// The MaxTTL can be null, if it's null, there's no max TTL and we should never have to renew.
|
||||
MaxTTL string `json:"maxTTL,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:subresource:status
|
||||
|
||||
// InfisicalDynamicSecret is the Schema for the infisicaldynamicsecrets API.
|
||||
type InfisicalDynamicSecret struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec InfisicalDynamicSecretSpec `json:"spec,omitempty"`
|
||||
Status InfisicalDynamicSecretStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// InfisicalDynamicSecretList contains a list of InfisicalDynamicSecret.
|
||||
type InfisicalDynamicSecretList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []InfisicalDynamicSecret `json:"items"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
SchemeBuilder.Register(&InfisicalDynamicSecret{}, &InfisicalDynamicSecretList{})
|
||||
}
|
@@ -16,64 +16,6 @@ type InfisicalPushSecretDestination struct {
|
||||
ProjectID string `json:"projectId"`
|
||||
}
|
||||
|
||||
type PushSecretTlsConfig struct {
|
||||
// Reference to secret containing CA cert
|
||||
// +kubebuilder:validation:Optional
|
||||
CaRef CaReference `json:"caRef,omitempty"`
|
||||
}
|
||||
|
||||
// PushSecretUniversalAuth defines universal authentication
|
||||
type PushSecretUniversalAuth struct {
|
||||
// +kubebuilder:validation:Required
|
||||
CredentialsRef KubeSecretReference `json:"credentialsRef"`
|
||||
}
|
||||
|
||||
type PushSecretAwsIamAuth struct {
|
||||
// +kubebuilder:validation:Required
|
||||
IdentityID string `json:"identityId"`
|
||||
}
|
||||
|
||||
type PushSecretAzureAuth struct {
|
||||
// +kubebuilder:validation:Required
|
||||
IdentityID string `json:"identityId"`
|
||||
// +kubebuilder:validation:Optional
|
||||
Resource string `json:"resource,omitempty"`
|
||||
}
|
||||
|
||||
type PushSecretGcpIdTokenAuth struct {
|
||||
// +kubebuilder:validation:Required
|
||||
IdentityID string `json:"identityId"`
|
||||
}
|
||||
|
||||
type PushSecretGcpIamAuth struct {
|
||||
// +kubebuilder:validation:Required
|
||||
IdentityID string `json:"identityId"`
|
||||
// +kubebuilder:validation:Required
|
||||
ServiceAccountKeyFilePath string `json:"serviceAccountKeyFilePath"`
|
||||
}
|
||||
|
||||
type PushSecretKubernetesAuth struct {
|
||||
// +kubebuilder:validation:Required
|
||||
IdentityID string `json:"identityId"`
|
||||
// +kubebuilder:validation:Required
|
||||
ServiceAccountRef KubernetesServiceAccountRef `json:"serviceAccountRef"`
|
||||
}
|
||||
|
||||
type PushSecretAuthentication struct {
|
||||
// +kubebuilder:validation:Optional
|
||||
UniversalAuth PushSecretUniversalAuth `json:"universalAuth,omitempty"`
|
||||
// +kubebuilder:validation:Optional
|
||||
KubernetesAuth PushSecretKubernetesAuth `json:"kubernetesAuth,omitempty"`
|
||||
// +kubebuilder:validation:Optional
|
||||
AwsIamAuth PushSecretAwsIamAuth `json:"awsIamAuth,omitempty"`
|
||||
// +kubebuilder:validation:Optional
|
||||
AzureAuth PushSecretAzureAuth `json:"azureAuth,omitempty"`
|
||||
// +kubebuilder:validation:Optional
|
||||
GcpIdTokenAuth PushSecretGcpIdTokenAuth `json:"gcpIdTokenAuth,omitempty"`
|
||||
// +kubebuilder:validation:Optional
|
||||
GcpIamAuth PushSecretGcpIamAuth `json:"gcpIamAuth,omitempty"`
|
||||
}
|
||||
|
||||
type SecretPush struct {
|
||||
// +kubebuilder:validation:Required
|
||||
Secret KubeSecretReference `json:"secret"`
|
||||
@@ -92,7 +34,7 @@ type InfisicalPushSecretSpec struct {
|
||||
Destination InfisicalPushSecretDestination `json:"destination"`
|
||||
|
||||
// +kubebuilder:validation:Optional
|
||||
Authentication PushSecretAuthentication `json:"authentication"`
|
||||
Authentication GenericInfisicalAuthentication `json:"authentication"`
|
||||
|
||||
// +kubebuilder:validation:Required
|
||||
Push SecretPush `json:"push"`
|
||||
@@ -104,7 +46,7 @@ type InfisicalPushSecretSpec struct {
|
||||
HostAPI string `json:"hostAPI"`
|
||||
|
||||
// +kubebuilder:validation:Optional
|
||||
TLS PushSecretTlsConfig `json:"tls"`
|
||||
TLS TLSConfig `json:"tls"`
|
||||
}
|
||||
|
||||
// InfisicalPushSecretStatus defines the observed state of InfisicalPushSecret
|
||||
|
@@ -116,43 +116,6 @@ type MachineIdentityScopeInWorkspace struct {
|
||||
Recursive bool `json:"recursive"`
|
||||
}
|
||||
|
||||
type KubeSecretReference struct {
|
||||
// The name of the Kubernetes Secret
|
||||
// +kubebuilder:validation:Required
|
||||
SecretName string `json:"secretName"`
|
||||
|
||||
// The name space where the Kubernetes Secret is located
|
||||
// +kubebuilder:validation:Required
|
||||
SecretNamespace string `json:"secretNamespace"`
|
||||
}
|
||||
|
||||
type MangedKubeSecretConfig struct {
|
||||
// The name of the Kubernetes Secret
|
||||
// +kubebuilder:validation:Required
|
||||
SecretName string `json:"secretName"`
|
||||
|
||||
// The name space where the Kubernetes Secret is located
|
||||
// +kubebuilder:validation:Required
|
||||
SecretNamespace string `json:"secretNamespace"`
|
||||
|
||||
// The Kubernetes Secret type (experimental feature). More info: https://kubernetes.io/docs/concepts/configuration/secret/#secret-types
|
||||
// +kubebuilder:validation:Optional
|
||||
// +kubebuilder:default:=Opaque
|
||||
SecretType string `json:"secretType"`
|
||||
|
||||
// The Kubernetes Secret creation policy.
|
||||
// Enum with values: 'Owner', 'Orphan'.
|
||||
// Owner creates the secret and sets .metadata.ownerReferences of the InfisicalSecret CRD that created it.
|
||||
// Orphan will not set the secret owner. This will result in the secret being orphaned and not deleted when the resource is deleted.
|
||||
// +kubebuilder:validation:Optional
|
||||
// +kubebuilder:default:=Orphan
|
||||
CreationPolicy string `json:"creationPolicy"`
|
||||
|
||||
// The template to transform the secret data
|
||||
// +kubebuilder:validation:Optional
|
||||
Template *InfisicalSecretTemplate `json:"template,omitempty"`
|
||||
}
|
||||
|
||||
type InfisicalSecretTemplate struct {
|
||||
// This injects all retrieved secrets into the top level of your template.
|
||||
// Secrets defined in the template will take precedence over the injected ones.
|
||||
@@ -163,26 +126,6 @@ type InfisicalSecretTemplate struct {
|
||||
Data map[string]string `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
type CaReference struct {
|
||||
// The name of the Kubernetes Secret
|
||||
// +kubebuilder:validation:Required
|
||||
SecretName string `json:"secretName"`
|
||||
|
||||
// The namespace where the Kubernetes Secret is located
|
||||
// +kubebuilder:validation:Required
|
||||
SecretNamespace string `json:"secretNamespace"`
|
||||
|
||||
// +kubebuilder:validation:Required
|
||||
// The name of the secret property with the CA certificate value
|
||||
SecretKey string `json:"key"`
|
||||
}
|
||||
|
||||
type TLSConfig struct {
|
||||
// Reference to secret containing CA cert
|
||||
// +kubebuilder:validation:Optional
|
||||
CaRef CaReference `json:"caRef,omitempty"`
|
||||
}
|
||||
|
||||
// InfisicalSecretSpec defines the desired state of InfisicalSecret
|
||||
type InfisicalSecretSpec struct {
|
||||
// +kubebuilder:validation:Optional
|
||||
@@ -192,7 +135,7 @@ type InfisicalSecretSpec struct {
|
||||
Authentication Authentication `json:"authentication"`
|
||||
|
||||
// +kubebuilder:validation:Required
|
||||
ManagedSecretReference MangedKubeSecretConfig `json:"managedSecretReference"`
|
||||
ManagedSecretReference ManagedKubeSecretConfig `json:"managedSecretReference"`
|
||||
|
||||
// +kubebuilder:default:=60
|
||||
ResyncInterval int `json:"resyncInterval"`
|
||||
|
@@ -96,6 +96,21 @@ func (in *CaReference) DeepCopy() *CaReference {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *DynamicSecretDetails) DeepCopyInto(out *DynamicSecretDetails) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DynamicSecretDetails.
|
||||
func (in *DynamicSecretDetails) DeepCopy() *DynamicSecretDetails {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(DynamicSecretDetails)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *GCPIdTokenAuthDetails) DeepCopyInto(out *GCPIdTokenAuthDetails) {
|
||||
*out = *in
|
||||
@@ -128,6 +143,241 @@ func (in *GcpIamAuthDetails) DeepCopy() *GcpIamAuthDetails {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *GenericAwsIamAuth) DeepCopyInto(out *GenericAwsIamAuth) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GenericAwsIamAuth.
|
||||
func (in *GenericAwsIamAuth) DeepCopy() *GenericAwsIamAuth {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(GenericAwsIamAuth)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *GenericAzureAuth) DeepCopyInto(out *GenericAzureAuth) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GenericAzureAuth.
|
||||
func (in *GenericAzureAuth) DeepCopy() *GenericAzureAuth {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(GenericAzureAuth)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *GenericGcpIamAuth) DeepCopyInto(out *GenericGcpIamAuth) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GenericGcpIamAuth.
|
||||
func (in *GenericGcpIamAuth) DeepCopy() *GenericGcpIamAuth {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(GenericGcpIamAuth)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *GenericGcpIdTokenAuth) DeepCopyInto(out *GenericGcpIdTokenAuth) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GenericGcpIdTokenAuth.
|
||||
func (in *GenericGcpIdTokenAuth) DeepCopy() *GenericGcpIdTokenAuth {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(GenericGcpIdTokenAuth)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *GenericInfisicalAuthentication) DeepCopyInto(out *GenericInfisicalAuthentication) {
|
||||
*out = *in
|
||||
out.UniversalAuth = in.UniversalAuth
|
||||
out.KubernetesAuth = in.KubernetesAuth
|
||||
out.AwsIamAuth = in.AwsIamAuth
|
||||
out.AzureAuth = in.AzureAuth
|
||||
out.GcpIdTokenAuth = in.GcpIdTokenAuth
|
||||
out.GcpIamAuth = in.GcpIamAuth
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GenericInfisicalAuthentication.
|
||||
func (in *GenericInfisicalAuthentication) DeepCopy() *GenericInfisicalAuthentication {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(GenericInfisicalAuthentication)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *GenericKubernetesAuth) DeepCopyInto(out *GenericKubernetesAuth) {
|
||||
*out = *in
|
||||
out.ServiceAccountRef = in.ServiceAccountRef
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GenericKubernetesAuth.
|
||||
func (in *GenericKubernetesAuth) DeepCopy() *GenericKubernetesAuth {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(GenericKubernetesAuth)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *GenericUniversalAuth) DeepCopyInto(out *GenericUniversalAuth) {
|
||||
*out = *in
|
||||
out.CredentialsRef = in.CredentialsRef
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GenericUniversalAuth.
|
||||
func (in *GenericUniversalAuth) DeepCopy() *GenericUniversalAuth {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(GenericUniversalAuth)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *InfisicalDynamicSecret) DeepCopyInto(out *InfisicalDynamicSecret) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
in.Status.DeepCopyInto(&out.Status)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InfisicalDynamicSecret.
|
||||
func (in *InfisicalDynamicSecret) DeepCopy() *InfisicalDynamicSecret {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(InfisicalDynamicSecret)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *InfisicalDynamicSecret) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *InfisicalDynamicSecretLease) DeepCopyInto(out *InfisicalDynamicSecretLease) {
|
||||
*out = *in
|
||||
in.CreationTimestamp.DeepCopyInto(&out.CreationTimestamp)
|
||||
in.ExpiresAt.DeepCopyInto(&out.ExpiresAt)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InfisicalDynamicSecretLease.
|
||||
func (in *InfisicalDynamicSecretLease) DeepCopy() *InfisicalDynamicSecretLease {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(InfisicalDynamicSecretLease)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *InfisicalDynamicSecretList) DeepCopyInto(out *InfisicalDynamicSecretList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]InfisicalDynamicSecret, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InfisicalDynamicSecretList.
|
||||
func (in *InfisicalDynamicSecretList) DeepCopy() *InfisicalDynamicSecretList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(InfisicalDynamicSecretList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *InfisicalDynamicSecretList) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *InfisicalDynamicSecretSpec) DeepCopyInto(out *InfisicalDynamicSecretSpec) {
|
||||
*out = *in
|
||||
in.ManagedSecretReference.DeepCopyInto(&out.ManagedSecretReference)
|
||||
out.Authentication = in.Authentication
|
||||
out.DynamicSecret = in.DynamicSecret
|
||||
out.TLS = in.TLS
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InfisicalDynamicSecretSpec.
|
||||
func (in *InfisicalDynamicSecretSpec) DeepCopy() *InfisicalDynamicSecretSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(InfisicalDynamicSecretSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *InfisicalDynamicSecretStatus) DeepCopyInto(out *InfisicalDynamicSecretStatus) {
|
||||
*out = *in
|
||||
if in.Conditions != nil {
|
||||
in, out := &in.Conditions, &out.Conditions
|
||||
*out = make([]v1.Condition, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.Lease != nil {
|
||||
in, out := &in.Lease, &out.Lease
|
||||
*out = new(InfisicalDynamicSecretLease)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InfisicalDynamicSecretStatus.
|
||||
func (in *InfisicalDynamicSecretStatus) DeepCopy() *InfisicalDynamicSecretStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(InfisicalDynamicSecretStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *InfisicalPushSecret) DeepCopyInto(out *InfisicalPushSecret) {
|
||||
*out = *in
|
||||
@@ -435,7 +685,7 @@ func (in *MachineIdentityScopeInWorkspace) DeepCopy() *MachineIdentityScopeInWor
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *MangedKubeSecretConfig) DeepCopyInto(out *MangedKubeSecretConfig) {
|
||||
func (in *ManagedKubeSecretConfig) DeepCopyInto(out *ManagedKubeSecretConfig) {
|
||||
*out = *in
|
||||
if in.Template != nil {
|
||||
in, out := &in.Template, &out.Template
|
||||
@@ -444,141 +694,12 @@ func (in *MangedKubeSecretConfig) DeepCopyInto(out *MangedKubeSecretConfig) {
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MangedKubeSecretConfig.
|
||||
func (in *MangedKubeSecretConfig) DeepCopy() *MangedKubeSecretConfig {
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ManagedKubeSecretConfig.
|
||||
func (in *ManagedKubeSecretConfig) DeepCopy() *ManagedKubeSecretConfig {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(MangedKubeSecretConfig)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PushSecretAuthentication) DeepCopyInto(out *PushSecretAuthentication) {
|
||||
*out = *in
|
||||
out.UniversalAuth = in.UniversalAuth
|
||||
out.KubernetesAuth = in.KubernetesAuth
|
||||
out.AwsIamAuth = in.AwsIamAuth
|
||||
out.AzureAuth = in.AzureAuth
|
||||
out.GcpIdTokenAuth = in.GcpIdTokenAuth
|
||||
out.GcpIamAuth = in.GcpIamAuth
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PushSecretAuthentication.
|
||||
func (in *PushSecretAuthentication) DeepCopy() *PushSecretAuthentication {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PushSecretAuthentication)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PushSecretAwsIamAuth) DeepCopyInto(out *PushSecretAwsIamAuth) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PushSecretAwsIamAuth.
|
||||
func (in *PushSecretAwsIamAuth) DeepCopy() *PushSecretAwsIamAuth {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PushSecretAwsIamAuth)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PushSecretAzureAuth) DeepCopyInto(out *PushSecretAzureAuth) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PushSecretAzureAuth.
|
||||
func (in *PushSecretAzureAuth) DeepCopy() *PushSecretAzureAuth {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PushSecretAzureAuth)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PushSecretGcpIamAuth) DeepCopyInto(out *PushSecretGcpIamAuth) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PushSecretGcpIamAuth.
|
||||
func (in *PushSecretGcpIamAuth) DeepCopy() *PushSecretGcpIamAuth {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PushSecretGcpIamAuth)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PushSecretGcpIdTokenAuth) DeepCopyInto(out *PushSecretGcpIdTokenAuth) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PushSecretGcpIdTokenAuth.
|
||||
func (in *PushSecretGcpIdTokenAuth) DeepCopy() *PushSecretGcpIdTokenAuth {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PushSecretGcpIdTokenAuth)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PushSecretKubernetesAuth) DeepCopyInto(out *PushSecretKubernetesAuth) {
|
||||
*out = *in
|
||||
out.ServiceAccountRef = in.ServiceAccountRef
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PushSecretKubernetesAuth.
|
||||
func (in *PushSecretKubernetesAuth) DeepCopy() *PushSecretKubernetesAuth {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PushSecretKubernetesAuth)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PushSecretTlsConfig) DeepCopyInto(out *PushSecretTlsConfig) {
|
||||
*out = *in
|
||||
out.CaRef = in.CaRef
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PushSecretTlsConfig.
|
||||
func (in *PushSecretTlsConfig) DeepCopy() *PushSecretTlsConfig {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PushSecretTlsConfig)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PushSecretUniversalAuth) DeepCopyInto(out *PushSecretUniversalAuth) {
|
||||
*out = *in
|
||||
out.CredentialsRef = in.CredentialsRef
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PushSecretUniversalAuth.
|
||||
func (in *PushSecretUniversalAuth) DeepCopy() *PushSecretUniversalAuth {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PushSecretUniversalAuth)
|
||||
out := new(ManagedKubeSecretConfig)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
@@ -0,0 +1,306 @@
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.10.0
|
||||
creationTimestamp: null
|
||||
name: infisicaldynamicsecrets.secrets.infisical.com
|
||||
spec:
|
||||
group: secrets.infisical.com
|
||||
names:
|
||||
kind: InfisicalDynamicSecret
|
||||
listKind: InfisicalDynamicSecretList
|
||||
plural: infisicaldynamicsecrets
|
||||
singular: infisicaldynamicsecret
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: InfisicalDynamicSecret is the Schema for the infisicaldynamicsecrets
|
||||
API.
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: InfisicalDynamicSecretSpec defines the desired state of InfisicalDynamicSecret.
|
||||
properties:
|
||||
authentication:
|
||||
properties:
|
||||
awsIamAuth:
|
||||
properties:
|
||||
identityId:
|
||||
type: string
|
||||
required:
|
||||
- identityId
|
||||
type: object
|
||||
azureAuth:
|
||||
properties:
|
||||
identityId:
|
||||
type: string
|
||||
resource:
|
||||
type: string
|
||||
required:
|
||||
- identityId
|
||||
type: object
|
||||
gcpIamAuth:
|
||||
properties:
|
||||
identityId:
|
||||
type: string
|
||||
serviceAccountKeyFilePath:
|
||||
type: string
|
||||
required:
|
||||
- identityId
|
||||
- serviceAccountKeyFilePath
|
||||
type: object
|
||||
gcpIdTokenAuth:
|
||||
properties:
|
||||
identityId:
|
||||
type: string
|
||||
required:
|
||||
- identityId
|
||||
type: object
|
||||
kubernetesAuth:
|
||||
properties:
|
||||
identityId:
|
||||
type: string
|
||||
serviceAccountRef:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- namespace
|
||||
type: object
|
||||
required:
|
||||
- identityId
|
||||
- serviceAccountRef
|
||||
type: object
|
||||
universalAuth:
|
||||
properties:
|
||||
credentialsRef:
|
||||
properties:
|
||||
secretName:
|
||||
description: The name of the Kubernetes Secret
|
||||
type: string
|
||||
secretNamespace:
|
||||
description: The name space where the Kubernetes Secret
|
||||
is located
|
||||
type: string
|
||||
required:
|
||||
- secretName
|
||||
- secretNamespace
|
||||
type: object
|
||||
required:
|
||||
- credentialsRef
|
||||
type: object
|
||||
type: object
|
||||
dynamicSecret:
|
||||
properties:
|
||||
environmentSlug:
|
||||
type: string
|
||||
projectId:
|
||||
type: string
|
||||
secretName:
|
||||
type: string
|
||||
secretsPath:
|
||||
type: string
|
||||
required:
|
||||
- environmentSlug
|
||||
- projectId
|
||||
- secretName
|
||||
- secretsPath
|
||||
type: object
|
||||
hostAPI:
|
||||
type: string
|
||||
leaseRevocationPolicy:
|
||||
type: string
|
||||
leaseTTL:
|
||||
type: string
|
||||
managedSecretReference:
|
||||
properties:
|
||||
creationPolicy:
|
||||
default: Orphan
|
||||
description: 'The Kubernetes Secret creation policy. Enum with
|
||||
values: ''Owner'', ''Orphan''. Owner creates the secret and
|
||||
sets .metadata.ownerReferences of the InfisicalSecret CRD that
|
||||
created it. Orphan will not set the secret owner. This will
|
||||
result in the secret being orphaned and not deleted when the
|
||||
resource is deleted.'
|
||||
type: string
|
||||
secretName:
|
||||
description: The name of the Kubernetes Secret
|
||||
type: string
|
||||
secretNamespace:
|
||||
description: The name space where the Kubernetes Secret is located
|
||||
type: string
|
||||
secretType:
|
||||
default: Opaque
|
||||
description: 'The Kubernetes Secret type (experimental feature).
|
||||
More info: https://kubernetes.io/docs/concepts/configuration/secret/#secret-types'
|
||||
type: string
|
||||
template:
|
||||
description: The template to transform the secret data
|
||||
properties:
|
||||
data:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: The template key values
|
||||
type: object
|
||||
includeAllSecrets:
|
||||
description: This injects all retrieved secrets into the top
|
||||
level of your template. Secrets defined in the template
|
||||
will take precedence over the injected ones.
|
||||
type: boolean
|
||||
type: object
|
||||
required:
|
||||
- secretName
|
||||
- secretNamespace
|
||||
type: object
|
||||
tls:
|
||||
properties:
|
||||
caRef:
|
||||
description: Reference to secret containing CA cert
|
||||
properties:
|
||||
key:
|
||||
description: The name of the secret property with the CA certificate
|
||||
value
|
||||
type: string
|
||||
secretName:
|
||||
description: The name of the Kubernetes Secret
|
||||
type: string
|
||||
secretNamespace:
|
||||
description: The namespace where the Kubernetes Secret is
|
||||
located
|
||||
type: string
|
||||
required:
|
||||
- key
|
||||
- secretName
|
||||
- secretNamespace
|
||||
type: object
|
||||
type: object
|
||||
required:
|
||||
- authentication
|
||||
- dynamicSecret
|
||||
- leaseRevocationPolicy
|
||||
- leaseTTL
|
||||
- managedSecretReference
|
||||
type: object
|
||||
status:
|
||||
description: InfisicalDynamicSecretStatus defines the observed state of
|
||||
InfisicalDynamicSecret.
|
||||
properties:
|
||||
conditions:
|
||||
items:
|
||||
description: "Condition contains details for one aspect of the current
|
||||
state of this API Resource. --- This struct is intended for direct
|
||||
use as an array at the field path .status.conditions. For example,
|
||||
\n type FooStatus struct{ // Represents the observations of a
|
||||
foo's current state. // Known .status.conditions.type are: \"Available\",
|
||||
\"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge
|
||||
// +listType=map // +listMapKey=type Conditions []metav1.Condition
|
||||
`json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\"
|
||||
protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }"
|
||||
properties:
|
||||
lastTransitionTime:
|
||||
description: lastTransitionTime is the last time the condition
|
||||
transitioned from one status to another. This should be when
|
||||
the underlying condition changed. If that is not known, then
|
||||
using the time when the API field changed is acceptable.
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
description: message is a human readable message indicating
|
||||
details about the transition. This may be an empty string.
|
||||
maxLength: 32768
|
||||
type: string
|
||||
observedGeneration:
|
||||
description: observedGeneration represents the .metadata.generation
|
||||
that the condition was set based upon. For instance, if .metadata.generation
|
||||
is currently 12, but the .status.conditions[x].observedGeneration
|
||||
is 9, the condition is out of date with respect to the current
|
||||
state of the instance.
|
||||
format: int64
|
||||
minimum: 0
|
||||
type: integer
|
||||
reason:
|
||||
description: reason contains a programmatic identifier indicating
|
||||
the reason for the condition's last transition. Producers
|
||||
of specific condition types may define expected values and
|
||||
meanings for this field, and whether the values are considered
|
||||
a guaranteed API. The value should be a CamelCase string.
|
||||
This field may not be empty.
|
||||
maxLength: 1024
|
||||
minLength: 1
|
||||
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
|
||||
type: string
|
||||
status:
|
||||
description: status of the condition, one of True, False, Unknown.
|
||||
enum:
|
||||
- "True"
|
||||
- "False"
|
||||
- Unknown
|
||||
type: string
|
||||
type:
|
||||
description: type of condition in CamelCase or in foo.example.com/CamelCase.
|
||||
--- Many .condition.type values are consistent across resources
|
||||
like Available, but because arbitrary conditions can be useful
|
||||
(see .node.status.conditions), the ability to deconflict is
|
||||
important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
|
||||
maxLength: 316
|
||||
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
|
||||
type: string
|
||||
required:
|
||||
- lastTransitionTime
|
||||
- message
|
||||
- reason
|
||||
- status
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
dynamicSecretId:
|
||||
type: string
|
||||
lease:
|
||||
properties:
|
||||
creationTimestamp:
|
||||
format: date-time
|
||||
type: string
|
||||
expiresAt:
|
||||
format: date-time
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
version:
|
||||
format: int64
|
||||
type: integer
|
||||
required:
|
||||
- creationTimestamp
|
||||
- expiresAt
|
||||
- id
|
||||
- version
|
||||
type: object
|
||||
maxTTL:
|
||||
description: The MaxTTL can be null, if it's null, there's no max
|
||||
TTL and we should never have to renew.
|
||||
type: string
|
||||
required:
|
||||
- conditions
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
@@ -90,7 +90,6 @@ spec:
|
||||
- serviceAccountRef
|
||||
type: object
|
||||
universalAuth:
|
||||
description: PushSecretUniversalAuth defines universal authentication
|
||||
properties:
|
||||
credentialsRef:
|
||||
properties:
|
||||
|
@@ -4,6 +4,7 @@
|
||||
resources:
|
||||
- bases/secrets.infisical.com_infisicalsecrets.yaml
|
||||
- bases/secrets.infisical.com_infisicalpushsecrets.yaml
|
||||
- bases/secrets.infisical.com_infisicaldynamicsecrets.yaml
|
||||
#+kubebuilder:scaffold:crdkustomizeresource
|
||||
|
||||
patchesStrategicMerge:
|
||||
|
@@ -0,0 +1,27 @@
|
||||
# permissions for end users to edit infisicaldynamicsecrets.
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: k8-operator
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
name: infisicaldynamicsecret-editor-role
|
||||
rules:
|
||||
- apiGroups:
|
||||
- secrets.infisical.com
|
||||
resources:
|
||||
- infisicaldynamicsecrets
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- secrets.infisical.com
|
||||
resources:
|
||||
- infisicaldynamicsecrets/status
|
||||
verbs:
|
||||
- get
|
@@ -0,0 +1,23 @@
|
||||
# permissions for end users to view infisicaldynamicsecrets.
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: k8-operator
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
name: infisicaldynamicsecret-viewer-role
|
||||
rules:
|
||||
- apiGroups:
|
||||
- secrets.infisical.com
|
||||
resources:
|
||||
- infisicaldynamicsecrets
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- secrets.infisical.com
|
||||
resources:
|
||||
- infisicaldynamicsecrets/status
|
||||
verbs:
|
||||
- get
|
@@ -44,6 +44,32 @@ rules:
|
||||
- list
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- secrets.infisical.com
|
||||
resources:
|
||||
- infisicaldynamicsecrets
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- secrets.infisical.com
|
||||
resources:
|
||||
- infisicaldynamicsecrets/finalizers
|
||||
verbs:
|
||||
- update
|
||||
- apiGroups:
|
||||
- secrets.infisical.com
|
||||
resources:
|
||||
- infisicaldynamicsecrets/status
|
||||
verbs:
|
||||
- get
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- secrets.infisical.com
|
||||
resources:
|
||||
|
@@ -0,0 +1,27 @@
|
||||
apiVersion: secrets.infisical.com/v1alpha1
|
||||
kind: InfisicalDynamicSecret
|
||||
metadata:
|
||||
name: infisicaldynamicsecret-demo
|
||||
spec:
|
||||
hostAPI: https://app.infisical.com/api
|
||||
|
||||
dynamicSecret:
|
||||
secretName: <dynamic-secret-name>
|
||||
projectId: <project-id>
|
||||
secretsPath: <secret-path>
|
||||
environmentSlug: <env-slug>
|
||||
|
||||
leaseRevocationPolicy: Revoke # Revoke or None. Revoke will revoke leases created by the operator if the CRD is deleted.
|
||||
leaseTTL: 1m # TTL for the leases created. Must be below 24 hours.
|
||||
|
||||
# Reference to the secret that you want to store the lease credentials in. If a secret with the name specified name does not exist, it will automatically be created.
|
||||
managedSecretReference:
|
||||
secretName: lease
|
||||
secretNamespace: default
|
||||
creationPolicy: Orphan # Orphan or Owner
|
||||
|
||||
authentication:
|
||||
universalAuth:
|
||||
credentialsRef:
|
||||
secretName: universal-auth-credentials # universal-auth-credentials
|
||||
secretNamespace: default # default
|
@@ -1,7 +1,7 @@
|
||||
apiVersion: secrets.infisical.com/v1alpha1
|
||||
kind: InfisicalPushSecret
|
||||
metadata:
|
||||
name: infisical-push-secret-demo
|
||||
name: infisical-api-secret-sample-push
|
||||
spec:
|
||||
resyncInterval: 1m
|
||||
hostAPI: https://app.infisical.com/api
|
||||
|
146
k8-operator/controllers/infisicaldynamicsecret/conditions.go
Normal file
@@ -0,0 +1,146 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/Infisical/infisical/k8-operator/api/v1alpha1"
|
||||
"github.com/go-logr/logr"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func (r *InfisicalDynamicSecretReconciler) SetReconcileAutoRedeploymentConditionStatus(ctx context.Context, logger logr.Logger, infisicalDynamicSecret *v1alpha1.InfisicalDynamicSecret, numDeployments int, errorToConditionOn error) {
|
||||
if infisicalDynamicSecret.Status.Conditions == nil {
|
||||
infisicalDynamicSecret.Status.Conditions = []metav1.Condition{}
|
||||
}
|
||||
|
||||
if errorToConditionOn == nil {
|
||||
meta.SetStatusCondition(&infisicalDynamicSecret.Status.Conditions, metav1.Condition{
|
||||
Type: "secrets.infisical.com/AutoRedeployReady",
|
||||
Status: metav1.ConditionTrue,
|
||||
Reason: "OK",
|
||||
Message: fmt.Sprintf("Infisical has found %v deployments which are ready to be auto redeployed when dynamic secret lease changes", numDeployments),
|
||||
})
|
||||
} else {
|
||||
meta.SetStatusCondition(&infisicalDynamicSecret.Status.Conditions, metav1.Condition{
|
||||
Type: "secrets.infisical.com/AutoRedeployReady",
|
||||
Status: metav1.ConditionFalse,
|
||||
Reason: "Error",
|
||||
Message: fmt.Sprintf("Failed reconcile deployments because: %v", errorToConditionOn),
|
||||
})
|
||||
}
|
||||
|
||||
err := r.Client.Status().Update(ctx, infisicalDynamicSecret)
|
||||
if err != nil {
|
||||
logger.Error(err, "Could not set condition for AutoRedeployReady")
|
||||
}
|
||||
}
|
||||
|
||||
func (r *InfisicalDynamicSecretReconciler) SetAuthenticatedConditionStatus(ctx context.Context, logger logr.Logger, infisicalDynamicSecret *v1alpha1.InfisicalDynamicSecret, errorToConditionOn error) {
|
||||
if infisicalDynamicSecret.Status.Conditions == nil {
|
||||
infisicalDynamicSecret.Status.Conditions = []metav1.Condition{}
|
||||
}
|
||||
|
||||
if errorToConditionOn == nil {
|
||||
meta.SetStatusCondition(&infisicalDynamicSecret.Status.Conditions, metav1.Condition{
|
||||
Type: "secrets.infisical.com/Authenticated",
|
||||
Status: metav1.ConditionTrue,
|
||||
Reason: "OK",
|
||||
Message: "Infisical has successfully authenticated with the Infisical API",
|
||||
})
|
||||
} else {
|
||||
meta.SetStatusCondition(&infisicalDynamicSecret.Status.Conditions, metav1.Condition{
|
||||
Type: "secrets.infisical.com/Authenticated",
|
||||
Status: metav1.ConditionFalse,
|
||||
Reason: "Error",
|
||||
Message: fmt.Sprintf("Failed to authenticate with Infisical API because: %v", errorToConditionOn),
|
||||
})
|
||||
}
|
||||
|
||||
err := r.Client.Status().Update(ctx, infisicalDynamicSecret)
|
||||
if err != nil {
|
||||
logger.Error(err, "Could not set condition for Authenticated")
|
||||
}
|
||||
}
|
||||
|
||||
func (r *InfisicalDynamicSecretReconciler) SetLeaseRenewalConditionStatus(ctx context.Context, logger logr.Logger, infisicalDynamicSecret *v1alpha1.InfisicalDynamicSecret, errorToConditionOn error) {
|
||||
if infisicalDynamicSecret.Status.Conditions == nil {
|
||||
infisicalDynamicSecret.Status.Conditions = []metav1.Condition{}
|
||||
}
|
||||
|
||||
if errorToConditionOn == nil {
|
||||
meta.SetStatusCondition(&infisicalDynamicSecret.Status.Conditions, metav1.Condition{
|
||||
Type: "secrets.infisical.com/LeaseRenewal",
|
||||
Status: metav1.ConditionTrue,
|
||||
Reason: "OK",
|
||||
Message: "Infisical has successfully renewed the lease",
|
||||
})
|
||||
} else {
|
||||
meta.SetStatusCondition(&infisicalDynamicSecret.Status.Conditions, metav1.Condition{
|
||||
Type: "secrets.infisical.com/LeaseRenewal",
|
||||
Status: metav1.ConditionFalse,
|
||||
Reason: "Error",
|
||||
Message: fmt.Sprintf("Failed to renew the lease because: %v", errorToConditionOn),
|
||||
})
|
||||
}
|
||||
|
||||
err := r.Client.Status().Update(ctx, infisicalDynamicSecret)
|
||||
if err != nil {
|
||||
logger.Error(err, "Could not set condition for LeaseRenewal")
|
||||
}
|
||||
}
|
||||
|
||||
func (r *InfisicalDynamicSecretReconciler) SetCreatedLeaseConditionStatus(ctx context.Context, logger logr.Logger, infisicalDynamicSecret *v1alpha1.InfisicalDynamicSecret, errorToConditionOn error) {
|
||||
if infisicalDynamicSecret.Status.Conditions == nil {
|
||||
infisicalDynamicSecret.Status.Conditions = []metav1.Condition{}
|
||||
}
|
||||
|
||||
if errorToConditionOn == nil {
|
||||
meta.SetStatusCondition(&infisicalDynamicSecret.Status.Conditions, metav1.Condition{
|
||||
Type: "secrets.infisical.com/LeaseCreated",
|
||||
Status: metav1.ConditionTrue,
|
||||
Reason: "OK",
|
||||
Message: "Infisical has successfully created the lease",
|
||||
})
|
||||
} else {
|
||||
meta.SetStatusCondition(&infisicalDynamicSecret.Status.Conditions, metav1.Condition{
|
||||
Type: "secrets.infisical.com/LeaseCreated",
|
||||
Status: metav1.ConditionFalse,
|
||||
Reason: "Error",
|
||||
Message: fmt.Sprintf("Failed to create the lease because: %v", errorToConditionOn),
|
||||
})
|
||||
}
|
||||
|
||||
err := r.Client.Status().Update(ctx, infisicalDynamicSecret)
|
||||
if err != nil {
|
||||
logger.Error(err, "Could not set condition for LeaseCreated")
|
||||
}
|
||||
}
|
||||
|
||||
func (r *InfisicalDynamicSecretReconciler) SetReconcileConditionStatus(ctx context.Context, logger logr.Logger, infisicalDynamicSecret *v1alpha1.InfisicalDynamicSecret, errorToConditionOn error) {
|
||||
if infisicalDynamicSecret.Status.Conditions == nil {
|
||||
infisicalDynamicSecret.Status.Conditions = []metav1.Condition{}
|
||||
}
|
||||
|
||||
if errorToConditionOn == nil {
|
||||
meta.SetStatusCondition(&infisicalDynamicSecret.Status.Conditions, metav1.Condition{
|
||||
Type: "secrets.infisical.com/Reconcile",
|
||||
Status: metav1.ConditionTrue,
|
||||
Reason: "OK",
|
||||
Message: "Infisical has successfully reconciled the InfisicalDynamicSecret",
|
||||
})
|
||||
} else {
|
||||
meta.SetStatusCondition(&infisicalDynamicSecret.Status.Conditions, metav1.Condition{
|
||||
Type: "secrets.infisical.com/Reconcile",
|
||||
Status: metav1.ConditionFalse,
|
||||
Reason: "Error",
|
||||
Message: fmt.Sprintf("Failed to reconcile the InfisicalDynamicSecret because: %v", errorToConditionOn),
|
||||
})
|
||||
}
|
||||
|
||||
err := r.Client.Status().Update(ctx, infisicalDynamicSecret)
|
||||
if err != nil {
|
||||
logger.Error(err, "Could not set condition for Reconcile")
|
||||
}
|
||||
}
|