mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-08 07:27:43 +00:00
fix: resolved trimming keys but keeping last line break for ssh keys and added skip encoding on integration sync
This commit is contained in:
@@ -103,7 +103,10 @@ export const getSecretsBotHelper = async ({
|
|||||||
environment: string;
|
environment: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
}) => {
|
}) => {
|
||||||
const content: Record<string, { value: string; comment?: string }> = {};
|
const content: Record<
|
||||||
|
string,
|
||||||
|
{ value: string; comment?: string; skipMultilineEncoding?: boolean }
|
||||||
|
> = {};
|
||||||
const key = await getKey({ workspaceId: workspaceId });
|
const key = await getKey({ workspaceId: workspaceId });
|
||||||
|
|
||||||
let folderId = "root";
|
let folderId = "root";
|
||||||
@@ -165,6 +168,8 @@ export const getSecretsBotHelper = async ({
|
|||||||
});
|
});
|
||||||
content[secretKey].comment = commentValue;
|
content[secretKey].comment = commentValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
content[secretKey].skipMultilineEncoding = secret.skipMultilineEncoding;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -194,6 +199,8 @@ export const getSecretsBotHelper = async ({
|
|||||||
});
|
});
|
||||||
content[secretKey].comment = commentValue;
|
content[secretKey].comment = commentValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
content[secretKey].skipMultilineEncoding = secret.skipMultilineEncoding;
|
||||||
});
|
});
|
||||||
|
|
||||||
await expandSecrets(workspaceId.toString(), key, content);
|
await expandSecrets(workspaceId.toString(), key, content);
|
||||||
|
|||||||
@@ -1203,7 +1203,7 @@ const formatMultiValueEnv = (val?: string) => {
|
|||||||
export const expandSecrets = async (
|
export const expandSecrets = async (
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
rootEncKey: string,
|
rootEncKey: string,
|
||||||
secrets: Record<string, { value: string; comment?: string }>
|
secrets: Record<string, { value: string; comment?: string; skipMultilineEncoding?: boolean }>
|
||||||
) => {
|
) => {
|
||||||
const expandedSec: Record<string, string> = {};
|
const expandedSec: Record<string, string> = {};
|
||||||
const interpolatedSec: Record<string, string> = {};
|
const interpolatedSec: Record<string, string> = {};
|
||||||
@@ -1221,7 +1221,10 @@ export const expandSecrets = async (
|
|||||||
|
|
||||||
for (const key of Object.keys(secrets)) {
|
for (const key of Object.keys(secrets)) {
|
||||||
if (expandedSec?.[key]) {
|
if (expandedSec?.[key]) {
|
||||||
secrets[key].value = formatMultiValueEnv(expandedSec[key]);
|
// should not do multi line encoding if user has set it to skip
|
||||||
|
secrets[key].value = secrets[key].skipMultilineEncoding
|
||||||
|
? expandedSec[key]
|
||||||
|
: formatMultiValueEnv(expandedSec[key]);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1236,7 +1239,9 @@ export const expandSecrets = async (
|
|||||||
key
|
key
|
||||||
);
|
);
|
||||||
|
|
||||||
secrets[key].value = formatMultiValueEnv(expandedVal);
|
secrets[key].value = secrets[key].skipMultilineEncoding
|
||||||
|
? expandedVal
|
||||||
|
: formatMultiValueEnv(expandedVal);
|
||||||
}
|
}
|
||||||
|
|
||||||
return secrets;
|
return secrets;
|
||||||
|
|||||||
@@ -65,10 +65,10 @@ import sodium from "libsodium-wrappers";
|
|||||||
import { standardRequest } from "../config/request";
|
import { standardRequest } from "../config/request";
|
||||||
|
|
||||||
const getSecretKeyValuePair = (
|
const getSecretKeyValuePair = (
|
||||||
secrets: Record<string, { value: string; comment?: string } | null>
|
secrets: Record<string, { value: string | null; comment?: string } | null>
|
||||||
) =>
|
) =>
|
||||||
Object.keys(secrets).reduce<Record<string, string>>((prev, key) => {
|
Object.keys(secrets).reduce<Record<string, string | null | undefined>>((prev, key) => {
|
||||||
if (secrets[key]) prev[key] = secrets[key]?.value || "";
|
prev[key] = secrets?.[key] === null ? null : secrets?.[key]?.value;
|
||||||
return prev;
|
return prev;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
@@ -348,16 +348,18 @@ const syncSecretsGCPSecretManager = async ({
|
|||||||
...(pageToken ? { pageToken } : {})
|
...(pageToken ? { pageToken } : {})
|
||||||
});
|
});
|
||||||
|
|
||||||
const res: GCPSMListSecretsRes = (await standardRequest.get(
|
const res: GCPSMListSecretsRes = (
|
||||||
`${INTEGRATION_GCP_SECRET_MANAGER_URL}/v1/projects/${integration.appId}/secrets${filterParam}`,
|
await standardRequest.get(
|
||||||
{
|
`${INTEGRATION_GCP_SECRET_MANAGER_URL}/v1/projects/${integration.appId}/secrets${filterParam}`,
|
||||||
params,
|
{
|
||||||
headers: {
|
params,
|
||||||
"Authorization": `Bearer ${accessToken}`,
|
headers: {
|
||||||
"Accept-Encoding": "application/json"
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
"Accept-Encoding": "application/json"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
)).data;
|
).data;
|
||||||
|
|
||||||
if (res.secrets) {
|
if (res.secrets) {
|
||||||
const filteredSecrets = res.secrets?.filter((gcpSecret) => {
|
const filteredSecrets = res.secrets?.filter((gcpSecret) => {
|
||||||
@@ -366,7 +368,10 @@ const syncSecretsGCPSecretManager = async ({
|
|||||||
|
|
||||||
let isValid = true;
|
let isValid = true;
|
||||||
|
|
||||||
if (integration.metadata.secretPrefix && !key.startsWith(integration.metadata.secretPrefix)) {
|
if (
|
||||||
|
integration.metadata.secretPrefix &&
|
||||||
|
!key.startsWith(integration.metadata.secretPrefix)
|
||||||
|
) {
|
||||||
isValid = false;
|
isValid = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -387,29 +392,30 @@ const syncSecretsGCPSecretManager = async ({
|
|||||||
pageToken = res.nextPageToken;
|
pageToken = res.nextPageToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
const res: { [key: string]: string; } = {};
|
const res: { [key: string]: string } = {};
|
||||||
|
|
||||||
interface GCPLatestSecretVersionAccess {
|
interface GCPLatestSecretVersionAccess {
|
||||||
name: string;
|
name: string;
|
||||||
payload: {
|
payload: {
|
||||||
data: string;
|
data: string;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
for await (const gcpSecret of gcpSecrets) {
|
for await (const gcpSecret of gcpSecrets) {
|
||||||
const arr = gcpSecret.name.split("/");
|
const arr = gcpSecret.name.split("/");
|
||||||
const key = arr[arr.length - 1];
|
const key = arr[arr.length - 1];
|
||||||
|
|
||||||
const secretLatest: GCPLatestSecretVersionAccess = (await standardRequest.get(
|
const secretLatest: GCPLatestSecretVersionAccess = (
|
||||||
`${INTEGRATION_GCP_SECRET_MANAGER_URL}/v1/projects/${integration.appId}/secrets/${key}/versions/latest:access`,
|
await standardRequest.get(
|
||||||
{
|
`${INTEGRATION_GCP_SECRET_MANAGER_URL}/v1/projects/${integration.appId}/secrets/${key}/versions/latest:access`,
|
||||||
headers: {
|
{
|
||||||
Authorization: `Bearer ${accessToken}`,
|
headers: {
|
||||||
"Accept-Encoding": "application/json"
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
"Accept-Encoding": "application/json"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
)).data;
|
).data;
|
||||||
|
|
||||||
|
|
||||||
res[key] = Buffer.from(secretLatest.payload.data, "base64").toString("utf-8");
|
res[key] = Buffer.from(secretLatest.payload.data, "base64").toString("utf-8");
|
||||||
}
|
}
|
||||||
@@ -423,11 +429,14 @@ const syncSecretsGCPSecretManager = async ({
|
|||||||
replication: {
|
replication: {
|
||||||
automatic: {}
|
automatic: {}
|
||||||
},
|
},
|
||||||
...(integration.metadata.secretGCPLabel ? {
|
...(integration.metadata.secretGCPLabel
|
||||||
labels: {
|
? {
|
||||||
[integration.metadata.secretGCPLabel.labelName]: integration.metadata.secretGCPLabel.labelValue
|
labels: {
|
||||||
}
|
[integration.metadata.secretGCPLabel.labelName]:
|
||||||
} : {})
|
integration.metadata.secretGCPLabel.labelValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: {})
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
params: {
|
params: {
|
||||||
@@ -489,7 +498,7 @@ const syncSecretsGCPSecretManager = async ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sync/push [secrets] to Azure Key Vault with vault URI [integration.app]
|
* Sync/push [secrets] to Azure Key Vault with vault URI [integration.app]
|
||||||
@@ -729,15 +738,12 @@ const syncSecretsAWSParameterStore = async ({
|
|||||||
} = {};
|
} = {};
|
||||||
|
|
||||||
if (parameterList) {
|
if (parameterList) {
|
||||||
awsParameterStoreSecretsObj = parameterList.reduce(
|
awsParameterStoreSecretsObj = parameterList.reduce((obj: any, secret: any) => {
|
||||||
(obj: any, secret: any) => {
|
return {
|
||||||
return ({
|
...obj,
|
||||||
...obj,
|
[secret.Name.substring(integration.path.length)]: secret
|
||||||
[secret.Name.substring(integration.path.length)]: secret
|
};
|
||||||
});
|
}, {});
|
||||||
},
|
|
||||||
{}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Identify secrets to create
|
// Identify secrets to create
|
||||||
@@ -1870,7 +1876,9 @@ const syncSecretsGitLab = async ({
|
|||||||
environment_scope: string;
|
environment_scope: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const gitLabApiUrl = integrationAuth.url ? `${integrationAuth.url}/api` : INTEGRATION_GITLAB_API_URL;
|
const gitLabApiUrl = integrationAuth.url
|
||||||
|
? `${integrationAuth.url}/api`
|
||||||
|
: INTEGRATION_GITLAB_API_URL;
|
||||||
|
|
||||||
const getAllEnvVariables = async (integrationAppId: string, accessToken: string) => {
|
const getAllEnvVariables = async (integrationAppId: string, accessToken: string) => {
|
||||||
const headers = {
|
const headers = {
|
||||||
@@ -1880,7 +1888,9 @@ const syncSecretsGitLab = async ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
let allEnvVariables: GitLabSecret[] = [];
|
let allEnvVariables: GitLabSecret[] = [];
|
||||||
let url: string | null = `${gitLabApiUrl}/v4/projects/${integrationAppId}/variables?per_page=100`;
|
let url:
|
||||||
|
| string
|
||||||
|
| null = `${gitLabApiUrl}/v4/projects/${integrationAppId}/variables?per_page=100`;
|
||||||
|
|
||||||
while (url) {
|
while (url) {
|
||||||
const response: any = await standardRequest.get(url, { headers });
|
const response: any = await standardRequest.get(url, { headers });
|
||||||
@@ -1901,17 +1911,21 @@ const syncSecretsGitLab = async ({
|
|||||||
|
|
||||||
const allEnvVariables = await getAllEnvVariables(integration?.appId, accessToken);
|
const allEnvVariables = await getAllEnvVariables(integration?.appId, accessToken);
|
||||||
const getSecretsRes: GitLabSecret[] = allEnvVariables
|
const getSecretsRes: GitLabSecret[] = allEnvVariables
|
||||||
.filter(
|
.filter((secret: GitLabSecret) => secret.environment_scope === integration.targetEnvironment)
|
||||||
(secret: GitLabSecret) => secret.environment_scope === integration.targetEnvironment
|
|
||||||
)
|
|
||||||
.filter((gitLabSecret) => {
|
.filter((gitLabSecret) => {
|
||||||
let isValid = true;
|
let isValid = true;
|
||||||
|
|
||||||
if (integration.metadata.secretPrefix && !gitLabSecret.key.startsWith(integration.metadata.secretPrefix)) {
|
if (
|
||||||
|
integration.metadata.secretPrefix &&
|
||||||
|
!gitLabSecret.key.startsWith(integration.metadata.secretPrefix)
|
||||||
|
) {
|
||||||
isValid = false;
|
isValid = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (integration.metadata.secretSuffix && !gitLabSecret.key.endsWith(integration.metadata.secretSuffix)) {
|
if (
|
||||||
|
integration.metadata.secretSuffix &&
|
||||||
|
!gitLabSecret.key.endsWith(integration.metadata.secretSuffix)
|
||||||
|
) {
|
||||||
isValid = false;
|
isValid = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2371,41 +2385,43 @@ const syncSecretsTeamCity = async ({
|
|||||||
|
|
||||||
if (integration.targetEnvironment && integration.targetEnvironmentId) {
|
if (integration.targetEnvironment && integration.targetEnvironmentId) {
|
||||||
// case: sync to specific build-config in TeamCity project
|
// case: sync to specific build-config in TeamCity project
|
||||||
const res = (await standardRequest.get<GetTeamCityBuildConfigParametersRes>(
|
const res = (
|
||||||
`${integrationAuth.url}/app/rest/buildTypes/${integration.targetEnvironmentId}/parameters`,
|
await standardRequest.get<GetTeamCityBuildConfigParametersRes>(
|
||||||
{
|
`${integrationAuth.url}/app/rest/buildTypes/${integration.targetEnvironmentId}/parameters`,
|
||||||
headers: {
|
{
|
||||||
Authorization: `Bearer ${accessToken}`,
|
headers: {
|
||||||
Accept: "application/json",
|
Authorization: `Bearer ${accessToken}`,
|
||||||
},
|
Accept: "application/json"
|
||||||
}
|
}
|
||||||
))
|
}
|
||||||
.data
|
)
|
||||||
.property
|
).data.property
|
||||||
.filter((parameter) => !parameter.inherited)
|
.filter((parameter) => !parameter.inherited)
|
||||||
.reduce((obj: any, secret: TeamCitySecret) => {
|
.reduce((obj: any, secret: TeamCitySecret) => {
|
||||||
const secretName = secret.name.replace(/^env\./, "");
|
const secretName = secret.name.replace(/^env\./, "");
|
||||||
return {
|
return {
|
||||||
...obj,
|
...obj,
|
||||||
[secretName]: secret.value
|
[secretName]: secret.value
|
||||||
};
|
};
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
for await (const key of Object.keys(secrets)) {
|
for await (const key of Object.keys(secrets)) {
|
||||||
if (!(key in res) || (key in res && secrets[key].value !== res[key])) {
|
if (!(key in res) || (key in res && secrets[key].value !== res[key])) {
|
||||||
// case: secret does not exist in TeamCity or secret value has changed
|
// case: secret does not exist in TeamCity or secret value has changed
|
||||||
// -> create/update secret
|
// -> create/update secret
|
||||||
await standardRequest.post(`${integrationAuth.url}/app/rest/buildTypes/${integration.targetEnvironmentId}/parameters`,
|
await standardRequest.post(
|
||||||
{
|
`${integrationAuth.url}/app/rest/buildTypes/${integration.targetEnvironmentId}/parameters`,
|
||||||
name:`env.${key}`,
|
{
|
||||||
value: secrets[key].value
|
name: `env.${key}`,
|
||||||
},
|
value: secrets[key].value
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${accessToken}`,
|
|
||||||
Accept: "application/json",
|
|
||||||
},
|
},
|
||||||
});
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
Accept: "application/json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -257,7 +257,9 @@ export const CreateSecretRawV3 = z.object({
|
|||||||
workspaceId: z.string().trim(),
|
workspaceId: z.string().trim(),
|
||||||
environment: z.string().trim(),
|
environment: z.string().trim(),
|
||||||
secretPath: z.string().trim().default("/"),
|
secretPath: z.string().trim().default("/"),
|
||||||
secretValue: z.string().trim(),
|
secretValue: z
|
||||||
|
.string()
|
||||||
|
.transform((val) => (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim())),
|
||||||
secretComment: z.string().trim(),
|
secretComment: z.string().trim(),
|
||||||
skipMultilineEncoding: z.boolean().optional(),
|
skipMultilineEncoding: z.boolean().optional(),
|
||||||
type: z.enum([SECRET_SHARED, SECRET_PERSONAL])
|
type: z.enum([SECRET_SHARED, SECRET_PERSONAL])
|
||||||
@@ -274,7 +276,9 @@ export const UpdateSecretByNameRawV3 = z.object({
|
|||||||
body: z.object({
|
body: z.object({
|
||||||
workspaceId: z.string().trim(),
|
workspaceId: z.string().trim(),
|
||||||
environment: z.string().trim(),
|
environment: z.string().trim(),
|
||||||
secretValue: z.string().trim(),
|
secretValue: z
|
||||||
|
.string()
|
||||||
|
.transform((val) => (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim())),
|
||||||
secretPath: z.string().trim().default("/"),
|
secretPath: z.string().trim().default("/"),
|
||||||
skipMultilineEncoding: z.boolean().optional(),
|
skipMultilineEncoding: z.boolean().optional(),
|
||||||
type: z.enum([SECRET_SHARED, SECRET_PERSONAL]).default(SECRET_SHARED)
|
type: z.enum([SECRET_SHARED, SECRET_PERSONAL]).default(SECRET_SHARED)
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import { secretKeys } from "@app/hooks/api/secrets/queries";
|
|||||||
import { DecryptedSecret } from "@app/hooks/api/secrets/types";
|
import { DecryptedSecret } from "@app/hooks/api/secrets/types";
|
||||||
import { UserWsKeyPair, WsTag } from "@app/hooks/api/types";
|
import { UserWsKeyPair, WsTag } from "@app/hooks/api/types";
|
||||||
|
|
||||||
|
import { secretSnapshotKeys } from "~/hooks/api/secretSnapshots/queries";
|
||||||
|
|
||||||
import { Filter, GroupBy, SortDir } from "../../SecretMainPage.types";
|
import { Filter, GroupBy, SortDir } from "../../SecretMainPage.types";
|
||||||
import { SecretDetailSidebar } from "./SecretDetaiSidebar";
|
import { SecretDetailSidebar } from "./SecretDetaiSidebar";
|
||||||
import { SecretItem } from "./SecretItem";
|
import { SecretItem } from "./SecretItem";
|
||||||
@@ -218,8 +220,13 @@ export const SecretListView = ({
|
|||||||
queryClient.invalidateQueries(
|
queryClient.invalidateQueries(
|
||||||
secretKeys.getProjectSecret({ workspaceId, environment, secretPath })
|
secretKeys.getProjectSecret({ workspaceId, environment, secretPath })
|
||||||
);
|
);
|
||||||
|
queryClient.invalidateQueries(
|
||||||
|
secretSnapshotKeys.list({ workspaceId, environment, directory: secretPath })
|
||||||
|
);
|
||||||
|
queryClient.invalidateQueries(
|
||||||
|
secretSnapshotKeys.count({ workspaceId, environment, directory: secretPath })
|
||||||
|
);
|
||||||
handlePopUpClose("secretDetail");
|
handlePopUpClose("secretDetail");
|
||||||
|
|
||||||
createNotification({
|
createNotification({
|
||||||
type: "success",
|
type: "success",
|
||||||
text: "Successfully saved secrets"
|
text: "Successfully saved secrets"
|
||||||
@@ -242,6 +249,12 @@ export const SecretListView = ({
|
|||||||
queryClient.invalidateQueries(
|
queryClient.invalidateQueries(
|
||||||
secretKeys.getProjectSecret({ workspaceId, environment, secretPath })
|
secretKeys.getProjectSecret({ workspaceId, environment, secretPath })
|
||||||
);
|
);
|
||||||
|
queryClient.invalidateQueries(
|
||||||
|
secretSnapshotKeys.list({ workspaceId, environment, directory: secretPath })
|
||||||
|
);
|
||||||
|
queryClient.invalidateQueries(
|
||||||
|
secretSnapshotKeys.count({ workspaceId, environment, directory: secretPath })
|
||||||
|
);
|
||||||
handlePopUpClose("deleteSecret");
|
handlePopUpClose("deleteSecret");
|
||||||
handlePopUpClose("secretDetail");
|
handlePopUpClose("secretDetail");
|
||||||
createNotification({
|
createNotification({
|
||||||
|
|||||||
+11
-5
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable no-nested-ternary */
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
export enum SecretActionType {
|
export enum SecretActionType {
|
||||||
@@ -7,11 +8,16 @@ export enum SecretActionType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const formSchema = z.object({
|
export const formSchema = z.object({
|
||||||
key: z.string(),
|
key: z.string().trim(),
|
||||||
value: z.string(),
|
value: z.string().transform((val) => (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim())),
|
||||||
idOverride: z.string().optional(),
|
idOverride: z.string().trim().optional(),
|
||||||
valueOverride: z.string().optional(),
|
valueOverride: z
|
||||||
overrideAction: z.string().optional(),
|
.string()
|
||||||
|
.optional()
|
||||||
|
.transform((val) =>
|
||||||
|
typeof val === "string" ? (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim()) : val
|
||||||
|
),
|
||||||
|
overrideAction: z.string().trim().optional(),
|
||||||
comment: z.string().trim().optional(),
|
comment: z.string().trim().optional(),
|
||||||
skipMultilineEncoding: z.boolean().optional(),
|
skipMultilineEncoding: z.boolean().optional(),
|
||||||
tags: z
|
tags: z
|
||||||
|
|||||||
Reference in New Issue
Block a user