feat(secret-sync): add Gitlab PR comments suggestions

This commit is contained in:
carlosmonastyrski
2025-06-24 10:05:46 -03:00
parent c305ddd463
commit 43e0d400f9
17 changed files with 243 additions and 153 deletions
+2 -1
View File
@@ -2232,7 +2232,8 @@ export const AppConnections = {
GITLAB: { GITLAB: {
instanceUrl: "The GitLab instance URL to connect with.", instanceUrl: "The GitLab instance URL to connect with.",
accessToken: "The Access Token used to access GitLab.", accessToken: "The Access Token used to access GitLab.",
code: "The OAuth code to use to connect with GitLab." code: "The OAuth code to use to connect with GitLab.",
accessTokenType: "The type of token used to connect with GitLab."
} }
} }
}; };
@@ -2,3 +2,8 @@ export enum GitLabConnectionMethod {
OAuth = "oauth", OAuth = "oauth",
AccessToken = "access-token" AccessToken = "access-token"
} }
export enum GitLabAccessTokenType {
Project = "project",
Personal = "personal"
}
@@ -79,10 +79,13 @@ export const refreshGitLabToken = async (
} }
}); });
const expiresAt = new Date(Date.now() + data.expires_in * 1000 - 60000); const expiresAt = new Date(Date.now() + data.expires_in * 1000 - 600000);
const encryptedCredentials = await encryptAppConnectionCredentials({ const encryptedCredentials = await encryptAppConnectionCredentials({
credentials: { credentials: {
instanceUrl,
tokenType: data.token_type,
createdAt: new Date(data.created_at * 1000).toISOString(),
refreshToken: data.refresh_token, refreshToken: data.refresh_token,
accessToken: data.access_token, accessToken: data.access_token,
expiresAt expiresAt
@@ -174,7 +177,7 @@ export const validateGitLabConnectionCredentials = async (config: TGitLabConnect
try { try {
const url = await getGitLabInstanceUrl(inputCredentials.instanceUrl); const url = await getGitLabInstanceUrl(inputCredentials.instanceUrl);
response = await request.get<TGitLabProject[]>(`${url}/api/v4/groups`, { response = await request.get<TGitLabProject[]>(`${url}/api/v4/user`, {
headers: { headers: {
Authorization: `Bearer ${accessToken}`, Authorization: `Bearer ${accessToken}`,
Accept: "application/json" Accept: "application/json"
@@ -200,6 +203,7 @@ export const validateGitLabConnectionCredentials = async (config: TGitLabConnect
if (method === GitLabConnectionMethod.OAuth && oauthData) { if (method === GitLabConnectionMethod.OAuth && oauthData) {
return { return {
accessToken, accessToken,
instanceUrl: inputCredentials.instanceUrl,
refreshToken: oauthData.refresh_token, refreshToken: oauthData.refresh_token,
expiresAt: new Date(Date.now() + oauthData.expires_in * 1000 - 60000), expiresAt: new Date(Date.now() + oauthData.expires_in * 1000 - 60000),
tokenType: oauthData.token_type, tokenType: oauthData.token_type,
@@ -8,9 +8,8 @@ import {
GenericUpdateAppConnectionFieldsSchema GenericUpdateAppConnectionFieldsSchema
} from "@app/services/app-connection/app-connection-schemas"; } from "@app/services/app-connection/app-connection-schemas";
import { GitLabConnectionMethod } from "./gitlab-connection-enums"; import { GitLabAccessTokenType, GitLabConnectionMethod } from "./gitlab-connection-enums";
// Fixed: Use consistent accessToken naming throughout
export const GitLabConnectionAccessTokenCredentialsSchema = z.object({ export const GitLabConnectionAccessTokenCredentialsSchema = z.object({
accessToken: z accessToken: z
.string() .string()
@@ -22,7 +21,8 @@ export const GitLabConnectionAccessTokenCredentialsSchema = z.object({
.trim() .trim()
.url("Invalid Instance URL") .url("Invalid Instance URL")
.optional() .optional()
.describe(AppConnections.CREDENTIALS.GITLAB.instanceUrl) .describe(AppConnections.CREDENTIALS.GITLAB.instanceUrl),
accessTokenType: z.nativeEnum(GitLabAccessTokenType).describe(AppConnections.CREDENTIALS.GITLAB.accessTokenType)
}); });
export const GitLabConnectionOAuthCredentialsSchema = z.object({ export const GitLabConnectionOAuthCredentialsSchema = z.object({
@@ -35,7 +35,6 @@ export const GitLabConnectionOAuthCredentialsSchema = z.object({
.describe(AppConnections.CREDENTIALS.GITLAB.instanceUrl) .describe(AppConnections.CREDENTIALS.GITLAB.instanceUrl)
}); });
// Fixed: Updated schema to match GitLab's actual OAuth response structure
export const GitLabConnectionOAuthOutputCredentialsSchema = z.object({ export const GitLabConnectionOAuthOutputCredentialsSchema = z.object({
accessToken: z.string().trim(), accessToken: z.string().trim(),
refreshToken: z.string().trim(), refreshToken: z.string().trim(),
@@ -50,7 +49,6 @@ export const GitLabConnectionOAuthOutputCredentialsSchema = z.object({
.describe(AppConnections.CREDENTIALS.GITLAB.instanceUrl) .describe(AppConnections.CREDENTIALS.GITLAB.instanceUrl)
}); });
// Schema for refresh token input during initial setup
export const GitLabConnectionRefreshTokenCredentialsSchema = z.object({ export const GitLabConnectionRefreshTokenCredentialsSchema = z.object({
refreshToken: z.string().trim().min(1, "Refresh token required"), refreshToken: z.string().trim().min(1, "Refresh token required"),
instanceUrl: z instanceUrl: z
@@ -83,8 +81,9 @@ export const SanitizedGitLabConnectionSchema = z.discriminatedUnion("method", [
BaseGitLabConnectionSchema.extend({ BaseGitLabConnectionSchema.extend({
method: z.literal(GitLabConnectionMethod.AccessToken), method: z.literal(GitLabConnectionMethod.AccessToken),
credentials: GitLabConnectionAccessTokenCredentialsSchema.pick({ credentials: GitLabConnectionAccessTokenCredentialsSchema.pick({
instanceUrl: true instanceUrl: true,
}) // Don't expose sensitive data accessTokenType: true
})
}), }),
BaseGitLabConnectionSchema.extend({ BaseGitLabConnectionSchema.extend({
method: z.literal(GitLabConnectionMethod.OAuth), method: z.literal(GitLabConnectionMethod.OAuth),
@@ -271,67 +271,84 @@ export const GitLabSyncFns = {
const currentVariableMap = new Map(currentVariables.map((v) => [v.key, v])); const currentVariableMap = new Map(currentVariables.map((v) => [v.key, v]));
for (const [key, { value }] of Object.entries(secretMap)) { for (const [key, { value }] of Object.entries(secretMap)) {
const existingVariable = currentVariableMap.get(key); try {
const existingVariable = currentVariableMap.get(key);
if (existingVariable) { if (existingVariable) {
if (existingVariable.value !== value) { if (existingVariable.value !== value) {
await updateGitLabVariable({ await updateGitLabVariable({
accessToken,
connection,
projectId,
key,
variable: {
value,
variable_type: existingVariable.variable_type,
environment_scope: targetEnvironment || existingVariable.environment_scope,
protected: destinationConfig.shouldProtectSecrets ?? existingVariable.protected,
...(!existingVariable.masked && destinationConfig.shouldMaskSecrets && { masked: value?.length > 8 }),
...(!existingVariable.hidden &&
destinationConfig.shouldHideSecrets && { masked_and_hidden: value?.length > 8 }),
description: existingVariable.description ?? undefined
},
targetEnvironment
});
}
} else {
await createGitLabVariable({
accessToken, accessToken,
connection, connection,
projectId, projectId,
key,
variable: { variable: {
key,
value, value,
variable_type: existingVariable.variable_type, variable_type: "env_var",
environment_scope: targetEnvironment || existingVariable.environment_scope, environment_scope: targetEnvironment || "*",
protected: destinationConfig.shouldProtectSecrets ?? existingVariable.protected, protected: destinationConfig.shouldProtectSecrets || false,
...(!existingVariable.masked && destinationConfig.shouldMaskSecrets && { masked: value?.length > 8 }), masked: value?.length > 8 ? destinationConfig.shouldMaskSecrets || false : false,
...(!existingVariable.hidden && masked_and_hidden: value?.length > 8 ? destinationConfig.shouldHideSecrets || false : false
destinationConfig.shouldHideSecrets && { masked_and_hidden: value?.length > 8 }), }
description: existingVariable.description ?? undefined
},
targetEnvironment
}); });
} }
} else { } catch (error) {
await createGitLabVariable({ throw new SecretSyncError({
accessToken, error,
connection, secretKey: key
projectId,
variable: {
key,
value,
variable_type: "env_var",
environment_scope: targetEnvironment || "*",
protected: destinationConfig.shouldProtectSecrets || false,
masked: value?.length > 8 ? destinationConfig.shouldMaskSecrets || false : false,
masked_and_hidden: value?.length > 8 ? destinationConfig.shouldHideSecrets || false : false
}
}); });
} }
} }
if (!secretSync.syncOptions.disableSecretDeletion) { if (!secretSync.syncOptions.disableSecretDeletion) {
for (const variable of currentVariables) { for (const variable of currentVariables) {
const shouldDelete = try {
matchesSchema(variable.key, environment?.slug || "", secretSync.syncOptions.keySchema) && const shouldDelete =
!(variable.key in secretMap); matchesSchema(variable.key, environment?.slug || "", secretSync.syncOptions.keySchema) &&
!(variable.key in secretMap);
if (shouldDelete) { if (shouldDelete) {
await deleteGitLabVariable({ await deleteGitLabVariable({
accessToken, accessToken,
connection, connection,
projectId, projectId,
key: variable.key, key: variable.key,
targetEnvironment targetEnvironment
});
}
} catch (error) {
throw new SecretSyncError({
error,
secretKey: variable.key
}); });
} }
} }
} }
} catch (error) { } catch (error) {
if (error instanceof SecretSyncError) {
throw error;
}
throw new SecretSyncError({ throw new SecretSyncError({
error, message: "Failed to sync secrets",
secretKey: "batch_sync" error
}); });
} }
}, },
@@ -347,8 +364,8 @@ export const GitLabSyncFns = {
const accessToken = await getValidAccessToken(connection, appConnectionDAL, kmsService); const accessToken = await getValidAccessToken(connection, appConnectionDAL, kmsService);
try { for (const key of Object.keys(secretMap)) {
for (const key of Object.keys(secretMap)) { try {
await deleteGitLabVariable({ await deleteGitLabVariable({
accessToken, accessToken,
connection, connection,
@@ -356,12 +373,12 @@ export const GitLabSyncFns = {
key, key,
targetEnvironment targetEnvironment
}); });
} catch (error) {
throw new SecretSyncError({
error,
secretKey: key
});
} }
} catch (error) {
throw new SecretSyncError({
error,
secretKey: "batch_remove"
});
} }
}, },
@@ -12,7 +12,6 @@ export type TGitLabSyncWithCredentials = TGitLabSync & {
connection: TGitLabConnection; connection: TGitLabConnection;
}; };
// GitLab CI/CD Variable structure based on API documentation
export type TGitLabVariable = { export type TGitLabVariable = {
key: string; key: string;
value: string; value: string;
@@ -25,7 +24,6 @@ export type TGitLabVariable = {
description: string | null; description: string | null;
}; };
// Type for creating a new variable
export type TGitLabVariableCreate = { export type TGitLabVariableCreate = {
key: string; key: string;
value: string; value: string;
@@ -37,7 +35,6 @@ export type TGitLabVariableCreate = {
description?: string; description?: string;
}; };
// Type for updating an existing variable
export type TGitLabVariableUpdate = { export type TGitLabVariableUpdate = {
value: string; value: string;
variable_type?: "env_var" | "file"; variable_type?: "env_var" | "file";
+36 -24
View File
@@ -1,6 +1,6 @@
--- ---
title: "GitLab App Connection" title: "GitLab Connection"
description: "Learn how to configure a GitLab App Connection for Infisical using OAuth or Access Token methods." description: "Learn how to configure a GitLab Connection for Infisical using OAuth or Access Token methods."
--- ---
Infisical supports two methods for connecting to GitLab: **OAuth** and **Access Token**. Choose the method that best fits your setup and security requirements. Infisical supports two methods for connecting to GitLab: **OAuth** and **Access Token**. Choose the method that best fits your setup and security requirements.
@@ -10,7 +10,7 @@ Infisical supports two methods for connecting to GitLab: **OAuth** and **Access
The OAuth method provides secure authentication through GitLab's OAuth flow. The OAuth method provides secure authentication through GitLab's OAuth flow.
<Accordion title="Self-Hosted Instance Setup"> <Accordion title="Self-Hosted Instance Setup">
Using the GitLab App Connection with OAuth on a self-hosted instance of Infisical requires configuring an OAuth application in GitLab and registering your instance with it. Using the GitLab Connection with OAuth on a self-hosted instance of Infisical requires configuring an OAuth application in GitLab and registering your instance with it.
**Prerequisites:** **Prerequisites:**
- A GitLab account with existing projects - A GitLab account with existing projects
@@ -47,7 +47,7 @@ Infisical supports two methods for connecting to GitLab: **OAuth** and **Access
- `CLIENT_ID_GITLAB`: The **Application ID** of your GitLab OAuth application. - `CLIENT_ID_GITLAB`: The **Application ID** of your GitLab OAuth application.
- `CLIENT_SECRET_GITLAB`: The **Secret** of your GitLab OAuth application. - `CLIENT_SECRET_GITLAB`: The **Secret** of your GitLab OAuth application.
Once added, restart your Infisical instance and use the GitLab App Connection. Once added, restart your Infisical instance and use the GitLab Connection.
</Step> </Step>
</Steps> </Steps>
</Accordion> </Accordion>
@@ -60,7 +60,7 @@ Infisical supports two methods for connecting to GitLab: **OAuth** and **Access
![App Connections Tab](/images/app-connections/general/add-connection.png) ![App Connections Tab](/images/app-connections/general/add-connection.png)
</Step> </Step>
<Step title="Add Connection"> <Step title="Add Connection">
Select the **GitLab App Connection** option from the connection options modal. Select the **GitLab Connection** option from the connection options modal.
![Select GitLab Connection](/images/app-connections/gitlab/select-gitlab-connection.png) ![Select GitLab Connection](/images/app-connections/gitlab/select-gitlab-connection.png)
</Step> </Step>
<Step title="Choose OAuth Method"> <Step title="Choose OAuth Method">
@@ -73,7 +73,7 @@ Infisical supports two methods for connecting to GitLab: **OAuth** and **Access
![GitLab Authorization](/images/app-connections/gitlab/gitlab-authorization-page.png) ![GitLab Authorization](/images/app-connections/gitlab/gitlab-authorization-page.png)
</Step> </Step>
<Step title="Connection Created"> <Step title="Connection Created">
Your **GitLab App Connection** is now available for use. Your **GitLab Connection** is now available for use.
![GitLab OAuth Connection](/images/app-connections/gitlab/gitlab-oauth-connection.png) ![GitLab OAuth Connection](/images/app-connections/gitlab/gitlab-oauth-connection.png)
</Step> </Step>
</Steps> </Steps>
@@ -96,13 +96,17 @@ Infisical supports two methods for connecting to GitLab: **OAuth** and **Access
![GitLab Personal Access Tokens](/images/app-connections/gitlab/gitlab-add-access-token.png) ![GitLab Personal Access Tokens](/images/app-connections/gitlab/gitlab-add-access-token.png)
</Step> </Step>
<Step title="Configure Token"> <Step title="Configure Token">
Fill in the token details: <Tabs>
- **Token name**: A descriptive name for the token (e.g., "connection-token") <Tab title="Secret Rotation">
- **Expiration date**: Set an appropriate expiration date For Secret Rotations, your token will require the ability to access the API:
- **Select scopes**: Choose the **api** scope for full API access Fill in the token details:
- **Token name**: A descriptive name for the token (e.g., "connection-token")
![GitLab Personal Token Form](/images/app-connections/gitlab/gitlab-personal-access-token-form.png) - **Expiration date**: Set an appropriate expiration date
- **Select scopes**: Choose the **api** scope for full API access
![GitLab Personal Token Form](/images/app-connections/gitlab/gitlab-personal-access-token-form.png)
</Tab>
</Tabs>
</Step> </Step>
<Step title="Copy Token"> <Step title="Copy Token">
Copy the generated token immediately as it won't be shown again. Copy the generated token immediately as it won't be shown again.
@@ -126,19 +130,31 @@ Infisical supports two methods for connecting to GitLab: **OAuth** and **Access
![GitLab Project Access Tokens](/images/app-connections/gitlab/gitlab-project-access-token-list.png) ![GitLab Project Access Tokens](/images/app-connections/gitlab/gitlab-project-access-token-list.png)
</Step> </Step>
<Step title="Configure Token"> <Step title="Configure Token">
Fill in the token details: <Tabs>
- **Token name**: A descriptive name for the token <Tab title="Secret Rotation">
- **Expiration date**: Set an appropriate expiration date For Secret Rotations, your token will require the ability to access the API and be at least an **Owner**:
- **Select role**: Choose **Owner** or higher role Fill in the token details:
- **Select scopes**: Choose the **api** scope for API access - **Token name**: A descriptive name for the token
- **Expiration date**: Set an appropriate expiration date
- **Select role**: Choose **Owner** or higher role
- **Select scopes**: Choose the **api** scope for API access
![GitLab Create Project Token](/images/app-connections/gitlab/gitlab-project-access-token-form.png) ![GitLab Create Project Token](/images/app-connections/gitlab/gitlab-project-access-token-form.png)
</Tab>
</Tabs>
<Info>
Access Token connections require manual token rotation when your GitLab access token expires or is regenerated. Monitor your connection status and update the token as needed.
</Info>
</Step> </Step>
<Step title="Copy Token"> <Step title="Copy Token">
Copy the generated token immediately as it won't be shown again. Copy the generated token immediately as it won't be shown again.
![GitLab Project Token Form](/images/app-connections/gitlab/gitlab-project-access-token-created.png) ![GitLab Project Token Form](/images/app-connections/gitlab/gitlab-project-access-token-created.png)
<Warning>
Keep your access token secure and do not share it. Anyone with access to this token can access your GitLab account and projects.
</Warning>
</Step> </Step>
</Steps> </Steps>
</Tab> </Tab>
@@ -152,7 +168,7 @@ Infisical supports two methods for connecting to GitLab: **OAuth** and **Access
![App Connections Tab](/images/app-connections/general/add-connection.png) ![App Connections Tab](/images/app-connections/general/add-connection.png)
</Step> </Step>
<Step title="Add Connection"> <Step title="Add Connection">
Select the **GitLab App Connection** option from the connection options modal. Select the **GitLab Connection** option from the connection options modal.
![Select GitLab Connection](/images/app-connections/gitlab/select-gitlab-connection.png) ![Select GitLab Connection](/images/app-connections/gitlab/select-gitlab-connection.png)
</Step> </Step>
<Step title="Configure Access Token"> <Step title="Configure Access Token">
@@ -163,14 +179,10 @@ Infisical supports two methods for connecting to GitLab: **OAuth** and **Access
Click **Connect** to establish the connection. Click **Connect** to establish the connection.
</Step> </Step>
<Step title="Connection Created"> <Step title="Connection Created">
Your **GitLab App Connection** is now available for use. Your **GitLab Connection** is now available for use.
![GitLab Access Token Connection](/images/app-connections/gitlab/gitlab-access-token-connection.png) ![GitLab Access Token Connection](/images/app-connections/gitlab/gitlab-access-token-connection.png)
</Step> </Step>
</Steps> </Steps>
<Info>
Access Token connections require manual token rotation when your GitLab access token expires or is regenerated. Monitor your connection status and update the token as needed.
</Info>
</Tab> </Tab>
</Tabs> </Tabs>
+8 -8
View File
@@ -1,6 +1,6 @@
--- ---
title: "Heroku App Connection" title: "Heroku Connection"
description: "Learn how to configure a Heroku App Connection for Infisical using OAuth or Auth Token methods." description: "Learn how to configure a Heroku Connection for Infisical using OAuth or Auth Token methods."
--- ---
Infisical supports two methods for connecting to Heroku: **OAuth** and **Auth Token**. Choose the method that best fits your setup and security requirements. Infisical supports two methods for connecting to Heroku: **OAuth** and **Auth Token**. Choose the method that best fits your setup and security requirements.
@@ -10,7 +10,7 @@ Infisical supports two methods for connecting to Heroku: **OAuth** and **Auth To
The OAuth method provides secure authentication through Heroku's OAuth flow. The OAuth method provides secure authentication through Heroku's OAuth flow.
<Accordion title="Self-Hosted Instance Setup"> <Accordion title="Self-Hosted Instance Setup">
Using the Heroku App Connection with OAuth on a self-hosted instance of Infisical requires configuring an API client in Heroku and registering your instance with it. Using the Heroku Connection with OAuth on a self-hosted instance of Infisical requires configuring an API client in Heroku and registering your instance with it.
**Prerequisites:** **Prerequisites:**
- A Heroku account with existing applications - A Heroku account with existing applications
@@ -42,7 +42,7 @@ Infisical supports two methods for connecting to Heroku: **OAuth** and **Auth To
- `CLIENT_ID_HEROKU`: The **Client ID** of your Heroku API client. - `CLIENT_ID_HEROKU`: The **Client ID** of your Heroku API client.
- `CLIENT_SECRET_HEROKU`: The **Client Secret** of your Heroku API client. - `CLIENT_SECRET_HEROKU`: The **Client Secret** of your Heroku API client.
Once added, restart your Infisical instance and use the Heroku App Connection. Once added, restart your Infisical instance and use the Heroku Connection.
</Step> </Step>
</Steps> </Steps>
</Accordion> </Accordion>
@@ -55,7 +55,7 @@ Infisical supports two methods for connecting to Heroku: **OAuth** and **Auth To
![App Connections Tab](/images/app-connections/general/add-connection.png) ![App Connections Tab](/images/app-connections/general/add-connection.png)
</Step> </Step>
<Step title="Add Connection"> <Step title="Add Connection">
Select the **Heroku App Connection** option from the connection options modal. Select the **Heroku Connection** option from the connection options modal.
![Select Heroku Connection](/images/app-connections/heroku/heroku-select-connection.png) ![Select Heroku Connection](/images/app-connections/heroku/heroku-select-connection.png)
</Step> </Step>
<Step title="Choose OAuth Method"> <Step title="Choose OAuth Method">
@@ -68,7 +68,7 @@ Infisical supports two methods for connecting to Heroku: **OAuth** and **Auth To
![Heroku Authorization](/images/integrations/heroku/integrations-heroku-auth.png) ![Heroku Authorization](/images/integrations/heroku/integrations-heroku-auth.png)
</Step> </Step>
<Step title="Connection Created"> <Step title="Connection Created">
Your **Heroku App Connection** is now available for use. Your **Heroku Connection** is now available for use.
![Heroku OAuth Connection](/images/app-connections/heroku/heroku-connection.png) ![Heroku OAuth Connection](/images/app-connections/heroku/heroku-connection.png)
</Step> </Step>
</Steps> </Steps>
@@ -97,7 +97,7 @@ Infisical supports two methods for connecting to Heroku: **OAuth** and **Auth To
![App Connections Tab](/images/app-connections/general/add-connection.png) ![App Connections Tab](/images/app-connections/general/add-connection.png)
</Step> </Step>
<Step title="Add Connection"> <Step title="Add Connection">
Select the **Heroku App Connection** option from the connection options modal. Select the **Heroku Connection** option from the connection options modal.
![Select Heroku Connection](/images/app-connections/heroku/heroku-select-connection.png) ![Select Heroku Connection](/images/app-connections/heroku/heroku-select-connection.png)
</Step> </Step>
<Step title="Configure Auth Token"> <Step title="Configure Auth Token">
@@ -108,7 +108,7 @@ Infisical supports two methods for connecting to Heroku: **OAuth** and **Auth To
Click **Connect** to establish the connection. Click **Connect** to establish the connection.
</Step> </Step>
<Step title="Connection Created"> <Step title="Connection Created">
Your **Heroku App Connection** is now available for use. Your **Heroku Connection** is now available for use.
![Heroku Auth Token Connection](/images/app-connections/heroku/heroku-connection.png) ![Heroku Auth Token Connection](/images/app-connections/heroku/heroku-connection.png)
</Step> </Step>
</Steps> </Steps>
+6 -6
View File
@@ -39,17 +39,17 @@ description: "Learn how to configure a GitLab Sync for Infisical."
<Accordion title="Individual"> <Accordion title="Individual">
- **GitLab Project**: The project to deploy secrets to. - **GitLab Project**: The project to deploy secrets to.
- **GitLab Environment Scope**: The environment scope to deploy secrets to (optional, defaults to "*" for all environments). - **GitLab Environment Scope**: The environment scope to deploy secrets to (optional, defaults to "*" for all environments).
- **Mark Infisical secrets in GitLab as 'Protected' secrets**: If enabled, synced secrets will be marked as protected in GitLab. - **Mark secrets as Protected**: If enabled, synced secrets will be marked as protected in GitLab.
- **Mark Infisical secrets in GitLab as 'Masked' secrets**: If enabled, synced secrets will be masked in GitLab CI/CD logs. - **Mark secrets as Masked**: If enabled, synced secrets will be masked in GitLab CI/CD logs.
- **Mark Infisical secrets in GitLab as 'Hidden' secrets**: If enabled, synced secrets will be hidden from the GitLab UI. - **Mark secrets as Hidden**: If enabled, synced secrets will be hidden from the GitLab UI.
</Accordion> </Accordion>
<Accordion title="Group"> <Accordion title="Group">
- **GitLab Group**: The group containing the project. - **GitLab Group**: The group containing the project.
- **GitLab Project**: The project to deploy secrets to. - **GitLab Project**: The project to deploy secrets to.
- **GitLab Environment Scope**: The environment scope to deploy secrets to (optional, defaults to "*" for all environments). - **GitLab Environment Scope**: The environment scope to deploy secrets to (optional, defaults to "*" for all environments).
- **Mark Infisical secrets in GitLab as 'Protected' secrets**: If enabled, synced secrets will be marked as protected in GitLab. - **Mark secrets as Protected**: If enabled, synced secrets will be marked as protected in GitLab.
- **Mark Infisical secrets in GitLab as 'Masked' secrets**: If enabled, synced secrets will be masked in GitLab CI/CD logs. - **Mark secrets as Masked**: If enabled, synced secrets will be masked in GitLab CI/CD logs.
- **Mark Infisical secrets in GitLab as 'Hidden' secrets**: If enabled, synced secrets will be hidden from the GitLab UI. - **Mark secrets as Hidden**: If enabled, synced secrets will be hidden from the GitLab UI.
</Accordion> </Accordion>
</AccordionGroup> </AccordionGroup>
+2 -2
View File
@@ -6,7 +6,7 @@ description: "Learn how to configure a Heroku Sync for Infisical."
**Prerequisites:** **Prerequisites:**
- Set up and add secrets to [Infisical Cloud](https://app.infisical.com) - Set up and add secrets to [Infisical Cloud](https://app.infisical.com)
- Create a [Heroku App Connection](/integrations/app-connections/heroku) - Create a [Heroku Connection](/integrations/app-connections/heroku)
<Tabs> <Tabs>
<Tab title="Infisical UI"> <Tab title="Infisical UI">
@@ -29,7 +29,7 @@ description: "Learn how to configure a Heroku Sync for Infisical."
4. Configure the **Destination** to where secrets should be deployed, then click **Next**. 4. Configure the **Destination** to where secrets should be deployed, then click **Next**.
![Configure Destination](/images/secret-syncs/heroku/heroku-destination.png) ![Configure Destination](/images/secret-syncs/heroku/heroku-destination.png)
- **Heroku App Connection**: The Heroku App Connection to authenticate with. - **Heroku Connection**: The Heroku Connection to authenticate with.
- **Heroku App**: The Heroku application to sync secrets to. - **Heroku App**: The Heroku application to sync secrets to.
5. Configure the **Sync Options** to specify how secrets should be synced, then click **Next**. 5. Configure the **Sync Options** to specify how secrets should be synced, then click **Next**.
@@ -590,6 +590,17 @@ You can configure third-party app connections for re-use across Infisical Projec
</Accordion> </Accordion>
<Accordion title="GitLab OAuth Connection">
<ParamField query="CLIENT_ID_GITLAB" type="string" default="none" optional>
The Application ID of your GitLab OAuth application.
</ParamField>
<ParamField query="CLIENT_SECRET_GITLAB" type="string" default="none" optional>
The Secret of your GitLab OAuth application.
</ParamField>
</Accordion>
## Native Secret Integrations ## Native Secret Integrations
To help you sync secrets from Infisical to services such as Github and Gitlab, Infisical provides native integrations out of the box. To help you sync secrets from Infisical to services such as Github and Gitlab, Infisical provides native integrations out of the box.
@@ -1,6 +1,6 @@
import { Controller, useFormContext, useWatch } from "react-hook-form"; import { Controller, useFormContext, useWatch } from "react-hook-form";
import { SingleValue } from "react-select"; import { SingleValue } from "react-select";
import { faCircleInfo } from "@fortawesome/free-solid-svg-icons"; import { faCircleInfo, faQuestionCircle } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { SecretSyncConnectionField } from "@app/components/secret-syncs/forms/SecretSyncConnectionField"; import { SecretSyncConnectionField } from "@app/components/secret-syncs/forms/SecretSyncConnectionField";
@@ -40,26 +40,24 @@ const SecretProtectionOption = ({
tooltip?: string; tooltip?: string;
}) => { }) => {
return ( return (
<div className="flex items-start justify-between rounded-lg border border-mineshaft-600 bg-mineshaft-800/50 p-4 transition-all duration-200 hover:border-mineshaft-500"> <Switch
<div className="flex flex-1 items-start space-x-3"> className="bg-mineshaft-400/80 shadow-inner data-[state=checked]:bg-green/80"
<div className="min-w-0 flex-1"> id={id}
<div className="mb-1 flex items-center gap-2"> thumbClassName="bg-mineshaft-800"
<h4 className="text-sm font-medium text-bunker-100">{title}</h4> onCheckedChange={onChange}
{tooltip && ( isChecked={isEnabled}
<Tooltip className="max-w-sm" content={tooltip}> isDisabled={isDisabled}
<FontAwesomeIcon containerClassName="w-full"
icon={faCircleInfo} >
className="cursor-help text-xs text-mineshaft-400 hover:text-mineshaft-300" <p>
/> {title}{" "}
</Tooltip> {tooltip && (
)} <Tooltip className="max-w-md" content={tooltip}>
</div> <FontAwesomeIcon icon={faQuestionCircle} size="sm" className="ml-1" />
</div> </Tooltip>
</div> )}
<div className="ml-4 flex-shrink-0"> </p>
<Switch id={id} onCheckedChange={onChange} isChecked={isEnabled} isDisabled={isDisabled} /> </Switch>
</div>
</div>
); );
}; };
@@ -86,7 +84,7 @@ export const GitLabSyncFields = () => {
); );
return ( return (
<div className="h-[calc(100vh-20rem)] overflow-auto"> <div className="h-[calc(100vh-28rem)] overflow-auto">
<SecretSyncConnectionField <SecretSyncConnectionField
onChange={() => { onChange={() => {
setValue("destinationConfig.projectId", ""); setValue("destinationConfig.projectId", "");
@@ -175,7 +173,7 @@ export const GitLabSyncFields = () => {
helperText={ helperText={
<Tooltip <Tooltip
className="max-w-md" className="max-w-md"
content="Ensure the project exists in the connection's GitLab instance URL." content="Ensure the project exists in the connection's GitLab instance URL and the connection has access to it."
> >
<div> <div>
<span>Don&#39;t see the project you&#39;re looking for?</span>{" "} <span>Don&#39;t see the project you&#39;re looking for?</span>{" "}
@@ -229,7 +227,7 @@ export const GitLabSyncFields = () => {
render={({ field: { onChange, value } }) => ( render={({ field: { onChange, value } }) => (
<SecretProtectionOption <SecretProtectionOption
id="should-protect-secrets" id="should-protect-secrets"
title="Mark Infisical secrets in GitLab as 'Protected' secrets" title="Mark secrets as Protected"
isEnabled={value || false} isEnabled={value || false}
onChange={onChange} onChange={onChange}
/> />
@@ -242,7 +240,7 @@ export const GitLabSyncFields = () => {
render={({ field: { onChange, value } }) => ( render={({ field: { onChange, value } }) => (
<SecretProtectionOption <SecretProtectionOption
id="should-mask-secrets" id="should-mask-secrets"
title="Mark Infisical secrets in GitLab as 'Masked' secrets" title="Mark secrets as Masked"
tooltip="GitLab has limitations for masked variables: secrets must be at least 8 characters long and not match existing CI/CD variable names. Secrets not meeting these criteria won't be masked." tooltip="GitLab has limitations for masked variables: secrets must be at least 8 characters long and not match existing CI/CD variable names. Secrets not meeting these criteria won't be masked."
isEnabled={value || false} isEnabled={value || false}
onChange={(checked) => { onChange={(checked) => {
@@ -262,7 +260,7 @@ export const GitLabSyncFields = () => {
<div className="max-h-32 opacity-100 transition-all duration-300"> <div className="max-h-32 opacity-100 transition-all duration-300">
<SecretProtectionOption <SecretProtectionOption
id="should-hide-secrets" id="should-hide-secrets"
title="Mark Infisical secrets in GitLab as 'Hidden' secrets" title="Mark secrets as Hidden"
tooltip="Secrets can only be marked as hidden if they are also masked." tooltip="Secrets can only be marked as hidden if they are also masked."
isEnabled={value || false} isEnabled={value || false}
onChange={onChange} onChange={onChange}
@@ -6,7 +6,7 @@ import { SecretSync } from "@app/hooks/api/secretSyncs";
export const GitLabSyncReviewFields = () => { export const GitLabSyncReviewFields = () => {
const { watch } = useFormContext<TSecretSyncForm & { destination: SecretSync.GitLab }>(); const { watch } = useFormContext<TSecretSyncForm & { destination: SecretSync.GitLab }>();
const projectId = watch("destinationConfig.projectId"); const projectName = watch("destinationConfig.projectName");
const targetEnvironment = watch("destinationConfig.targetEnvironment"); const targetEnvironment = watch("destinationConfig.targetEnvironment");
const groupId = watch("destinationConfig.groupId"); const groupId = watch("destinationConfig.groupId");
const scope = watch("destinationConfig.scope"); const scope = watch("destinationConfig.scope");
@@ -17,7 +17,7 @@ export const GitLabSyncReviewFields = () => {
return ( return (
<> <>
<GenericFieldLabel label="Scope">{scope}</GenericFieldLabel> <GenericFieldLabel label="Scope">{scope}</GenericFieldLabel>
<GenericFieldLabel label="Project ID">{projectId}</GenericFieldLabel> <GenericFieldLabel label="Project Name">{projectName}</GenericFieldLabel>
{groupId && <GenericFieldLabel label="Group ID">{groupId}</GenericFieldLabel>} {groupId && <GenericFieldLabel label="Group ID">{groupId}</GenericFieldLabel>}
{targetEnvironment && ( {targetEnvironment && (
<GenericFieldLabel label="Environment">{targetEnvironment}</GenericFieldLabel> <GenericFieldLabel label="Environment">{targetEnvironment}</GenericFieldLabel>
+1 -1
View File
@@ -75,7 +75,7 @@ export const SECRET_SYNC_MAP: Record<SecretSync, { name: string; image: string }
image: "Flyio.svg" image: "Flyio.svg"
}, },
[SecretSync.GitLab]: { [SecretSync.GitLab]: {
name: "Gitlab", name: "GitLab",
image: "GitLab.png" image: "GitLab.png"
} }
}; };
@@ -7,3 +7,8 @@ export type TGitLabGroup = {
id: string; id: string;
name: string; name: string;
}; };
export enum GitLabAccessTokenType {
Personal = "personal",
Project = "project"
}
@@ -1,6 +1,8 @@
import { AppConnection } from "@app/hooks/api/appConnections/enums"; import { AppConnection } from "@app/hooks/api/appConnections/enums";
import { TRootAppConnection } from "@app/hooks/api/appConnections/types/root-connection"; import { TRootAppConnection } from "@app/hooks/api/appConnections/types/root-connection";
import { GitLabAccessTokenType } from "../gitlab";
export enum GitlabConnectionMethod { export enum GitlabConnectionMethod {
AccessToken = "access-token", AccessToken = "access-token",
OAuth = "oauth" OAuth = "oauth"
@@ -12,6 +14,7 @@ export type TGitlabConnection = TRootAppConnection & { app: AppConnection.Gitlab
credentials: { credentials: {
instanceUrl?: string; instanceUrl?: string;
accessToken: string; accessToken: string;
accessTokenType: GitLabAccessTokenType;
}; };
} }
| { | {
@@ -20,6 +20,7 @@ import { APP_CONNECTION_MAP, getAppConnectionMethodDetails } from "@app/helpers/
import { isInfisicalCloud } from "@app/helpers/platform"; import { isInfisicalCloud } from "@app/helpers/platform";
import { useGetAppConnectionOption } from "@app/hooks/api/appConnections"; import { useGetAppConnectionOption } from "@app/hooks/api/appConnections";
import { AppConnection } from "@app/hooks/api/appConnections/enums"; import { AppConnection } from "@app/hooks/api/appConnections/enums";
import { GitLabAccessTokenType } from "@app/hooks/api/appConnections/gitlab";
import { import {
GitlabConnectionMethod, GitlabConnectionMethod,
TGitlabConnection TGitlabConnection
@@ -41,6 +42,7 @@ const formSchema = z.discriminatedUnion("method", [
method: z.literal(GitlabConnectionMethod.AccessToken), method: z.literal(GitlabConnectionMethod.AccessToken),
credentials: z.object({ credentials: z.object({
accessToken: z.string().min(1, "Access token is required"), accessToken: z.string().min(1, "Access token is required"),
accessTokenType: z.nativeEnum(GitLabAccessTokenType),
instanceUrl: z instanceUrl: z
.string() .string()
.trim() .trim()
@@ -90,6 +92,7 @@ export const GitLabConnectionForm = ({ appConnection, onSubmit: formSubmit }: Pr
method: GitlabConnectionMethod.AccessToken, method: GitlabConnectionMethod.AccessToken,
credentials: { credentials: {
accessToken: "", accessToken: "",
accessTokenType: GitLabAccessTokenType.Personal,
instanceUrl: "" instanceUrl: ""
} }
} as FormData)) } as FormData))
@@ -151,7 +154,7 @@ export const GitLabConnectionForm = ({ appConnection, onSubmit: formSubmit }: Pr
break; break;
default: default:
throw new Error("Unhandled Gitlab Connection method"); throw new Error("Unhandled GitLab Connection method");
} }
} catch (error) { } catch (error) {
console.error("Error handling form submission:", error); console.error("Error handling form submission:", error);
@@ -169,7 +172,7 @@ export const GitLabConnectionForm = ({ appConnection, onSubmit: formSubmit }: Pr
isMissingConfig = false; isMissingConfig = false;
break; break;
default: default:
throw new Error(`Unhandled Gitlab Connection method: ${selectedMethod}`); throw new Error(`Unhandled GitLab Connection method: ${selectedMethod}`);
} }
const methodDetails = getAppConnectionMethodDetails(selectedMethod); const methodDetails = getAppConnectionMethodDetails(selectedMethod);
@@ -211,7 +214,7 @@ export const GitLabConnectionForm = ({ appConnection, onSubmit: formSubmit }: Pr
? `Environment variables have not been configured. ${ ? `Environment variables have not been configured. ${
isInfisicalCloud() isInfisicalCloud()
? "Please contact Infisical." ? "Please contact Infisical."
: `See Docs to configure Gitlab ${methodDetails.name} Connections.` : `See Docs to configure GitLab ${methodDetails.name} Connections.`
}` }`
: error?.message : error?.message
} }
@@ -245,24 +248,59 @@ export const GitLabConnectionForm = ({ appConnection, onSubmit: formSubmit }: Pr
/> />
{selectedMethod === GitlabConnectionMethod.AccessToken && ( {selectedMethod === GitlabConnectionMethod.AccessToken && (
<Controller <>
name="credentials.accessToken" <Controller
control={control} name="credentials.accessTokenType"
render={({ field: { value, onChange }, fieldState: { error } }) => ( control={control}
<FormControl render={({ field: { value, onChange }, fieldState: { error } }) => (
label="Access Token" <FormControl
errorText={error?.message} errorText={error?.message}
isError={Boolean(error?.message)} isError={Boolean(error?.message)}
tooltipText="Your Gitlab Access Token" label="Access Token Type"
> >
<SecretInput <Select
containerClassName="text-gray-400 group-focus-within:!border-primary-400/50 border border-mineshaft-500 bg-mineshaft-900 px-2.5 py-1.5" isDisabled={isUpdate}
value={value} value={value}
onChange={(e) => onChange(e.target.value)} onValueChange={(val) => {
/> onChange(val);
</FormControl> if (val === GitlabConnectionMethod.OAuth) {
)} setValue("credentials.code", "custom");
/> }
}}
className="w-full border border-mineshaft-500"
position="popper"
dropdownContainerClassName="max-w-none"
>
{Object.values(GitLabAccessTokenType).map((method) => {
return (
<SelectItem value={method} key={method}>
{method.charAt(0).toUpperCase() + method.slice(1)} Access Token
</SelectItem>
);
})}
</Select>
</FormControl>
)}
/>
<Controller
name="credentials.accessToken"
control={control}
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
label="Access Token"
errorText={error?.message}
isError={Boolean(error?.message)}
tooltipText="Your GitLab Access Token"
>
<SecretInput
containerClassName="text-gray-400 group-focus-within:!border-primary-400/50 border border-mineshaft-500 bg-mineshaft-900 px-2.5 py-1.5"
value={value}
onChange={(e) => onChange(e.target.value)}
/>
</FormControl>
)}
/>
</>
)} )}
<div className="mt-8 flex items-center"> <div className="mt-8 flex items-center">
@@ -280,10 +318,10 @@ export const GitLabConnectionForm = ({ appConnection, onSubmit: formSubmit }: Pr
} }
> >
{isRedirecting && selectedMethod === GitlabConnectionMethod.OAuth {isRedirecting && selectedMethod === GitlabConnectionMethod.OAuth
? "Redirecting to Gitlab..." ? "Redirecting to GitLab..."
: isUpdate : isUpdate
? "Reconnect to Gitlab" ? "Reconnect to GitLab"
: "Connect to Gitlab"} : "Connect to GitLab"}
</Button> </Button>
<ModalClose asChild> <ModalClose asChild>
<Button colorSchema="secondary" variant="plain"> <Button colorSchema="secondary" variant="plain">