mirror of
https://github.com/Infisical/infisical.git
synced 2025-08-28 18:55:53 +00:00
Compare commits
17 Commits
update-har
...
doc/add-ca
Author | SHA1 | Date | |
---|---|---|---|
|
0d4d73b61d | ||
|
198b607e2e | ||
|
1f5a73047d | ||
|
0366df6e19 | ||
|
c77e0c0666 | ||
|
8e70731c4c | ||
|
21c6700db2 | ||
|
619062033b | ||
|
36973b1b5c | ||
|
1ca578ee03 | ||
|
8a7f7ac9fd | ||
|
049fd8e769 | ||
|
2c825616a6 | ||
|
874dc01692 | ||
|
b44b8bf647 | ||
|
ecf2cb6e51 | ||
|
1e5a9a6020 |
@@ -535,6 +535,107 @@ describe.each([{ auth: AuthMode.JWT }, { auth: AuthMode.IDENTITY_ACCESS_TOKEN }]
|
||||
);
|
||||
});
|
||||
|
||||
test.each(secretTestCases)("Bulk upsert secrets in path $path", async ({ secret, path }) => {
|
||||
const updateSharedSecRes = await testServer.inject({
|
||||
method: "PATCH",
|
||||
url: `/api/v3/secrets/batch/raw`,
|
||||
headers: {
|
||||
authorization: `Bearer ${authToken}`
|
||||
},
|
||||
body: {
|
||||
workspaceId: seedData1.projectV3.id,
|
||||
environment: seedData1.environment.slug,
|
||||
secretPath: path,
|
||||
mode: "upsert",
|
||||
secrets: Array.from(Array(5)).map((_e, i) => ({
|
||||
secretKey: `BULK-${secret.key}-${i + 1}`,
|
||||
secretValue: "update-value",
|
||||
secretComment: secret.comment
|
||||
}))
|
||||
}
|
||||
});
|
||||
expect(updateSharedSecRes.statusCode).toBe(200);
|
||||
const updateSharedSecPayload = JSON.parse(updateSharedSecRes.payload);
|
||||
expect(updateSharedSecPayload).toHaveProperty("secrets");
|
||||
|
||||
// bulk ones should exist
|
||||
const secrets = await getSecrets(seedData1.environment.slug, path);
|
||||
expect(secrets).toEqual(
|
||||
expect.arrayContaining(
|
||||
Array.from(Array(5)).map((_e, i) =>
|
||||
expect.objectContaining({
|
||||
secretKey: `BULK-${secret.key}-${i + 1}`,
|
||||
secretValue: "update-value",
|
||||
type: SecretType.Shared
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
await Promise.all(
|
||||
Array.from(Array(5)).map((_e, i) => deleteSecret({ path, key: `BULK-${secret.key}-${i + 1}` }))
|
||||
);
|
||||
});
|
||||
|
||||
test("Bulk upsert secrets in path multiple paths", async () => {
|
||||
const firstBatchSecrets = Array.from(Array(5)).map((_e, i) => ({
|
||||
secretKey: `BULK-KEY-${secretTestCases[0].secret.key}-${i + 1}`,
|
||||
secretValue: "update-value",
|
||||
secretComment: "comment",
|
||||
secretPath: secretTestCases[0].path
|
||||
}));
|
||||
const secondBatchSecrets = Array.from(Array(5)).map((_e, i) => ({
|
||||
secretKey: `BULK-KEY-${secretTestCases[1].secret.key}-${i + 1}`,
|
||||
secretValue: "update-value",
|
||||
secretComment: "comment",
|
||||
secretPath: secretTestCases[1].path
|
||||
}));
|
||||
const testSecrets = [...firstBatchSecrets, ...secondBatchSecrets];
|
||||
|
||||
const updateSharedSecRes = await testServer.inject({
|
||||
method: "PATCH",
|
||||
url: `/api/v3/secrets/batch/raw`,
|
||||
headers: {
|
||||
authorization: `Bearer ${authToken}`
|
||||
},
|
||||
body: {
|
||||
workspaceId: seedData1.projectV3.id,
|
||||
environment: seedData1.environment.slug,
|
||||
mode: "upsert",
|
||||
secrets: testSecrets
|
||||
}
|
||||
});
|
||||
expect(updateSharedSecRes.statusCode).toBe(200);
|
||||
const updateSharedSecPayload = JSON.parse(updateSharedSecRes.payload);
|
||||
expect(updateSharedSecPayload).toHaveProperty("secrets");
|
||||
|
||||
// bulk ones should exist
|
||||
const firstBatchSecretsOnInfisical = await getSecrets(seedData1.environment.slug, secretTestCases[0].path);
|
||||
expect(firstBatchSecretsOnInfisical).toEqual(
|
||||
expect.arrayContaining(
|
||||
firstBatchSecrets.map((el) =>
|
||||
expect.objectContaining({
|
||||
secretKey: el.secretKey,
|
||||
secretValue: "update-value",
|
||||
type: SecretType.Shared
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
const secondBatchSecretsOnInfisical = await getSecrets(seedData1.environment.slug, secretTestCases[1].path);
|
||||
expect(secondBatchSecretsOnInfisical).toEqual(
|
||||
expect.arrayContaining(
|
||||
secondBatchSecrets.map((el) =>
|
||||
expect.objectContaining({
|
||||
secretKey: el.secretKey,
|
||||
secretValue: "update-value",
|
||||
type: SecretType.Shared
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
await Promise.all(testSecrets.map((el) => deleteSecret({ path: el.secretPath, key: el.secretKey })));
|
||||
});
|
||||
|
||||
test.each(secretTestCases)("Bulk delete secrets in path $path", async ({ secret, path }) => {
|
||||
await Promise.all(
|
||||
Array.from(Array(5)).map((_e, i) => createSecret({ ...secret, key: `BULK-${secret.key}-${i + 1}`, path }))
|
||||
|
@@ -352,6 +352,7 @@ interface CreateSecretBatchEvent {
|
||||
secrets: Array<{
|
||||
secretId: string;
|
||||
secretKey: string;
|
||||
secretPath?: string;
|
||||
secretVersion: number;
|
||||
secretMetadata?: TSecretMetadata;
|
||||
}>;
|
||||
@@ -374,8 +375,14 @@ interface UpdateSecretBatchEvent {
|
||||
type: EventType.UPDATE_SECRETS;
|
||||
metadata: {
|
||||
environment: string;
|
||||
secretPath: string;
|
||||
secrets: Array<{ secretId: string; secretKey: string; secretVersion: number; secretMetadata?: TSecretMetadata }>;
|
||||
secretPath?: string;
|
||||
secrets: Array<{
|
||||
secretId: string;
|
||||
secretKey: string;
|
||||
secretVersion: number;
|
||||
secretMetadata?: TSecretMetadata;
|
||||
secretPath?: string;
|
||||
}>;
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -721,7 +721,8 @@ export const RAW_SECRETS = {
|
||||
secretName: "The name of the secret to update.",
|
||||
secretComment: "Update comment to the secret.",
|
||||
environment: "The slug of the environment where the secret is located.",
|
||||
secretPath: "The path of the secret to update.",
|
||||
mode: "Defines how the system should handle missing secrets during an update.",
|
||||
secretPath: "The default path for secrets to update or upsert, if not provided in the secret details.",
|
||||
secretValue: "The new value of the secret.",
|
||||
skipMultilineEncoding: "Skip multiline encoding for the secret value.",
|
||||
type: "The type of the secret to update.",
|
||||
|
@@ -20,6 +20,7 @@ import { ActorType, AuthMode } from "@app/services/auth/auth-type";
|
||||
import { ProjectFilterType } from "@app/services/project/project-types";
|
||||
import { ResourceMetadataSchema } from "@app/services/resource-metadata/resource-metadata-schema";
|
||||
import { SecretOperations, SecretProtectionType } from "@app/services/secret/secret-types";
|
||||
import { SecretUpdateMode } from "@app/services/secret-v2-bridge/secret-v2-bridge-types";
|
||||
import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";
|
||||
|
||||
import { secretRawSchema } from "../sanitizedSchemas";
|
||||
@@ -2030,6 +2031,11 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
.default("/")
|
||||
.transform(removeTrailingSlash)
|
||||
.describe(RAW_SECRETS.UPDATE.secretPath),
|
||||
mode: z
|
||||
.nativeEnum(SecretUpdateMode)
|
||||
.optional()
|
||||
.default(SecretUpdateMode.FailOnNotFound)
|
||||
.describe(RAW_SECRETS.UPDATE.mode),
|
||||
secrets: z
|
||||
.object({
|
||||
secretKey: SecretNameSchema.describe(RAW_SECRETS.UPDATE.secretName),
|
||||
@@ -2037,6 +2043,12 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
.string()
|
||||
.transform((val) => (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim()))
|
||||
.describe(RAW_SECRETS.UPDATE.secretValue),
|
||||
secretPath: z
|
||||
.string()
|
||||
.trim()
|
||||
.transform(removeTrailingSlash)
|
||||
.optional()
|
||||
.describe(RAW_SECRETS.UPDATE.secretPath),
|
||||
secretComment: z.string().trim().optional().describe(RAW_SECRETS.UPDATE.secretComment),
|
||||
skipMultilineEncoding: z.boolean().optional().describe(RAW_SECRETS.UPDATE.skipMultilineEncoding),
|
||||
newSecretName: SecretNameSchema.optional().describe(RAW_SECRETS.UPDATE.newSecretName),
|
||||
@@ -2073,7 +2085,8 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
environment,
|
||||
projectSlug,
|
||||
projectId: req.body.workspaceId,
|
||||
secrets: inputSecrets
|
||||
secrets: inputSecrets,
|
||||
mode: req.body.mode
|
||||
});
|
||||
if (secretOperation.type === SecretProtectionType.Approval) {
|
||||
return { approval: secretOperation.approval };
|
||||
@@ -2092,15 +2105,39 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
metadata: {
|
||||
environment: req.body.environment,
|
||||
secretPath: req.body.secretPath,
|
||||
secrets: secrets.map((secret) => ({
|
||||
secretId: secret.id,
|
||||
secretKey: secret.secretKey,
|
||||
secretVersion: secret.version,
|
||||
secretMetadata: secretMetadataMap.get(secret.secretKey)
|
||||
}))
|
||||
secrets: secrets
|
||||
.filter((el) => el.version > 1)
|
||||
.map((secret) => ({
|
||||
secretId: secret.id,
|
||||
secretPath: secret.secretPath,
|
||||
secretKey: secret.secretKey,
|
||||
secretVersion: secret.version,
|
||||
secretMetadata: secretMetadataMap.get(secret.secretKey)
|
||||
}))
|
||||
}
|
||||
}
|
||||
});
|
||||
const createdSecrets = secrets.filter((el) => el.version === 1);
|
||||
if (createdSecrets.length) {
|
||||
await server.services.auditLog.createAuditLog({
|
||||
projectId: secrets[0].workspace,
|
||||
...req.auditLogInfo,
|
||||
event: {
|
||||
type: EventType.CREATE_SECRETS,
|
||||
metadata: {
|
||||
environment: req.body.environment,
|
||||
secretPath: req.body.secretPath,
|
||||
secrets: createdSecrets.map((secret) => ({
|
||||
secretId: secret.id,
|
||||
secretPath: secret.secretPath,
|
||||
secretKey: secret.secretKey,
|
||||
secretVersion: secret.version,
|
||||
secretMetadata: secretMetadataMap.get(secret.secretKey)
|
||||
}))
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await server.services.telemetry.sendPostHogEvents({
|
||||
event: PostHogEventTypes.SecretUpdated,
|
||||
|
@@ -1,7 +1,15 @@
|
||||
import { ForbiddenError, PureAbility, subject } from "@casl/ability";
|
||||
import { Knex } from "knex";
|
||||
import { z } from "zod";
|
||||
|
||||
import { ActionProjectType, ProjectMembershipRole, SecretsV2Schema, SecretType, TableName } from "@app/db/schemas";
|
||||
import {
|
||||
ActionProjectType,
|
||||
ProjectMembershipRole,
|
||||
SecretsV2Schema,
|
||||
SecretType,
|
||||
TableName,
|
||||
TSecretsV2
|
||||
} from "@app/db/schemas";
|
||||
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";
|
||||
@@ -36,6 +44,7 @@ import {
|
||||
} from "./secret-v2-bridge-fns";
|
||||
import {
|
||||
SecretOperations,
|
||||
SecretUpdateMode,
|
||||
TBackFillSecretReferencesDTO,
|
||||
TCreateManySecretDTO,
|
||||
TCreateSecretDTO,
|
||||
@@ -103,12 +112,13 @@ export const secretV2BridgeServiceFactory = ({
|
||||
const $validateSecretReferences = async (
|
||||
projectId: string,
|
||||
permission: PureAbility,
|
||||
references: ReturnType<typeof getAllSecretReferences>["nestedReferences"]
|
||||
references: ReturnType<typeof getAllSecretReferences>["nestedReferences"],
|
||||
tx?: Knex
|
||||
) => {
|
||||
if (!references.length) return;
|
||||
|
||||
const uniqueReferenceEnvironmentSlugs = Array.from(new Set(references.map((el) => el.environment)));
|
||||
const referencesEnvironments = await projectEnvDAL.findBySlugs(projectId, uniqueReferenceEnvironmentSlugs);
|
||||
const referencesEnvironments = await projectEnvDAL.findBySlugs(projectId, uniqueReferenceEnvironmentSlugs, tx);
|
||||
if (referencesEnvironments.length !== uniqueReferenceEnvironmentSlugs.length)
|
||||
throw new BadRequestError({
|
||||
message: `Referenced environment not found. Missing ${diff(
|
||||
@@ -122,36 +132,41 @@ export const secretV2BridgeServiceFactory = ({
|
||||
references.map((el) => ({
|
||||
secretPath: el.secretPath,
|
||||
envId: referencesEnvironmentGroupBySlug[el.environment][0].id
|
||||
}))
|
||||
})),
|
||||
tx
|
||||
);
|
||||
const referencesFolderGroupByPath = groupBy(referredFolders.filter(Boolean), (i) => `${i?.envId}-${i?.path}`);
|
||||
const referredSecrets = await secretDAL.find({
|
||||
$complex: {
|
||||
operator: "or",
|
||||
value: references.map((el) => {
|
||||
const folderId =
|
||||
referencesFolderGroupByPath[`${referencesEnvironmentGroupBySlug[el.environment][0].id}-${el.secretPath}`][0]
|
||||
?.id;
|
||||
if (!folderId) throw new BadRequestError({ message: `Referenced path ${el.secretPath} doesn't exist` });
|
||||
const referredSecrets = await secretDAL.find(
|
||||
{
|
||||
$complex: {
|
||||
operator: "or",
|
||||
value: references.map((el) => {
|
||||
const folderId =
|
||||
referencesFolderGroupByPath[
|
||||
`${referencesEnvironmentGroupBySlug[el.environment][0].id}-${el.secretPath}`
|
||||
][0]?.id;
|
||||
if (!folderId) throw new BadRequestError({ message: `Referenced path ${el.secretPath} doesn't exist` });
|
||||
|
||||
return {
|
||||
operator: "and",
|
||||
value: [
|
||||
{
|
||||
operator: "eq",
|
||||
field: "folderId",
|
||||
value: folderId
|
||||
},
|
||||
{
|
||||
operator: "eq",
|
||||
field: `${TableName.SecretV2}.key` as "key",
|
||||
value: el.secretKey
|
||||
}
|
||||
]
|
||||
};
|
||||
})
|
||||
}
|
||||
});
|
||||
return {
|
||||
operator: "and",
|
||||
value: [
|
||||
{
|
||||
operator: "eq",
|
||||
field: "folderId",
|
||||
value: folderId
|
||||
},
|
||||
{
|
||||
operator: "eq",
|
||||
field: `${TableName.SecretV2}.key` as "key",
|
||||
value: el.secretKey
|
||||
}
|
||||
]
|
||||
};
|
||||
})
|
||||
}
|
||||
},
|
||||
{ tx }
|
||||
);
|
||||
|
||||
if (
|
||||
referredSecrets.length !==
|
||||
@@ -1245,8 +1260,9 @@ export const secretV2BridgeServiceFactory = ({
|
||||
actorAuthMethod,
|
||||
environment,
|
||||
projectId,
|
||||
secretPath,
|
||||
secrets: inputSecrets
|
||||
secretPath: defaultSecretPath = "/",
|
||||
secrets: inputSecrets,
|
||||
mode: updateMode
|
||||
}: TUpdateManySecretDTO) => {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor,
|
||||
@@ -1257,196 +1273,280 @@ export const secretV2BridgeServiceFactory = ({
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
|
||||
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
|
||||
if (!folder)
|
||||
const secretsToUpdateGroupByPath = groupBy(inputSecrets, (el) => el.secretPath || defaultSecretPath);
|
||||
const projectEnvironment = await projectEnvDAL.findOne({ projectId, slug: environment });
|
||||
if (!projectEnvironment) {
|
||||
throw new NotFoundError({
|
||||
message: `Folder with path '${secretPath}' in environment with slug '${environment}' not found`,
|
||||
message: `Environment with slug '${environment}' in project with ID '${projectId}' not found`
|
||||
});
|
||||
}
|
||||
|
||||
const folders = await folderDAL.findByManySecretPath(
|
||||
Object.keys(secretsToUpdateGroupByPath).map((el) => ({ envId: projectEnvironment.id, secretPath: el }))
|
||||
);
|
||||
if (folders.length !== Object.keys(secretsToUpdateGroupByPath).length)
|
||||
throw new NotFoundError({
|
||||
message: `Folder with path '${null}' in environment with slug '${environment}' not found`,
|
||||
name: "UpdateManySecret"
|
||||
});
|
||||
const folderId = folder.id;
|
||||
|
||||
const secretsToUpdate = await secretDAL.find({
|
||||
folderId,
|
||||
$complex: {
|
||||
operator: "and",
|
||||
value: [
|
||||
{
|
||||
operator: "or",
|
||||
value: inputSecrets.map((el) => ({
|
||||
operator: "and",
|
||||
value: [
|
||||
{
|
||||
operator: "eq",
|
||||
field: `${TableName.SecretV2}.key` as "key",
|
||||
value: el.secretKey
|
||||
},
|
||||
{
|
||||
operator: "eq",
|
||||
field: "type",
|
||||
value: SecretType.Shared
|
||||
}
|
||||
]
|
||||
}))
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
if (secretsToUpdate.length !== inputSecrets.length) {
|
||||
const secretsToUpdateNames = secretsToUpdate.map((secret) => secret.key);
|
||||
const invalidSecrets = inputSecrets.filter((secret) => !secretsToUpdateNames.includes(secret.secretKey));
|
||||
throw new NotFoundError({
|
||||
message: `Secret does not exist: ${invalidSecrets.map((el) => el.secretKey).join(",")}`
|
||||
});
|
||||
}
|
||||
const secretsToUpdateInDBGroupedByKey = groupBy(secretsToUpdate, (i) => i.key);
|
||||
|
||||
secretsToUpdate.forEach((el) => {
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Edit,
|
||||
subject(ProjectPermissionSub.Secrets, {
|
||||
environment,
|
||||
secretPath,
|
||||
secretName: el.key,
|
||||
secretTags: el.tags.map((i) => i.slug)
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// get all tags
|
||||
const sanitizedTagIds = inputSecrets.flatMap(({ tagIds = [] }) => tagIds);
|
||||
const tags = sanitizedTagIds.length ? await secretTagDAL.findManyTagsById(projectId, sanitizedTagIds) : [];
|
||||
if (tags.length !== sanitizedTagIds.length) throw new NotFoundError({ message: "Tag not found" });
|
||||
const tagsGroupByID = groupBy(tags, (i) => i.id);
|
||||
|
||||
// check again to avoid non authorized tags are removed
|
||||
inputSecrets.forEach((el) => {
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Edit,
|
||||
subject(ProjectPermissionSub.Secrets, {
|
||||
environment,
|
||||
secretPath,
|
||||
secretName: el.secretKey,
|
||||
secretTags: (el.tagIds || []).map((i) => tagsGroupByID[i][0].slug)
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// now find any secret that needs to update its name
|
||||
// same process as above
|
||||
const secretsWithNewName = inputSecrets.filter(({ newSecretName }) => Boolean(newSecretName));
|
||||
if (secretsWithNewName.length) {
|
||||
const secrets = await secretDAL.find({
|
||||
folderId,
|
||||
$complex: {
|
||||
operator: "and",
|
||||
value: [
|
||||
{
|
||||
operator: "or",
|
||||
value: secretsWithNewName.map((el) => ({
|
||||
operator: "and",
|
||||
value: [
|
||||
{
|
||||
operator: "eq",
|
||||
field: `${TableName.SecretV2}.key` as "key",
|
||||
value: el.secretKey
|
||||
},
|
||||
{
|
||||
operator: "eq",
|
||||
field: "type",
|
||||
value: SecretType.Shared
|
||||
}
|
||||
]
|
||||
}))
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
if (secrets.length)
|
||||
throw new BadRequestError({
|
||||
message: `Secret with new name already exists: ${secretsWithNewName.map((el) => el.newSecretName).join(",")}`
|
||||
});
|
||||
|
||||
secretsWithNewName.forEach((el) => {
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Create,
|
||||
subject(ProjectPermissionSub.Secrets, {
|
||||
environment,
|
||||
secretPath,
|
||||
secretName: el.newSecretName as string,
|
||||
secretTags: (el.tagIds || []).map((i) => tagsGroupByID[i][0].slug)
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
// now get all secret references made and validate the permission
|
||||
const secretReferencesGroupByInputSecretKey: Record<string, ReturnType<typeof getAllSecretReferences>> = {};
|
||||
const secretReferences: TSecretReference[] = [];
|
||||
inputSecrets.forEach((el) => {
|
||||
if (el.secretValue) {
|
||||
const references = getAllSecretReferences(el.secretValue);
|
||||
secretReferencesGroupByInputSecretKey[el.secretKey] = references;
|
||||
secretReferences.push(...references.nestedReferences);
|
||||
references.localReferences.forEach((localRefKey) => {
|
||||
secretReferences.push({ secretKey: localRefKey, secretPath, environment });
|
||||
});
|
||||
}
|
||||
});
|
||||
await $validateSecretReferences(projectId, permission, secretReferences);
|
||||
|
||||
const { encryptor: secretManagerEncryptor, decryptor: secretManagerDecryptor } =
|
||||
await kmsService.createCipherPairWithDataKey({ type: KmsDataKey.SecretManager, projectId });
|
||||
|
||||
const secrets = await secretDAL.transaction(async (tx) =>
|
||||
fnSecretBulkUpdate({
|
||||
folderId,
|
||||
orgId: actorOrgId,
|
||||
tx,
|
||||
inputSecrets: inputSecrets.map((el) => {
|
||||
const originalSecret = secretsToUpdateInDBGroupedByKey[el.secretKey][0];
|
||||
const encryptedValue =
|
||||
typeof el.secretValue !== "undefined"
|
||||
? {
|
||||
encryptedValue: secretManagerEncryptor({ plainText: Buffer.from(el.secretValue) }).cipherTextBlob,
|
||||
references: secretReferencesGroupByInputSecretKey[el.secretKey]?.nestedReferences
|
||||
}
|
||||
: {};
|
||||
const updatedSecrets: Array<TSecretsV2 & { secretPath: string }> = [];
|
||||
await secretDAL.transaction(async (tx) => {
|
||||
for await (const folder of folders) {
|
||||
if (!folder) throw new NotFoundError({ message: "Folder not found" });
|
||||
|
||||
return {
|
||||
filter: { id: originalSecret.id, type: SecretType.Shared },
|
||||
data: {
|
||||
reminderRepeatDays: el.secretReminderRepeatDays,
|
||||
encryptedComment: setKnexStringValue(
|
||||
el.secretComment,
|
||||
(value) => secretManagerEncryptor({ plainText: Buffer.from(value) }).cipherTextBlob
|
||||
),
|
||||
reminderNote: el.secretReminderNote,
|
||||
skipMultilineEncoding: el.skipMultilineEncoding,
|
||||
key: el.newSecretName || el.secretKey,
|
||||
tags: el.tagIds,
|
||||
secretMetadata: el.secretMetadata,
|
||||
...encryptedValue
|
||||
const folderId = folder.id;
|
||||
const secretPath = folder.path;
|
||||
let secretsToUpdate = secretsToUpdateGroupByPath[secretPath];
|
||||
const secretsToUpdateInDB = await secretDAL.find(
|
||||
{
|
||||
folderId,
|
||||
$complex: {
|
||||
operator: "and",
|
||||
value: [
|
||||
{
|
||||
operator: "or",
|
||||
value: secretsToUpdate.map((el) => ({
|
||||
operator: "and",
|
||||
value: [
|
||||
{
|
||||
operator: "eq",
|
||||
field: `${TableName.SecretV2}.key` as "key",
|
||||
value: el.secretKey
|
||||
},
|
||||
{
|
||||
operator: "eq",
|
||||
field: "type",
|
||||
value: SecretType.Shared
|
||||
}
|
||||
]
|
||||
}))
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
}),
|
||||
secretDAL,
|
||||
secretVersionDAL,
|
||||
secretTagDAL,
|
||||
secretVersionTagDAL,
|
||||
resourceMetadataDAL
|
||||
})
|
||||
);
|
||||
await snapshotService.performSnapshot(folderId);
|
||||
await secretQueueService.syncSecrets({
|
||||
actor,
|
||||
actorId,
|
||||
secretPath,
|
||||
projectId,
|
||||
orgId: actorOrgId,
|
||||
environmentSlug: folder.environment.slug
|
||||
},
|
||||
{ tx }
|
||||
);
|
||||
if (secretsToUpdateInDB.length !== secretsToUpdate.length && updateMode === SecretUpdateMode.FailOnNotFound)
|
||||
throw new NotFoundError({
|
||||
message: `Secret does not exist: ${diff(
|
||||
secretsToUpdate.map((el) => el.secretKey),
|
||||
secretsToUpdateInDB.map((el) => el.key)
|
||||
).join(", ")} in path ${folder.path}`
|
||||
});
|
||||
|
||||
const secretsToUpdateInDBGroupedByKey = groupBy(secretsToUpdateInDB, (i) => i.key);
|
||||
const secretsToCreate = secretsToUpdate.filter((el) => !secretsToUpdateInDBGroupedByKey?.[el.secretKey]);
|
||||
secretsToUpdate = secretsToUpdate.filter((el) => secretsToUpdateInDBGroupedByKey?.[el.secretKey]);
|
||||
|
||||
secretsToUpdateInDB.forEach((el) => {
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Edit,
|
||||
subject(ProjectPermissionSub.Secrets, {
|
||||
environment,
|
||||
secretPath,
|
||||
secretName: el.key,
|
||||
secretTags: el.tags.map((i) => i.slug)
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// get all tags
|
||||
const sanitizedTagIds = secretsToUpdate.flatMap(({ tagIds = [] }) => tagIds);
|
||||
const tags = sanitizedTagIds.length ? await secretTagDAL.findManyTagsById(projectId, sanitizedTagIds, tx) : [];
|
||||
if (tags.length !== sanitizedTagIds.length) throw new NotFoundError({ message: "Tag not found" });
|
||||
const tagsGroupByID = groupBy(tags, (i) => i.id);
|
||||
|
||||
// check create permission allowed in upsert mode
|
||||
if (updateMode === SecretUpdateMode.Upsert) {
|
||||
secretsToCreate.forEach((el) => {
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Create,
|
||||
subject(ProjectPermissionSub.Secrets, {
|
||||
environment,
|
||||
secretPath,
|
||||
secretName: el.secretKey,
|
||||
secretTags: (el.tagIds || []).map((i) => tagsGroupByID[i][0].slug)
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// check again to avoid non authorized tags are removed
|
||||
secretsToUpdate.forEach((el) => {
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Edit,
|
||||
subject(ProjectPermissionSub.Secrets, {
|
||||
environment,
|
||||
secretPath,
|
||||
secretName: el.secretKey,
|
||||
secretTags: (el.tagIds || []).map((i) => tagsGroupByID[i][0].slug)
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// now find any secret that needs to update its name
|
||||
// same process as above
|
||||
const secretsWithNewName = secretsToUpdate.filter(({ newSecretName }) => Boolean(newSecretName));
|
||||
if (secretsWithNewName.length) {
|
||||
const secrets = await secretDAL.find(
|
||||
{
|
||||
folderId,
|
||||
$complex: {
|
||||
operator: "and",
|
||||
value: [
|
||||
{
|
||||
operator: "or",
|
||||
value: secretsWithNewName.map((el) => ({
|
||||
operator: "and",
|
||||
value: [
|
||||
{
|
||||
operator: "eq",
|
||||
field: `${TableName.SecretV2}.key` as "key",
|
||||
value: el.secretKey
|
||||
},
|
||||
{
|
||||
operator: "eq",
|
||||
field: "type",
|
||||
value: SecretType.Shared
|
||||
}
|
||||
]
|
||||
}))
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{ tx }
|
||||
);
|
||||
if (secrets.length)
|
||||
throw new BadRequestError({
|
||||
message: `Secret with new name already exists: ${secretsWithNewName
|
||||
.map((el) => el.newSecretName)
|
||||
.join(", ")}`
|
||||
});
|
||||
|
||||
secretsWithNewName.forEach((el) => {
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Create,
|
||||
subject(ProjectPermissionSub.Secrets, {
|
||||
environment,
|
||||
secretPath,
|
||||
secretName: el.newSecretName as string,
|
||||
secretTags: (el.tagIds || []).map((i) => tagsGroupByID[i][0].slug)
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
// now get all secret references made and validate the permission
|
||||
const secretReferencesGroupByInputSecretKey: Record<string, ReturnType<typeof getAllSecretReferences>> = {};
|
||||
const secretReferences: TSecretReference[] = [];
|
||||
secretsToUpdate.concat(SecretUpdateMode.Upsert === updateMode ? secretsToCreate : []).forEach((el) => {
|
||||
if (el.secretValue) {
|
||||
const references = getAllSecretReferences(el.secretValue);
|
||||
secretReferencesGroupByInputSecretKey[el.secretKey] = references;
|
||||
secretReferences.push(...references.nestedReferences);
|
||||
references.localReferences.forEach((localRefKey) => {
|
||||
secretReferences.push({ secretKey: localRefKey, secretPath, environment });
|
||||
});
|
||||
}
|
||||
});
|
||||
await $validateSecretReferences(projectId, permission, secretReferences, tx);
|
||||
|
||||
const bulkUpdatedSecrets = await fnSecretBulkUpdate({
|
||||
folderId,
|
||||
orgId: actorOrgId,
|
||||
tx,
|
||||
inputSecrets: secretsToUpdate.map((el) => {
|
||||
const originalSecret = secretsToUpdateInDBGroupedByKey[el.secretKey][0];
|
||||
const encryptedValue =
|
||||
typeof el.secretValue !== "undefined"
|
||||
? {
|
||||
encryptedValue: secretManagerEncryptor({ plainText: Buffer.from(el.secretValue) }).cipherTextBlob,
|
||||
references: secretReferencesGroupByInputSecretKey[el.secretKey]?.nestedReferences
|
||||
}
|
||||
: {};
|
||||
|
||||
return {
|
||||
filter: { id: originalSecret.id, type: SecretType.Shared },
|
||||
data: {
|
||||
reminderRepeatDays: el.secretReminderRepeatDays,
|
||||
encryptedComment: setKnexStringValue(
|
||||
el.secretComment,
|
||||
(value) => secretManagerEncryptor({ plainText: Buffer.from(value) }).cipherTextBlob
|
||||
),
|
||||
reminderNote: el.secretReminderNote,
|
||||
skipMultilineEncoding: el.skipMultilineEncoding,
|
||||
key: el.newSecretName || el.secretKey,
|
||||
tags: el.tagIds,
|
||||
secretMetadata: el.secretMetadata,
|
||||
...encryptedValue
|
||||
}
|
||||
};
|
||||
}),
|
||||
secretDAL,
|
||||
secretVersionDAL,
|
||||
secretTagDAL,
|
||||
secretVersionTagDAL,
|
||||
resourceMetadataDAL
|
||||
});
|
||||
updatedSecrets.push(...bulkUpdatedSecrets.map((el) => ({ ...el, secretPath: folder.path })));
|
||||
if (updateMode === SecretUpdateMode.Upsert) {
|
||||
const bulkInsertedSecrets = await fnSecretBulkInsert({
|
||||
inputSecrets: secretsToCreate.map((el) => {
|
||||
const references = secretReferencesGroupByInputSecretKey[el.secretKey]?.nestedReferences;
|
||||
|
||||
return {
|
||||
version: 1,
|
||||
encryptedComment: setKnexStringValue(
|
||||
el.secretComment,
|
||||
(value) => secretManagerEncryptor({ plainText: Buffer.from(value) }).cipherTextBlob
|
||||
),
|
||||
encryptedValue: el.secretValue
|
||||
? secretManagerEncryptor({ plainText: Buffer.from(el.secretValue) }).cipherTextBlob
|
||||
: undefined,
|
||||
skipMultilineEncoding: el.skipMultilineEncoding,
|
||||
key: el.secretKey,
|
||||
tagIds: el.tagIds,
|
||||
references,
|
||||
secretMetadata: el.secretMetadata,
|
||||
type: SecretType.Shared
|
||||
};
|
||||
}),
|
||||
folderId,
|
||||
orgId: actorOrgId,
|
||||
secretDAL,
|
||||
resourceMetadataDAL,
|
||||
secretVersionDAL,
|
||||
secretTagDAL,
|
||||
secretVersionTagDAL,
|
||||
tx
|
||||
});
|
||||
updatedSecrets.push(...bulkInsertedSecrets.map((el) => ({ ...el, secretPath: folder.path })));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return secrets.map((el) =>
|
||||
reshapeBridgeSecret(projectId, environment, secretPath, {
|
||||
await Promise.allSettled(folders.map((el) => (el?.id ? snapshotService.performSnapshot(el.id) : undefined)));
|
||||
await Promise.allSettled(
|
||||
folders.map((el) =>
|
||||
el
|
||||
? secretQueueService.syncSecrets({
|
||||
actor,
|
||||
actorId,
|
||||
secretPath: el.path,
|
||||
projectId,
|
||||
orgId: actorOrgId,
|
||||
environmentSlug: environment
|
||||
})
|
||||
: undefined
|
||||
)
|
||||
);
|
||||
|
||||
return updatedSecrets.map((el) =>
|
||||
reshapeBridgeSecret(projectId, environment, el.secretPath, {
|
||||
...el,
|
||||
value: el.encryptedValue ? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString() : "",
|
||||
comment: el.encryptedComment ? secretManagerDecryptor({ cipherTextBlob: el.encryptedComment }).toString() : ""
|
||||
|
@@ -23,6 +23,12 @@ export type TSecretReferenceDTO = {
|
||||
secretKey: string;
|
||||
};
|
||||
|
||||
export enum SecretUpdateMode {
|
||||
Ignore = "ignore",
|
||||
Upsert = "upsert",
|
||||
FailOnNotFound = "failOnNotFound"
|
||||
}
|
||||
|
||||
export type TGetSecretsDTO = {
|
||||
expandSecretReferences?: boolean;
|
||||
path: string;
|
||||
@@ -113,6 +119,7 @@ export type TUpdateManySecretDTO = Omit<TProjectPermission, "projectId"> & {
|
||||
secretPath: string;
|
||||
projectId: string;
|
||||
environment: string;
|
||||
mode: SecretUpdateMode;
|
||||
secrets: {
|
||||
secretKey: string;
|
||||
newSecretName?: string;
|
||||
@@ -123,6 +130,7 @@ export type TUpdateManySecretDTO = Omit<TProjectPermission, "projectId"> & {
|
||||
secretReminderRepeatDays?: number | null;
|
||||
secretReminderNote?: string | null;
|
||||
secretMetadata?: ResourceMetadataDTO;
|
||||
secretPath?: string;
|
||||
}[];
|
||||
};
|
||||
|
||||
|
@@ -30,7 +30,10 @@ import { groupBy, pick } from "@app/lib/fn";
|
||||
import { logger } from "@app/lib/logger";
|
||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||
import { OrgServiceActor } from "@app/lib/types";
|
||||
import { TGetSecretsRawByFolderMappingsDTO } from "@app/services/secret-v2-bridge/secret-v2-bridge-types";
|
||||
import {
|
||||
SecretUpdateMode,
|
||||
TGetSecretsRawByFolderMappingsDTO
|
||||
} from "@app/services/secret-v2-bridge/secret-v2-bridge-types";
|
||||
|
||||
import { ActorType } from "../auth/auth-type";
|
||||
import { TProjectDALFactory } from "../project/project-dal";
|
||||
@@ -2012,6 +2015,7 @@ export const secretServiceFactory = ({
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
secretPath,
|
||||
mode = SecretUpdateMode.FailOnNotFound,
|
||||
secrets: inputSecrets = []
|
||||
}: TUpdateManySecretRawDTO) => {
|
||||
if (!projectSlug && !optionalProjectId)
|
||||
@@ -2076,7 +2080,8 @@ export const secretServiceFactory = ({
|
||||
actorOrgId,
|
||||
actor,
|
||||
actorId,
|
||||
secrets: inputSecrets
|
||||
secrets: inputSecrets,
|
||||
mode
|
||||
});
|
||||
return { type: SecretProtectionType.Direct as const, secrets };
|
||||
}
|
||||
|
@@ -17,6 +17,7 @@ import { TKmsServiceFactory } from "../kms/kms-service";
|
||||
import { TResourceMetadataDALFactory } from "../resource-metadata/resource-metadata-dal";
|
||||
import { ResourceMetadataDTO } from "../resource-metadata/resource-metadata-schema";
|
||||
import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal";
|
||||
import { SecretUpdateMode } from "../secret-v2-bridge/secret-v2-bridge-types";
|
||||
import { TSecretVersionV2DALFactory } from "../secret-v2-bridge/secret-version-dal";
|
||||
import { TSecretVersionV2TagDALFactory } from "../secret-v2-bridge/secret-version-tag-dal";
|
||||
|
||||
@@ -274,6 +275,7 @@ export type TUpdateManySecretRawDTO = Omit<TProjectPermission, "projectId"> & {
|
||||
projectId?: string;
|
||||
projectSlug?: string;
|
||||
environment: string;
|
||||
mode: SecretUpdateMode;
|
||||
secrets: {
|
||||
secretKey: string;
|
||||
newSecretName?: string;
|
||||
|
@@ -0,0 +1,87 @@
|
||||
---
|
||||
title: "Terraform Cloud"
|
||||
description: "How to authenticate with Infisical from Terraform Cloud using OIDC."
|
||||
---
|
||||
|
||||
This guide will walk you through setting up Terraform Cloud to inject a [workload identity token](https://developer.hashicorp.com/terraform/cloud-docs/workspaces/dynamic-provider-credentials/workload-identity-tokens) and use it for OIDC-based authentication with the Infisical Terraform provider. You'll start by creating a machine identity in Infisical, then configure Terraform Cloud to pass the injected token into your Terraform runs.
|
||||
|
||||
<Steps>
|
||||
<Step title="Create a Machine Identity in Infisical">
|
||||
Follow the instructions [in this documentation](/documentation/platform/identities/oidc-auth/general) to create a machine identity with OIDC auth. Infisical OIDC configuration values for Terraform Cloud:
|
||||
1. Set the OIDC Discovery URL to https://app.terraform.io.
|
||||
2. Set the Issuer to https://app.terraform.io.
|
||||
3. Configure the Audience to match the value you will use for **TFC_WORKLOAD_IDENTITY_AUDIENCE** in Terraform Cloud for the next step.
|
||||
|
||||
|
||||
To view all possible claims available from Terraform cloud, visit [HashiCorp’s documentation](https://developer.hashicorp.com/terraform/cloud-docs/workspaces/dynamic-provider-credentials/workload-identity-tokens#token-structure).
|
||||
|
||||
</Step>
|
||||
<Step title="Enable Workload Identity Token Injection in Terraform Cloud">
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Generate single token">
|
||||
1. **Navigate to your workspace** in Terraform Cloud.
|
||||
2. **Add a workspace variable** named `TFC_WORKLOAD_IDENTITY_AUDIENCE`:
|
||||
- **Key**: `TFC_WORKLOAD_IDENTITY_AUDIENCE`
|
||||
- **Value**: For example, `my-infisical-audience`
|
||||
- **Category**: Environment
|
||||
|
||||
> **Important**:
|
||||
> - The presence of `TFC_WORKLOAD_IDENTITY_AUDIENCE` is required for Terraform Cloud to inject a token.
|
||||
> - If you are self-hosting HCP Terraform agents, ensure they are **v1.7.0 or above**.
|
||||
|
||||
Once set, Terraform Cloud will inject a workload identity token into the run environment as `TFC_WORKLOAD_IDENTITY_TOKEN`.
|
||||
</Tab>
|
||||
<Tab title="(Optional) Generate Multiple Tokens">
|
||||
If you need multiple tokens (each with a different audience), create additional variables:
|
||||
|
||||
```
|
||||
TFC_WORKLOAD_IDENTITY_AUDIENCE_[YOUR_TAG_HERE]
|
||||
```
|
||||
|
||||
For example:
|
||||
- `TFC_WORKLOAD_IDENTITY_AUDIENCE_INFISICAL`
|
||||
- `TFC_WORKLOAD_IDENTITY_AUDIENCE_OTHER_SERVICE`
|
||||
|
||||
Terraform Cloud will then inject:
|
||||
- `TFC_WORKLOAD_IDENTITY_TOKEN_INFISICAL`
|
||||
- `TFC_WORKLOAD_IDENTITY_TOKEN_OTHER_SERVICE`
|
||||
|
||||
> **Note**:
|
||||
> - The `[YOUR_TAG_HERE]` can only contain letters, numbers, and underscores.
|
||||
> - You **cannot** use the reserved keyword `TYPE`.
|
||||
> - Generating multiple tokens requires **v1.12.0 or later** if you are self-hosting agents.
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
<Warning>
|
||||
If you are running on self-hosted HCP Terraform agents, you must use v1.7.0 or later to enable token injection. If you need to generate multiple tokens, you must use v1.12.0 or later.
|
||||
</Warning>
|
||||
</Step>
|
||||
<Step title="Configure the Infisical Provider">
|
||||
In your Terraform configuration, reference the injected token by name. For example:
|
||||
|
||||
```hcl
|
||||
provider "infisical" {
|
||||
host = "https://app.infisical.com"
|
||||
|
||||
auth = {
|
||||
oidc = {
|
||||
identity_id = "<identity-id>"
|
||||
# This must match the environment variable Terraform injects:
|
||||
token_environment_variable_name = "TFC_WORKLOAD_IDENTITY_TOKEN"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- **`host`**: Defaults to `https://app.infisical.com`. Override if using a self-hosted Infisical instance.
|
||||
- **`identity_id`**: The OIDC identity ID from Infisical.
|
||||
- **`token_environment_variable_name`**: Must match the injected variable name from Terraform Cloud. If using single token, use `TFC_WORKLOAD_IDENTITY_TOKEN`. If using multiple tokens, choose the one you want to use (e.g., `TFC_WORKLOAD_IDENTITY_TOKEN_INFISICAL`).
|
||||
</Step>
|
||||
<Step title="Validate Your Setup">
|
||||
1. Run a plan and apply in Terraform Cloud.
|
||||
2. Verify the Infisical provider authenticates successfully without issues. If you run into authentication errors, double-check the Infisical identity has the correct roles/permissions in Infisical.
|
||||
|
||||
</Step>
|
||||
</Steps>
|
36
docs/documentation/setup/networking.mdx
Normal file
36
docs/documentation/setup/networking.mdx
Normal file
@@ -0,0 +1,36 @@
|
||||
---
|
||||
title: "Networking"
|
||||
sidebarTitle: "Networking"
|
||||
description: "Network configuration details for Infisical Cloud"
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
When integrating your infrastructure with Infisical Cloud, you may need to configure network access controls. This page provides the IP addresses that Infisical uses to communicate with your services.
|
||||
|
||||
## Egress IP Addresses
|
||||
|
||||
Infisical Cloud operates from two regions: US and EU. If your infrastructure has strict network policies, you may need to allow traffic from Infisical by adding the following IP addresses to your ingress rules. These are the egress IPs Infisical uses when making outbound requests to your services.
|
||||
|
||||
### US Region
|
||||
|
||||
To allow connections from Infisical US, add these IP addresses to your ingress rules:
|
||||
|
||||
- `3.213.63.16`
|
||||
- `54.164.68.7`
|
||||
|
||||
### EU Region
|
||||
|
||||
To allow connections from Infisical EU, add these IP addresses to your ingress rules:
|
||||
|
||||
- `3.77.89.19`
|
||||
- `3.125.209.189`
|
||||
|
||||
## Common Use Cases
|
||||
|
||||
You may need to allow Infisical’s egress IPs if your services require inbound connections for:
|
||||
|
||||
- Secret rotation - When Infisical needs to send requests to your systems to automatically rotate credentials
|
||||
- Dynamic secrets - When Infisical generates and manages temporary credentials for your cloud services
|
||||
- Secret integrations - When syncing secrets with third-party services like Azure Key Vault
|
||||
- Native authentication with machine identities - When using methods like Kubernetes authentication
|
@@ -1,102 +1,237 @@
|
||||
---
|
||||
title: "Terraform Provider"
|
||||
description: "Learn how to fetch Secrets From Infisical With Terraform."
|
||||
url: "https://registry.terraform.io/providers/Infisical/infisical/latest/docs"
|
||||
title: "Terraform"
|
||||
description: "Learn how to fetch secrets from Infisical with Terraform using both traditional data sources and ephemeral resources"
|
||||
---
|
||||
{/*
|
||||
This guide provides step-by-step guidance on how to fetch secrets from Infisical using Terraform.
|
||||
|
||||
This guide demonstrates how to use Infisical to manage secrets in your Terraform infrastructure code, supporting both traditional data sources and ephemeral resources for enhanced security. It uses:
|
||||
|
||||
- Infisical (you can use [Infisical Cloud](https://app.infisical.com) or a [self-hosted instance of Infisical](https://infisical.com/docs/self-hosting/overview)) to store your secrets
|
||||
- The [Terraform Provider](https://registry.terraform.io/providers/Infisical/infisical/latest) to fetch secrets for your infrastructure
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Basic understanding of Terraform
|
||||
- Install [Terraform](https://www.terraform.io/downloads.html)
|
||||
Before you begin, make sure you have:
|
||||
|
||||
## Steps
|
||||
- [Terraform](https://www.terraform.io/downloads.html) installed (v1.10.0+ for ephemeral resources)
|
||||
- An Infisical account with access to a project
|
||||
- Basic understanding of Terraform and infrastructure as code
|
||||
|
||||
### 1. Define Required Providers
|
||||
## Project Setup
|
||||
|
||||
Specify `infisical` in the `required_providers` block within the `terraform` block of your configuration file. If you would like to use a specific version of the provider, uncomment and replace `<latest version>` with the version of the Infisical provider that you want to use.
|
||||
### Configure Provider
|
||||
|
||||
```hcl main.tf
|
||||
First, specify the Infisical provider in your Terraform configuration:
|
||||
|
||||
```hcl
|
||||
terraform {
|
||||
required_providers {
|
||||
infisical = {
|
||||
# version = <latest version>
|
||||
source = "infisical/infisical"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Configure the Infisical Provider
|
||||
### Authentication
|
||||
|
||||
Set up the Infisical provider by specifying the `host` and `service_token`. Replace `<>` in `service_token` with your actual token. The `host` is only required if you are using a self-hosted instance of Infisical.
|
||||
Configure the provider using one of these authentication methods:
|
||||
|
||||
```hcl main.tf
|
||||
#### Machine Identity (Recommended)
|
||||
|
||||
Using a Machine Identity, you can authenticate your Terraform provider using either [OIDC Auth](https://infisical.com/docs/documentation/platform/identities/oidc-auth/general) or [Universal Auth](https://infisical.com/docs/documentation/platform/identities/universal-auth) methods.
|
||||
|
||||
```hcl
|
||||
provider "infisical" {
|
||||
host = "https://app.infisical.com" # Only required if using a self-hosted instance of Infisical, default is https://app.infisical.com
|
||||
client_id = "<>"
|
||||
client_secret = "<>"
|
||||
service_token = "<>" # DEPRECATED, USE MACHINE IDENTITY AUTH INSTEAD
|
||||
host = "https://app.infisical.com" # Optional for cloud, required for self-hosted
|
||||
auth {
|
||||
universal { # or use oidc authentication method by providing an identity_id
|
||||
client_id = var.infisical_client_id
|
||||
client_secret = var.infisical_client_secret
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
Learn more about [machine identities](/documentation/platform/identities/machine-identities).
|
||||
|
||||
#### Service Token (Legacy)
|
||||
|
||||
<Warning>
|
||||
Machine Identity authentication is strongly recommended as the secure and modern method. Service tokens are considered legacy and will be deprecated in a future release.
|
||||
</Warning>
|
||||
|
||||
```hcl
|
||||
provider "infisical" {
|
||||
host = "https://app.infisical.com"
|
||||
service_token = var.infisical_service_token
|
||||
}
|
||||
```
|
||||
|
||||
## Using Secrets in Terraform
|
||||
|
||||
Infisical provides two methods to fetch and use secrets in your Terraform configurations:
|
||||
|
||||
### Method 1: Ephemeral Resources (Recommended)
|
||||
|
||||
Ephemeral resources, introduced in Terraform v1.10, provide enhanced security by ensuring sensitive values are never persisted in state files. This is the recommended approach for handling secrets in your infrastructure code.
|
||||
|
||||
```hcl
|
||||
# Fetch database credentials ephemerally
|
||||
ephemeral "infisical_secret" "db_creds" {
|
||||
name = "DB_CREDENTIALS"
|
||||
env_slug = "prod"
|
||||
workspace_id = var.infisical_workspace_id
|
||||
folder_path = "/database"
|
||||
}
|
||||
|
||||
# Use the credentials to configure a provider
|
||||
provider "postgresql" {
|
||||
host = data.aws_db_instance.example.address
|
||||
port = data.aws_db_instance.example.port
|
||||
username = jsondecode(ephemeral.infisical_secret.db_creds.value)["username"]
|
||||
password = jsondecode(ephemeral.infisical_secret.db_creds.value)["password"]
|
||||
}
|
||||
```
|
||||
|
||||
Key benefits:
|
||||
- Values are never stored in state files
|
||||
- Secrets are fetched on-demand during each Terraform operation
|
||||
- Perfect for GitOps workflows
|
||||
- Improved security posture for your infrastructure as code
|
||||
|
||||
### Method 2: Data Sources
|
||||
|
||||
For backwards compatibility or when working with older Terraform versions, you can use the traditional data source approach:
|
||||
|
||||
```hcl
|
||||
# Fetch all secrets in a folder
|
||||
data "infisical_secrets" "my_secrets" {
|
||||
env_slug = "dev"
|
||||
workspace_id = var.infisical_workspace_id
|
||||
folder_path = "/api"
|
||||
}
|
||||
|
||||
# Use individual secrets
|
||||
resource "aws_db_instance" "example" {
|
||||
username = data.infisical_secrets.my_secrets.secrets["DB_USER"]
|
||||
password = data.infisical_secrets.my_secrets.secrets["DB_PASS"]
|
||||
}
|
||||
```
|
||||
|
||||
<Warning>
|
||||
It is recommended to use Terraform variables to pass your service token dynamically to avoid hard coding it
|
||||
When using data sources, secret values are stored in Terraform's state file. Ensure your state file is properly secured.
|
||||
</Warning>
|
||||
|
||||
### 3. Fetch Infisical Secrets
|
||||
## Common Use Cases
|
||||
|
||||
Use the `infisical_secrets` data source to fetch your secrets. In this block, you must set the `env_slug` and `folder_path` to scope the secrets you want.
|
||||
### Secure Database Credential Management
|
||||
|
||||
`env_slug` is the slug of the environment name. This slug name can be found under the project settings page on the Infisical dashboard.
|
||||
|
||||
`folder_path` is the path to the folder in a given environment. The path `/` for root of the environment where as `/folder1` is the folder at the root of the environment.
|
||||
|
||||
```hcl main.tf
|
||||
data "infisical_secrets" "my-secrets" {
|
||||
env_slug = "dev"
|
||||
folder_path = "/some-folder/another-folder"
|
||||
workspace_id = "your-project-id"
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Define Outputs
|
||||
|
||||
As an example, we are going to output your fetched secrets. Replace `SECRET-NAME` with the actual name of your secret.
|
||||
|
||||
For a single secret:
|
||||
|
||||
```hcl main.tf
|
||||
output "single-secret" {
|
||||
value = data.infisical_secrets.my-secrets.secrets["SECRET-NAME"]
|
||||
}
|
||||
```
|
||||
|
||||
For all secrets:
|
||||
Manage database credentials securely without exposing sensitive information in your state files:
|
||||
|
||||
```hcl
|
||||
output "all-secrets" {
|
||||
value = data.infisical_secrets.my-secrets.secrets
|
||||
# Fetch database credentials securely
|
||||
ephemeral "infisical_secret" "db_creds" {
|
||||
name = "DB_CREDENTIALS"
|
||||
env_slug = "prod"
|
||||
workspace_id = var.infisical_workspace_id
|
||||
folder_path = "/database"
|
||||
}
|
||||
|
||||
# Use the credentials in your database instance
|
||||
resource "aws_db_instance" "example" {
|
||||
identifier = "my-database"
|
||||
allocated_storage = 20
|
||||
engine = "postgres"
|
||||
engine_version = "14.0"
|
||||
instance_class = "db.t3.micro"
|
||||
|
||||
# Securely inject credentials from Infisical
|
||||
username = jsondecode(ephemeral.infisical_secret.db_creds.value)["username"]
|
||||
password = jsondecode(ephemeral.infisical_secret.db_creds.value)["password"]
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Run Terraform
|
||||
### GitOps Workflow with OIDC
|
||||
|
||||
Once your configuration is complete, initialize your Terraform working directory:
|
||||
To eliminate the need for static credentials, you can authenticate your workflow using [OpenID Connect (OIDC)](https://infisical.com/docs/documentation/platform/identities/oidc-auth/general) through providers like the [Infisical Secrets GitHub Action](https://github.com/Infisical/secrets-action).
|
||||
Once authenticated, you can securely access secrets through the Infisical provider:
|
||||
|
||||
```bash
|
||||
$ terraform init
|
||||
```hcl
|
||||
provider "infisical" {
|
||||
# Auth credentials automatically injected from the environment
|
||||
}
|
||||
|
||||
# Fetch deployment credentials
|
||||
ephemeral "infisical_secret" "deploy_token" {
|
||||
name = "DEPLOY_TOKEN"
|
||||
env_slug = "prod"
|
||||
workspace_id = var.infisical_workspace_id
|
||||
folder_path = "/deployment"
|
||||
}
|
||||
```
|
||||
For detailed instructions on setting up OIDC authentication with GitHub Actions, refer to our [GitHub Actions OIDC guide](https://infisical.com/docs/documentation/platform/identities/oidc-auth/github).
|
||||
|
||||
Then, run the plan command to view the fetched secrets:
|
||||
## Best Practices
|
||||
|
||||
```bash
|
||||
$ terraform plan
|
||||
```
|
||||
1. **Use Ephemeral Resources**: Whenever possible, use ephemeral resources instead of data sources for improved security.
|
||||
|
||||
Terraform will now fetch your secrets from Infisical and display them as output according to your configuration.
|
||||
2. **Organize Secrets**: Structure your secrets in Infisical using folders to maintain clean separation:
|
||||
```hcl
|
||||
ephemeral "infisical_secret" "db_secret" {
|
||||
folder_path = "/databases/postgresql" # Organized by service
|
||||
# ...
|
||||
}
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
3. **Variable Usage**: Use Terraform variables for workspace IDs and environment slugs:
|
||||
```hcl
|
||||
variable "environment" {
|
||||
description = "Environment (dev, staging, prod)"
|
||||
type = string
|
||||
}
|
||||
|
||||
You have now successfully set up and used the Infisical provider with Terraform to fetch secrets. For more information, visit the [Infisical documentation](https://registry.terraform.io/providers/Infisical/infisical/latest/docs). */}
|
||||
ephemeral "infisical_secret" "secret" {
|
||||
env_slug = var.environment
|
||||
# ...
|
||||
}
|
||||
```
|
||||
|
||||
4. **Error Handling**: Add lifecycle blocks for critical secrets:
|
||||
```hcl
|
||||
ephemeral "infisical_secret" "critical_secret" {
|
||||
# ...
|
||||
lifecycle {
|
||||
postcondition {
|
||||
condition = length(self.value) > 0
|
||||
error_message = "Critical secret must not be empty"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## FAQ
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="What happens if I'm using an older version of Terraform?">
|
||||
If you're using Terraform < v1.10.0, you'll need to use the data source approach.
|
||||
Consider upgrading to take advantage of the enhanced security features provided
|
||||
by ephemeral resources.
|
||||
</Accordion>
|
||||
<Accordion title="Can I mix ephemeral resources and data sources?">
|
||||
Yes, you can use both in the same configuration. However, we recommend using
|
||||
ephemeral resources for any sensitive values to ensure they're not stored in state.
|
||||
</Accordion>
|
||||
<Accordion title="How do I secure my state file when using data sources?">
|
||||
When using data sources, follow Terraform's best practices for state management:
|
||||
- Use remote state with encryption at rest
|
||||
- Implement proper access controls
|
||||
- Consider using state encryption
|
||||
- Treat the state like a secret
|
||||
|
||||
Better yet, use ephemeral resources to avoid storing sensitive values in state entirely.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
See also:
|
||||
- [Machine Identity setup guide](/documentation/platform/identities/machine-identities)
|
||||
- [Terraform Provider Registry](https://registry.terraform.io/providers/Infisical/infisical/latest/docs)
|
||||
- [GitOps Best Practices](https://www.infisical.com/blog/gitops-best-practices)
|
||||
|
@@ -85,6 +85,10 @@
|
||||
"documentation/guides/microsoft-power-apps",
|
||||
"documentation/guides/organization-structure"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Setup",
|
||||
"pages": ["documentation/setup/networking"]
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -232,7 +236,8 @@
|
||||
"documentation/platform/identities/oidc-auth/general",
|
||||
"documentation/platform/identities/oidc-auth/github",
|
||||
"documentation/platform/identities/oidc-auth/circleci",
|
||||
"documentation/platform/identities/oidc-auth/gitlab"
|
||||
"documentation/platform/identities/oidc-auth/gitlab",
|
||||
"documentation/platform/identities/oidc-auth/terraform-cloud"
|
||||
]
|
||||
},
|
||||
"documentation/platform/mfa",
|
||||
@@ -633,7 +638,8 @@
|
||||
"api-reference/endpoints/oidc-auth/attach",
|
||||
"api-reference/endpoints/oidc-auth/retrieve",
|
||||
"api-reference/endpoints/oidc-auth/update",
|
||||
"api-reference/endpoints/oidc-auth/revoke"
|
||||
"api-reference/endpoints/oidc-auth/revoke",
|
||||
"integrations/frameworks/terraform-cloud"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@@ -4,8 +4,6 @@ sidebarTitle: "Go"
|
||||
icon: "golang"
|
||||
---
|
||||
|
||||
|
||||
|
||||
If you're working with Go Lang, the official [Infisical Go SDK](https://github.com/infisical/go-sdk) package is the easiest way to fetch and work with secrets for your application.
|
||||
|
||||
- [Package](https://pkg.go.dev/github.com/infisical/go-sdk)
|
||||
@@ -57,7 +55,9 @@ func main() {
|
||||
This example demonstrates how to use the Infisical Go SDK in a simple Go application. The application retrieves a secret named `API_KEY` from the `dev` environment of the `YOUR_PROJECT_ID` project.
|
||||
|
||||
<Warning>
|
||||
We do not recommend hardcoding your [Machine Identity Tokens](/platform/identities/overview). Setting it as an environment variable would be best.
|
||||
We do not recommend hardcoding your [Machine Identity
|
||||
Tokens](/platform/identities/overview). Setting it as an environment variable
|
||||
would be best.
|
||||
</Warning>
|
||||
|
||||
# Installation
|
||||
@@ -95,6 +95,10 @@ client := infisical.NewInfisicalClient(context.Background(), infisical.Config{
|
||||
<ParamField query="SilentMode" type="boolean" default={false} optional>
|
||||
Whether or not to suppress logs such as warnings from the token refreshing process. Defaults to false if not specified.
|
||||
</ParamField>
|
||||
|
||||
<ParamField query="CacheExpiryInSeconds" type="number" default={0} optional>
|
||||
Defines how long certain responses should be cached in memory, in seconds. When set to a positive value, responses from specific methods (like secret fetching) will be cached for this duration. Set to 0 to disable caching.
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
|
||||
</ParamField>
|
||||
@@ -140,6 +144,7 @@ Call `.Auth().UniversalAuthLogin()` with empty arguments to use the following en
|
||||
- `INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET` - Your machine identity client secret.
|
||||
|
||||
**Using the SDK directly**
|
||||
|
||||
```go
|
||||
_, err := client.Auth().UniversalAuthLogin("CLIENT_ID", "CLIENT_SECRET")
|
||||
|
||||
@@ -150,9 +155,12 @@ if err != nil {
|
||||
```
|
||||
|
||||
#### GCP ID Token Auth
|
||||
|
||||
<Info>
|
||||
Please note that this authentication method will only work if you're running your application on Google Cloud Platform.
|
||||
Please [read more](/documentation/platform/identities/gcp-auth) about this authentication method.
|
||||
Please note that this authentication method will only work if you're running
|
||||
your application on Google Cloud Platform. Please [read
|
||||
more](/documentation/platform/identities/gcp-auth) about this authentication
|
||||
method.
|
||||
</Info>
|
||||
|
||||
**Using environment variables**
|
||||
@@ -162,6 +170,7 @@ Call `.Auth().GcpIdTokenAuthLogin()` with empty arguments to use the following e
|
||||
- `INFISICAL_GCP_AUTH_IDENTITY_ID` - Your Infisical Machine Identity ID.
|
||||
|
||||
**Using the SDK directly**
|
||||
|
||||
```go
|
||||
_, err := client.Auth().GcpIdTokenAuthLogin("YOUR_MACHINE_IDENTITY_ID")
|
||||
|
||||
@@ -181,6 +190,7 @@ Call `.Auth().GcpIamAuthLogin()` with empty arguments to use the following envir
|
||||
- `INFISICAL_GCP_IAM_SERVICE_ACCOUNT_KEY_FILE_PATH` - The path to your GCP service account key file.
|
||||
|
||||
**Using the SDK directly**
|
||||
|
||||
```go
|
||||
_, err = client.Auth().GcpIamAuthLogin("MACHINE_IDENTITY_ID", "SERVICE_ACCOUNT_KEY_FILE_PATH")
|
||||
|
||||
@@ -191,9 +201,12 @@ if err != nil {
|
||||
```
|
||||
|
||||
#### AWS IAM Auth
|
||||
|
||||
<Info>
|
||||
Please note that this authentication method will only work if you're running your application on AWS.
|
||||
Please [read more](/documentation/platform/identities/aws-auth) about this authentication method.
|
||||
Please note that this authentication method will only work if you're running
|
||||
your application on AWS. Please [read
|
||||
more](/documentation/platform/identities/aws-auth) about this authentication
|
||||
method.
|
||||
</Info>
|
||||
|
||||
**Using environment variables**
|
||||
@@ -203,6 +216,7 @@ Call `.Auth().AwsIamAuthLogin()` with empty arguments to use the following envir
|
||||
- `INFISICAL_AWS_IAM_AUTH_IDENTITY_ID` - Your Infisical Machine Identity ID.
|
||||
|
||||
**Using the SDK directly**
|
||||
|
||||
```go
|
||||
_, err = client.Auth().AwsIamAuthLogin("MACHINE_IDENTITY_ID")
|
||||
|
||||
@@ -212,11 +226,13 @@ if err != nil {
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
#### Azure Auth
|
||||
|
||||
<Info>
|
||||
Please note that this authentication method will only work if you're running your application on Azure.
|
||||
Please [read more](/documentation/platform/identities/azure-auth) about this authentication method.
|
||||
Please note that this authentication method will only work if you're running
|
||||
your application on Azure. Please [read
|
||||
more](/documentation/platform/identities/azure-auth) about this authentication
|
||||
method.
|
||||
</Info>
|
||||
|
||||
**Using environment variables**
|
||||
@@ -226,6 +242,7 @@ Call `.Auth().AzureAuthLogin()` with empty arguments to use the following enviro
|
||||
- `INFISICAL_AZURE_AUTH_IDENTITY_ID` - Your Infisical Machine Identity ID.
|
||||
|
||||
**Using the SDK directly**
|
||||
|
||||
```go
|
||||
_, err = client.Auth().AzureAuthLogin("MACHINE_IDENTITY_ID")
|
||||
|
||||
@@ -236,9 +253,12 @@ if err != nil {
|
||||
```
|
||||
|
||||
#### Kubernetes Auth
|
||||
|
||||
<Info>
|
||||
Please note that this authentication method will only work if you're running your application on Kubernetes.
|
||||
Please [read more](/documentation/platform/identities/kubernetes-auth) about this authentication method.
|
||||
Please note that this authentication method will only work if you're running
|
||||
your application on Kubernetes. Please [read
|
||||
more](/documentation/platform/identities/kubernetes-auth) about this
|
||||
authentication method.
|
||||
</Info>
|
||||
|
||||
**Using environment variables**
|
||||
@@ -249,6 +269,7 @@ Call `.Auth().KubernetesAuthLogin()` with empty arguments to use the following e
|
||||
- `INFISICAL_KUBERNETES_SERVICE_ACCOUNT_TOKEN_PATH_ENV_NAME` - The environment variable name that contains the path to the service account token. This is optional and will default to `/var/run/secrets/kubernetes.io/serviceaccount/token`.
|
||||
|
||||
**Using the SDK directly**
|
||||
|
||||
```go
|
||||
// Service account token path will default to /var/run/secrets/kubernetes.io/serviceaccount/token if empty value is passed
|
||||
_, err = client.Auth().KubernetesAuthLogin("MACHINE_IDENTITY_ID", "SERVICE_ACCOUNT_TOKEN_PATH")
|
||||
@@ -262,6 +283,7 @@ if err != nil {
|
||||
## Working With Secrets
|
||||
|
||||
### List Secrets
|
||||
|
||||
`client.Secrets().List(options)`
|
||||
|
||||
Retrieve all secrets within the Infisical project and environment that client is connected to.
|
||||
@@ -311,7 +333,9 @@ secrets, err := client.Secrets().List(infisical.ListSecretsOptions{
|
||||
</ParamField>
|
||||
|
||||
###
|
||||
|
||||
### Retrieve Secret
|
||||
|
||||
`client.Secrets().Retrieve(options)`
|
||||
|
||||
Retrieve a secret from Infisical. By default `Secrets().Retrieve()` fetches and returns a shared secret.
|
||||
@@ -327,27 +351,31 @@ secret, err := client.Secrets().Retrieve(infisical.RetrieveSecretOptions{
|
||||
### Parameters
|
||||
|
||||
<ParamField query="Parameters" type="object" optional>
|
||||
<Expandable title="properties">
|
||||
<ParamField query="SecretKey" type="string" required>
|
||||
The key of the secret to retrieve.
|
||||
</ParamField>
|
||||
<ParamField query="ProjectID" type="string" required>
|
||||
The project ID where the secret lives in.
|
||||
</ParamField>
|
||||
<ParamField query="Environment" type="string" required>
|
||||
The slug name (dev, prod, etc) of the environment from where secrets should be fetched from.
|
||||
</ParamField>
|
||||
<ParamField query="SecretPath" type="string" optional>
|
||||
The path from where secret should be fetched from.
|
||||
</ParamField>
|
||||
<ParamField query="Type" type="string" optional>
|
||||
The type of the secret. Valid options are "shared" or "personal". If not specified, the default value is "shared".
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
<Expandable title="properties">
|
||||
<ParamField query="SecretKey" type="string" required>
|
||||
The key of the secret to retrieve.
|
||||
</ParamField>
|
||||
<ParamField query="ProjectID" type="string" required>
|
||||
The project ID where the secret lives in.
|
||||
</ParamField>
|
||||
<ParamField query="Environment" type="string" required>
|
||||
The slug name (dev, prod, etc) of the environment from where secrets
|
||||
should be fetched from.
|
||||
</ParamField>
|
||||
<ParamField query="SecretPath" type="string" optional>
|
||||
The path from where secret should be fetched from.
|
||||
</ParamField>
|
||||
<ParamField query="Type" type="string" optional>
|
||||
The type of the secret. Valid options are "shared" or "personal". If not
|
||||
specified, the default value is "shared".
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
###
|
||||
|
||||
### Create Secret
|
||||
|
||||
`client.Secrets().Create(options)`
|
||||
|
||||
Create a new secret in Infisical.
|
||||
@@ -363,36 +391,38 @@ secret, err := client.Secrets().Create(infisical.CreateSecretOptions{
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
### Parameters
|
||||
|
||||
<ParamField query="Parameters" type="object" optional>
|
||||
<Expandable title="properties">
|
||||
<ParamField query="SecretKey" type="string" required>
|
||||
The key of the secret to create.
|
||||
</ParamField>
|
||||
<ParamField query="SecretValue" type="string" required>
|
||||
The value of the secret.
|
||||
</ParamField>
|
||||
<ParamField query="SecretComment" type="string" optional>
|
||||
A comment for the secret.
|
||||
</ParamField>
|
||||
<ParamField query="ProjectID" type="string" required>
|
||||
The project ID where the secret lives in.
|
||||
</ParamField>
|
||||
<ParamField query="Environment" type="string" required>
|
||||
The slug name (dev, prod, etc) of the environment from where secrets should be fetched from.
|
||||
</ParamField>
|
||||
<ParamField query="SecretPath" type="string" optional>
|
||||
The path from where secret should be created.
|
||||
</ParamField>
|
||||
<ParamField query="Type" type="string" optional>
|
||||
The type of the secret. Valid options are "shared" or "personal". If not specified, the default value is "shared".
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
<Expandable title="properties">
|
||||
<ParamField query="SecretKey" type="string" required>
|
||||
The key of the secret to create.
|
||||
</ParamField>
|
||||
<ParamField query="SecretValue" type="string" required>
|
||||
The value of the secret.
|
||||
</ParamField>
|
||||
<ParamField query="SecretComment" type="string" optional>
|
||||
A comment for the secret.
|
||||
</ParamField>
|
||||
<ParamField query="ProjectID" type="string" required>
|
||||
The project ID where the secret lives in.
|
||||
</ParamField>
|
||||
<ParamField query="Environment" type="string" required>
|
||||
The slug name (dev, prod, etc) of the environment from where secrets
|
||||
should be fetched from.
|
||||
</ParamField>
|
||||
<ParamField query="SecretPath" type="string" optional>
|
||||
The path from where secret should be created.
|
||||
</ParamField>
|
||||
<ParamField query="Type" type="string" optional>
|
||||
The type of the secret. Valid options are "shared" or "personal". If not
|
||||
specified, the default value is "shared".
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
###
|
||||
|
||||
### Update Secret
|
||||
|
||||
`client.Secrets().Update(options)`
|
||||
@@ -412,33 +442,42 @@ secret, err := client.Secrets().Update(infisical.UpdateSecretOptions{
|
||||
### Parameters
|
||||
|
||||
<ParamField query="Parameters" type="object" optional>
|
||||
<Expandable title="properties">
|
||||
<ParamField query="SecretKey" type="string" required>
|
||||
The key of the secret to update.
|
||||
</ParamField>
|
||||
<ParamField query="NewSecretValue" type="string" required>
|
||||
The new value of the secret.
|
||||
</ParamField>
|
||||
<ParamField query="NewSkipMultilineEncoding" type="boolean" default="false" optional>
|
||||
Whether or not to skip multiline encoding for the new secret value.
|
||||
</ParamField>
|
||||
<ParamField query="ProjectID" type="string" required>
|
||||
The project ID where the secret lives in.
|
||||
</ParamField>
|
||||
<ParamField query="Environment" type="string" required>
|
||||
The slug name (dev, prod, etc) of the environment from where secrets should be fetched from.
|
||||
</ParamField>
|
||||
<ParamField query="SecretPath" type="string" optional>
|
||||
The path from where secret should be updated.
|
||||
</ParamField>
|
||||
<ParamField query="Type" type="string" optional>
|
||||
The type of the secret. Valid options are "shared" or "personal". If not specified, the default value is "shared".
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
<Expandable title="properties">
|
||||
<ParamField query="SecretKey" type="string" required>
|
||||
The key of the secret to update.
|
||||
</ParamField>
|
||||
<ParamField query="NewSecretValue" type="string" required>
|
||||
The new value of the secret.
|
||||
</ParamField>
|
||||
<ParamField
|
||||
query="NewSkipMultilineEncoding"
|
||||
type="boolean"
|
||||
default="false"
|
||||
optional
|
||||
>
|
||||
Whether or not to skip multiline encoding for the new secret value.
|
||||
</ParamField>
|
||||
<ParamField query="ProjectID" type="string" required>
|
||||
The project ID where the secret lives in.
|
||||
</ParamField>
|
||||
<ParamField query="Environment" type="string" required>
|
||||
The slug name (dev, prod, etc) of the environment from where secrets
|
||||
should be fetched from.
|
||||
</ParamField>
|
||||
<ParamField query="SecretPath" type="string" optional>
|
||||
The path from where secret should be updated.
|
||||
</ParamField>
|
||||
<ParamField query="Type" type="string" optional>
|
||||
The type of the secret. Valid options are "shared" or "personal". If not
|
||||
specified, the default value is "shared".
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
###
|
||||
|
||||
### Delete Secret
|
||||
|
||||
`client.Secrets().Delete(options)`
|
||||
|
||||
Delete a secret in Infisical.
|
||||
@@ -454,30 +493,33 @@ secret, err := client.Secrets().Delete(infisical.DeleteSecretOptions{
|
||||
### Parameters
|
||||
|
||||
<ParamField query="Parameters" type="object" optional>
|
||||
<Expandable title="properties">
|
||||
<ParamField query="SecretKey" type="string">
|
||||
The key of the secret to update.
|
||||
</ParamField>
|
||||
<ParamField query="ProjectID" type="string" required>
|
||||
The project ID where the secret lives in.
|
||||
</ParamField>
|
||||
<ParamField query="Environment" type="string" required>
|
||||
The slug name (dev, prod, etc) of the environment from where secrets should be fetched from.
|
||||
</ParamField>
|
||||
<ParamField query="SecretPath" type="string" optional>
|
||||
The path from where secret should be deleted.
|
||||
</ParamField>
|
||||
<ParamField query="Type" type="string" optional>
|
||||
The type of the secret. Valid options are "shared" or "personal". If not specified, the default value is "shared".
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
<Expandable title="properties">
|
||||
<ParamField query="SecretKey" type="string">
|
||||
The key of the secret to update.
|
||||
</ParamField>
|
||||
<ParamField query="ProjectID" type="string" required>
|
||||
The project ID where the secret lives in.
|
||||
</ParamField>
|
||||
<ParamField query="Environment" type="string" required>
|
||||
The slug name (dev, prod, etc) of the environment from where secrets
|
||||
should be fetched from.
|
||||
</ParamField>
|
||||
<ParamField query="SecretPath" type="string" optional>
|
||||
The path from where secret should be deleted.
|
||||
</ParamField>
|
||||
<ParamField query="Type" type="string" optional>
|
||||
The type of the secret. Valid options are "shared" or "personal". If not
|
||||
specified, the default value is "shared".
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
## Working With folders
|
||||
|
||||
|
||||
###
|
||||
|
||||
### List Folders
|
||||
|
||||
`client.Folders().List(options)`
|
||||
|
||||
Retrieve all within the Infisical project and environment that client is connected to.
|
||||
@@ -510,7 +552,9 @@ folders, err := client.Folders().List(infisical.ListFoldersOptions{
|
||||
</ParamField>
|
||||
|
||||
###
|
||||
|
||||
### Create Folder
|
||||
|
||||
`client.Folders().Create(options)`
|
||||
|
||||
Create a new folder in Infisical.
|
||||
@@ -527,25 +571,27 @@ folder, err := client.Folders().Create(infisical.CreateFolderOptions{
|
||||
### Parameters
|
||||
|
||||
<ParamField query="Parameters" type="object" optional>
|
||||
<Expandable title="properties">
|
||||
<ParamField query="ProjectID" type="string" required>
|
||||
The ID of the project where the folder will be created.
|
||||
</ParamField>
|
||||
<ParamField query="Environment" type="string" required>
|
||||
The slug name (dev, prod, etc) of the environment where the folder will be created.
|
||||
</ParamField>
|
||||
<ParamField query="Path" type="string" optional>
|
||||
The path to create the folder in. The root path is `/`.
|
||||
</ParamField>
|
||||
<ParamField query="Name" type="string" optional>
|
||||
The name of the folder to create.
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
<Expandable title="properties">
|
||||
<ParamField query="ProjectID" type="string" required>
|
||||
The ID of the project where the folder will be created.
|
||||
</ParamField>
|
||||
<ParamField query="Environment" type="string" required>
|
||||
The slug name (dev, prod, etc) of the environment where the folder will be
|
||||
created.
|
||||
</ParamField>
|
||||
<ParamField query="Path" type="string" optional>
|
||||
The path to create the folder in. The root path is `/`.
|
||||
</ParamField>
|
||||
<ParamField query="Name" type="string" optional>
|
||||
The name of the folder to create.
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
|
||||
###
|
||||
|
||||
### Update Folder
|
||||
|
||||
`client.Folders().Update(options)`
|
||||
|
||||
Update an existing folder in Infisical.
|
||||
@@ -563,27 +609,30 @@ folder, err := client.Folders().Update(infisical.UpdateFolderOptions{
|
||||
### Parameters
|
||||
|
||||
<ParamField query="Parameters" type="object" optional>
|
||||
<Expandable title="properties">
|
||||
<ParamField query="ProjectID" type="string" required>
|
||||
The ID of the project where the folder will be updated.
|
||||
</ParamField>
|
||||
<ParamField query="Environment" type="string" required>
|
||||
The slug name (dev, prod, etc) of the environment from where the folder lives in.
|
||||
</ParamField>
|
||||
<ParamField query="Path" type="string" optional>
|
||||
The path from where the folder should be updated.
|
||||
</ParamField>
|
||||
<ParamField query="FolderID" type="string" required>
|
||||
The ID of the folder to update.
|
||||
</ParamField>
|
||||
<ParamField query="NewName" type="string" required>
|
||||
The new name of the folder.
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
<Expandable title="properties">
|
||||
<ParamField query="ProjectID" type="string" required>
|
||||
The ID of the project where the folder will be updated.
|
||||
</ParamField>
|
||||
<ParamField query="Environment" type="string" required>
|
||||
The slug name (dev, prod, etc) of the environment from where the folder
|
||||
lives in.
|
||||
</ParamField>
|
||||
<ParamField query="Path" type="string" optional>
|
||||
The path from where the folder should be updated.
|
||||
</ParamField>
|
||||
<ParamField query="FolderID" type="string" required>
|
||||
The ID of the folder to update.
|
||||
</ParamField>
|
||||
<ParamField query="NewName" type="string" required>
|
||||
The new name of the folder.
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
###
|
||||
|
||||
### Delete Folder
|
||||
|
||||
`client.Folders().Delete(options)`
|
||||
|
||||
Delete a folder in Infisical.
|
||||
@@ -620,6 +669,5 @@ deletedFolder, err := client.Folders().Delete(infisical.DeleteFolderOptions{
|
||||
The path from where the folder should be deleted.
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
|
||||
</ParamField>
|
||||
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
---
|
||||
|
||||
title: "Upgrade Your Infisical Instance"
|
||||
title: "Upgrade Infisical Instance"
|
||||
description: "How to upgrade Infisical self-hosted instance"
|
||||
|
||||
---
|
||||
@@ -54,4 +54,4 @@ Now, migrations run automatically during boot-up. This improvement streamlines t
|
||||
- Once the migration is complete, all instances will operate with the updated schema.
|
||||
|
||||
5. **Verify the Upgrade:**
|
||||
- Review the logs for any migration errors or warnings.
|
||||
- Review the logs for any migration errors or warnings.
|
||||
|
Reference in New Issue
Block a user