mirror of
https://github.com/Infisical/infisical.git
synced 2025-07-02 16:55:02 +00:00
Compare commits
33 Commits
daniel/upd
...
daniel/ver
Author | SHA1 | Date | |
---|---|---|---|
27af943ee1 | |||
9b772ad55a | |||
94a1fc2809 | |||
10c10642a1 | |||
a93bfa69c9 | |||
08a0550cd7 | |||
d7503573b1 | |||
b5a89edeed | |||
860eaae4c8 | |||
c7a4b6c4e9 | |||
c12c6dcc6e | |||
8741414cfa | |||
b8d29793ec | |||
92013dbfbc | |||
c5319588fe | |||
9efb8eaf78 | |||
dfc973c7f7 | |||
3013d1977c | |||
f358e8942d | |||
c3970d1ea2 | |||
2dc00a638a | |||
bab9c1f454 | |||
2bd4770fb4 | |||
31905fab6e | |||
784acf16d0 | |||
114b89c952 | |||
81420198cb | |||
0ff18e277f | |||
e093f70301 | |||
8e2ff18f35 | |||
3fbfecf7a9 | |||
9087def21c | |||
586dbd79b0 |
@ -1151,6 +1151,50 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "GET",
|
||||
url: "/:integrationAuthId/vercel/custom-environments",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
schema: {
|
||||
querystring: z.object({
|
||||
teamId: z.string().trim()
|
||||
}),
|
||||
params: z.object({
|
||||
integrationAuthId: z.string().trim()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
environments: z
|
||||
.object({
|
||||
appId: z.string(),
|
||||
customEnvironments: z
|
||||
.object({
|
||||
id: z.string(),
|
||||
slug: z.string()
|
||||
})
|
||||
.array()
|
||||
})
|
||||
.array()
|
||||
})
|
||||
}
|
||||
},
|
||||
handler: async (req) => {
|
||||
const environments = await server.services.integrationAuth.getVercelCustomEnvironments({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
id: req.params.integrationAuthId,
|
||||
teamId: req.query.teamId
|
||||
});
|
||||
|
||||
return { environments };
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "GET",
|
||||
url: "/:integrationAuthId/octopus-deploy/spaces",
|
||||
|
@ -81,11 +81,14 @@ export const getAwsConnectionConfig = async (appConnection: TAwsConnectionConfig
|
||||
};
|
||||
|
||||
export const validateAwsConnectionCredentials = async (appConnection: TAwsConnectionConfig) => {
|
||||
const awsConfig = await getAwsConnectionConfig(appConnection);
|
||||
const sts = new AWS.STS(awsConfig);
|
||||
let resp: Awaited<ReturnType<ReturnType<typeof sts.getCallerIdentity>["promise"]>>;
|
||||
let resp: AWS.STS.GetCallerIdentityResponse & {
|
||||
$response: AWS.Response<AWS.STS.GetCallerIdentityResponse, AWS.AWSError>;
|
||||
};
|
||||
|
||||
try {
|
||||
const awsConfig = await getAwsConnectionConfig(appConnection);
|
||||
const sts = new AWS.STS(awsConfig);
|
||||
|
||||
resp = await sts.getCallerIdentity().promise();
|
||||
} catch (e: unknown) {
|
||||
throw new BadRequestError({
|
||||
@ -93,7 +96,7 @@ export const validateAwsConnectionCredentials = async (appConnection: TAwsConnec
|
||||
});
|
||||
}
|
||||
|
||||
if (resp.$response.httpResponse.statusCode !== 200)
|
||||
if (resp?.$response.httpResponse.statusCode !== 200)
|
||||
throw new InternalServerError({
|
||||
message: `Unable to validate credentials: ${
|
||||
resp.$response.error?.message ??
|
||||
|
@ -132,16 +132,26 @@ const getAppsHeroku = async ({ accessToken }: { accessToken: string }) => {
|
||||
|
||||
/**
|
||||
* Return list of names of apps for Vercel integration
|
||||
* This is re-used for getting custom environments for Vercel
|
||||
*/
|
||||
const getAppsVercel = async ({ accessToken, teamId }: { teamId?: string | null; accessToken: string }) => {
|
||||
const apps: Array<{ name: string; appId: string }> = [];
|
||||
export const getAppsVercel = async ({ accessToken, teamId }: { teamId?: string | null; accessToken: string }) => {
|
||||
const apps: Array<{ name: string; appId: string; customEnvironments: Array<{ slug: string; id: string }> }> = [];
|
||||
|
||||
const limit = "20";
|
||||
let hasMorePages = true;
|
||||
let next: number | null = null;
|
||||
|
||||
interface Response {
|
||||
projects: { name: string; id: string }[];
|
||||
projects: {
|
||||
name: string;
|
||||
id: string;
|
||||
customEnvironments?: {
|
||||
id: string;
|
||||
type: string;
|
||||
description: string;
|
||||
slug: string;
|
||||
}[];
|
||||
}[];
|
||||
pagination: {
|
||||
count: number;
|
||||
next: number | null;
|
||||
@ -173,7 +183,12 @@ const getAppsVercel = async ({ accessToken, teamId }: { teamId?: string | null;
|
||||
data.projects.forEach((a) => {
|
||||
apps.push({
|
||||
name: a.name,
|
||||
appId: a.id
|
||||
appId: a.id,
|
||||
customEnvironments:
|
||||
a.customEnvironments?.map((env) => ({
|
||||
slug: env.slug,
|
||||
id: env.id
|
||||
})) ?? []
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -25,11 +25,12 @@ import { TIntegrationDALFactory } from "../integration/integration-dal";
|
||||
import { TKmsServiceFactory } from "../kms/kms-service";
|
||||
import { KmsDataKey } from "../kms/kms-types";
|
||||
import { TProjectBotServiceFactory } from "../project-bot/project-bot-service";
|
||||
import { getApps } from "./integration-app-list";
|
||||
import { getApps, getAppsVercel } from "./integration-app-list";
|
||||
import { TCircleCIContext } from "./integration-app-types";
|
||||
import { TIntegrationAuthDALFactory } from "./integration-auth-dal";
|
||||
import { IntegrationAuthMetadataSchema, TIntegrationAuthMetadata } from "./integration-auth-schema";
|
||||
import {
|
||||
GetVercelCustomEnvironmentsDTO,
|
||||
OctopusDeployScope,
|
||||
TBitbucketEnvironment,
|
||||
TBitbucketWorkspace,
|
||||
@ -1825,6 +1826,41 @@ export const integrationAuthServiceFactory = ({
|
||||
return integrationAuthDAL.create(newIntegrationAuth);
|
||||
};
|
||||
|
||||
const getVercelCustomEnvironments = async ({
|
||||
actorId,
|
||||
actor,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
teamId,
|
||||
id
|
||||
}: GetVercelCustomEnvironmentsDTO) => {
|
||||
const integrationAuth = await integrationAuthDAL.findById(id);
|
||||
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor,
|
||||
actorId,
|
||||
projectId: integrationAuth.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
|
||||
|
||||
const { botKey, shouldUseSecretV2Bridge } = await projectBotService.getBotKey(integrationAuth.projectId);
|
||||
const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey);
|
||||
|
||||
const vercelApps = await getAppsVercel({
|
||||
accessToken,
|
||||
teamId
|
||||
});
|
||||
|
||||
return vercelApps.map((app) => ({
|
||||
customEnvironments: app.customEnvironments,
|
||||
appId: app.appId
|
||||
}));
|
||||
};
|
||||
|
||||
const getOctopusDeploySpaces = async ({
|
||||
actorId,
|
||||
actor,
|
||||
@ -1944,6 +1980,7 @@ export const integrationAuthServiceFactory = ({
|
||||
getIntegrationAccessToken,
|
||||
duplicateIntegrationAuth,
|
||||
getOctopusDeploySpaces,
|
||||
getOctopusDeployScopeValues
|
||||
getOctopusDeployScopeValues,
|
||||
getVercelCustomEnvironments
|
||||
};
|
||||
};
|
||||
|
@ -284,3 +284,8 @@ export type TOctopusDeployVariableSet = {
|
||||
Self: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type GetVercelCustomEnvironmentsDTO = {
|
||||
teamId: string;
|
||||
id: string;
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
@ -1450,9 +1450,13 @@ const syncSecretsVercel = async ({
|
||||
secrets: Record<string, { value: string; comment?: string } | null>;
|
||||
accessToken: string;
|
||||
}) => {
|
||||
const isCustomEnvironment = !["development", "preview", "production"].includes(
|
||||
integration.targetEnvironment as string
|
||||
);
|
||||
interface VercelSecret {
|
||||
id?: string;
|
||||
type: string;
|
||||
customEnvironmentIds?: string[];
|
||||
key: string;
|
||||
value: string;
|
||||
target: string[];
|
||||
@ -1486,6 +1490,16 @@ const syncSecretsVercel = async ({
|
||||
}
|
||||
)
|
||||
).data.envs.filter((secret) => {
|
||||
if (isCustomEnvironment) {
|
||||
if (!secret.customEnvironmentIds?.includes(integration.targetEnvironment as string)) {
|
||||
// case: secret does not have the same custom environment
|
||||
return false;
|
||||
}
|
||||
|
||||
// no need to check for preview environment, as custom environments are not available in preview
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!secret.target.includes(integration.targetEnvironment as string)) {
|
||||
// case: secret does not have the same target environment
|
||||
return false;
|
||||
@ -1583,7 +1597,13 @@ const syncSecretsVercel = async ({
|
||||
key,
|
||||
value: infisicalSecrets[key]?.value,
|
||||
type: "encrypted",
|
||||
target: [integration.targetEnvironment as string],
|
||||
...(isCustomEnvironment
|
||||
? {
|
||||
customEnvironmentIds: [integration.targetEnvironment as string]
|
||||
}
|
||||
: {
|
||||
target: [integration.targetEnvironment as string]
|
||||
}),
|
||||
...(integration.path
|
||||
? {
|
||||
gitBranch: integration.path
|
||||
@ -1607,9 +1627,19 @@ const syncSecretsVercel = async ({
|
||||
key,
|
||||
value: infisicalSecrets[key]?.value,
|
||||
type: res[key].type,
|
||||
target: res[key].target.includes(integration.targetEnvironment as string)
|
||||
? [...res[key].target]
|
||||
: [...res[key].target, integration.targetEnvironment as string],
|
||||
|
||||
...(!isCustomEnvironment
|
||||
? {
|
||||
target: res[key].target.includes(integration.targetEnvironment as string)
|
||||
? [...res[key].target]
|
||||
: [...res[key].target, integration.targetEnvironment as string]
|
||||
}
|
||||
: {
|
||||
customEnvironmentIds: res[key].customEnvironmentIds?.includes(integration.targetEnvironment as string)
|
||||
? [...(res[key].customEnvironmentIds || [])]
|
||||
: [...(res[key]?.customEnvironmentIds || []), integration.targetEnvironment as string]
|
||||
}),
|
||||
|
||||
...(integration.path
|
||||
? {
|
||||
gitBranch: integration.path
|
||||
|
@ -5,7 +5,7 @@ title: "Node"
|
||||
This guide demonstrates how to use Infisical to manage secrets for your Node stack from local development to production. 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 [@infisical/sdk](https://github.com/Infisical/sdk/tree/main/languages/node) Node.js client SDK to fetch secrets back to your Node application on demand.
|
||||
- The [@infisical/sdk](https://github.com/Infisical/node-sdk-v2) Node.js client SDK to fetch secrets back to your Node application on demand.
|
||||
|
||||
## Project Setup
|
||||
|
||||
@ -46,43 +46,57 @@ Finally, create an index.js file containing the application code.
|
||||
|
||||
```js
|
||||
const express = require('express');
|
||||
const { InfisicalClient } = require("@infisical/sdk");
|
||||
const { InfisicalSDK } = require("@infisical/sdk");
|
||||
|
||||
|
||||
const app = express();
|
||||
|
||||
const PORT = 3000;
|
||||
|
||||
const client = new InfisicalClient({
|
||||
auth: {
|
||||
universalAuth: {
|
||||
clientId: "YOUR_CLIENT_ID",
|
||||
clientSecret: "YOUR_CLIENT_SECRET",
|
||||
}
|
||||
}
|
||||
});
|
||||
let client;
|
||||
|
||||
const setupClient = () => {
|
||||
|
||||
if (client) {
|
||||
return;
|
||||
}
|
||||
|
||||
const infisicalSdk = new InfisicalSDK({
|
||||
siteUrl: "your-infisical-instance.com" // Optional, defaults to https://app.infisical.com
|
||||
});
|
||||
|
||||
await infisicalSdk.auth().universalAuth.login({
|
||||
clientId: "<machine-identity-client-id>",
|
||||
clientSecret: "<machine-identity-client-secret>"
|
||||
});
|
||||
|
||||
// If authentication was successful, assign the client
|
||||
client = infisicalSdk;
|
||||
}
|
||||
|
||||
|
||||
|
||||
app.get("/", async (req, res) => {
|
||||
// access value
|
||||
|
||||
|
||||
const name = await client.getSecret({
|
||||
environment: "dev",
|
||||
projectId: "PROJECT_ID",
|
||||
path: "/",
|
||||
type: "shared",
|
||||
secretName: "NAME"
|
||||
const name = await client.secrets().getSecret({
|
||||
environment: "dev", // dev, staging, prod, etc.
|
||||
projectId: "<project-id>",
|
||||
secretPath: "/",
|
||||
secretName: "NAME"
|
||||
});
|
||||
|
||||
|
||||
res.send(`Hello! My name is: ${name.secretValue}`);
|
||||
});
|
||||
|
||||
app.listen(PORT, async () => {
|
||||
// initialize client
|
||||
|
||||
console.log(`App listening on port ${PORT}`);
|
||||
// initialize http server and Infisical
|
||||
await setupClient();
|
||||
console.log(`Server listening on port ${PORT}`);
|
||||
});
|
||||
```
|
||||
|
||||
Here, we initialized a `client` instance of the Infisical Node SDK with the Infisical Token
|
||||
Here, we initialized a `client` instance of the Infisical Node SDK with the [Machine Identity](/documentation/platform/identities/overview)
|
||||
that we created earlier, giving access to the secrets in the development environment of the
|
||||
project in Infisical that we created earlier.
|
||||
|
||||
@ -94,16 +108,12 @@ node index.js
|
||||
|
||||
The client fetched the secret with the key `NAME` from Infisical that we returned in the response of the endpoint.
|
||||
|
||||
At this stage, you know how to fetch secrets from Infisical back to your Node application. By using Infisical Tokens scoped to different environments, you can easily manage secrets across various stages of your project in Infisical, from local development to production.
|
||||
At this stage, you know how to fetch secrets from Infisical back to your Node application.
|
||||
By using Machine Identities scoped to different projects and environments, you can easily manage secrets across various stages of your project in Infisical, from local development to production.
|
||||
|
||||
## FAQ
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Isn't it inefficient if my app makes a request every time it needs a secret?">
|
||||
The client SDK caches every secret and implements a 5-minute waiting period before
|
||||
re-requesting it. The waiting period can be controlled by setting the `cacheTTL` parameter at
|
||||
the time of initializing the client.
|
||||
</Accordion>
|
||||
<Accordion title="What if a request for a secret fails?">
|
||||
The SDK caches every secret and falls back to the cached value if a request fails. If no cached
|
||||
value ever-existed, the SDK falls back to whatever value is on `process.env`.
|
||||
@ -124,4 +134,4 @@ At this stage, you know how to fetch secrets from Infisical back to your Node ap
|
||||
|
||||
See also:
|
||||
|
||||
- Explore the [Node SDK](https://github.com/Infisical/sdk/tree/main/languages/node)
|
||||
- Explore the [Node SDK](https://github.com/Infisical/node-sdk-v2)
|
||||
|
@ -3,7 +3,7 @@ title: "Role-based Access Controls"
|
||||
description: "Learn how to use RBAC to manage user permissions."
|
||||
---
|
||||
|
||||
Infisical's Role-based Access Controls (RBAC) enable the usage of predefined and custom roles that imply a set of permissions for user and machine identities. Such roles male it possible to restrict access to resources and the range of actions that can be performed.
|
||||
Infisical's Role-based Access Controls (RBAC) enable the usage of predefined and custom roles that imply a set of permissions for user and machine identities. Such roles make it possible to restrict access to resources and the range of actions that can be performed.
|
||||
|
||||
In general, access controls can be split up across [projects](/documentation/platform/project) and [organizations](/documentation/platform/organization).
|
||||
|
||||
|
BIN
docs/images/app-connections/aws/access-key-create-policy.png
Normal file
BIN
docs/images/app-connections/aws/access-key-create-policy.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 509 KiB |
BIN
docs/images/app-connections/aws/assume-role-create-policy.png
Normal file
BIN
docs/images/app-connections/aws/assume-role-create-policy.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 522 KiB |
@ -9,10 +9,6 @@ Infisical supports two methods for connecting to AWS.
|
||||
<Tab title="Assume Role (Recommended)">
|
||||
Infisical will assume the provided role in your AWS account securely, without the need to share any credentials.
|
||||
|
||||
**Prerequisites:**
|
||||
|
||||
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
||||
|
||||
<Accordion title="Self-Hosted Instance">
|
||||
To connect your self-hosted Infisical instance with AWS, you need to set up an AWS IAM User account that can assume the configured AWS IAM Role.
|
||||
|
||||
@ -47,8 +43,8 @@ Infisical supports two methods for connecting to AWS.
|
||||

|
||||
</Step>
|
||||
<Step title="Set Up Connection Keys">
|
||||
1. Set the access key as **INF_APP_CONNECTION_AWS_CLIENT_ID**.
|
||||
2. Set the secret key as **INF_APP_CONNECTION_AWS_CLIENT_SECRET**.
|
||||
1. Set the access key as **INF_APP_CONNECTION_AWS_ACCESS_KEY_ID**.
|
||||
2. Set the secret key as **INF_APP_CONNECTION_AWS_SECRET_ACCESS_KEY**.
|
||||
</Step>
|
||||
</Steps>
|
||||
</Accordion>
|
||||
@ -63,7 +59,11 @@ Infisical supports two methods for connecting to AWS.
|
||||
4. Optionally, enable **Require external ID** and enter your **Organization ID** to further enhance security.
|
||||
</Step>
|
||||
|
||||
<Step title="Add Required Permissions for the IAM Role">
|
||||
<Step title="Add Required Permissions to the IAM Role">
|
||||
Navigate to your IAM role permissions and click **Create Inline Policy**.
|
||||
|
||||

|
||||
|
||||
Depending on your use case, add one or more of the following policies to your IAM Role:
|
||||
|
||||
<Tabs>
|
||||
@ -199,22 +199,13 @@ Infisical supports two methods for connecting to AWS.
|
||||
<Tab title="Access Key">
|
||||
Infisical will use the provided **Access Key ID** and **Secret Key** to connect to your AWS instance.
|
||||
|
||||
**Prerequisites:**
|
||||
|
||||
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
||||
|
||||
<Steps>
|
||||
<Step title="Create the Managing User IAM Role for Infisical">
|
||||
1. Navigate to the [Create IAM Role](https://console.aws.amazon.com/iamv2/home#/roles/create?step=selectEntities) page in your AWS Console.
|
||||

|
||||
<Step title="Add Required Permissions to the IAM User">
|
||||
Navigate to your IAM user permissions and click **Create Inline Policy**.
|
||||
|
||||
2. Select **AWS Account** as the **Trusted Entity Type**.
|
||||
3. Choose **Another AWS Account** and enter **381492033652** (Infisical AWS Account ID). This restricts the role to be assumed only by Infisical. If self-hosting, provide your AWS account number instead.
|
||||
4. Optionally, enable **Require external ID** and enter your **Organization ID** to further enhance security.
|
||||
</Step>
|
||||

|
||||
|
||||
<Step title="Add Required Permissions for the IAM Role">
|
||||
Depending on your use case, add one or more of the following policies to your IAM Role:
|
||||
Depending on your use case, add one or more of the following policies to your user:
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Secret Sync">
|
||||
|
@ -9,10 +9,6 @@ Infisical supports two methods for connecting to GitHub.
|
||||
<Tab title="GitHub App (Recommended)">
|
||||
Infisical will use a GitHub App with finely grained permissions to connect to GitHub.
|
||||
|
||||
**Prerequisites:**
|
||||
|
||||
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
||||
|
||||
<Accordion title="Self-Hosted Instance">
|
||||
Using the GitHub integration with app authentication on a self-hosted instance of Infisical requires configuring an application on GitHub
|
||||
and registering your instance with it.
|
||||
@ -61,9 +57,9 @@ Infisical supports two methods for connecting to GitHub.
|
||||
|
||||
- `INF_APP_CONNECTION_GITHUB_APP_CLIENT_ID`: The **Client ID** of your GitHub application.
|
||||
- `INF_APP_CONNECTION_GITHUB_APP_CLIENT_SECRET`: The **Client Secret** of your GitHub application.
|
||||
- `INF_APP_CONNECTION_GITHUB_APP_CLIENT_SLUG`: The **Slug** of your GitHub application. This is the one found in the URL.
|
||||
- `INF_APP_CONNECTION_GITHUB_APP_CLIENT_APP_ID`: The **App ID** of your GitHub application.
|
||||
- `INF_APP_CONNECTION_GITHUB_APP_CLIENT_PRIVATE_KEY`: The **Private Key** of your GitHub application.
|
||||
- `INF_APP_CONNECTION_GITHUB_APP_SLUG`: The **Slug** of your GitHub application. This is the one found in the URL.
|
||||
- `INF_APP_CONNECTION_GITHUB_APP_ID`: The **App ID** of your GitHub application.
|
||||
- `INF_APP_CONNECTION_GITHUB_APP_PRIVATE_KEY`: The **Private Key** of your GitHub application.
|
||||
|
||||
Once added, restart your Infisical instance and use the GitHub integration via app authentication.
|
||||
</Step>
|
||||
@ -100,10 +96,6 @@ Infisical supports two methods for connecting to GitHub.
|
||||
<Tab title="OAuth">
|
||||
Infisical will use an OAuth App to connect to GitHub.
|
||||
|
||||
**Prerequisites:**
|
||||
|
||||
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
||||
|
||||
<Accordion title="Self-Hosted Instance">
|
||||
Using the GitHub integration on a self-hosted instance of Infisical requires configuring an OAuth application in GitHub
|
||||
and registering your instance with it.
|
||||
|
@ -26,15 +26,15 @@ spec:
|
||||
name: <service-account-name>
|
||||
namespace: <service-account-namespace>
|
||||
|
||||
managedSecretReference:
|
||||
secretName: managed-secret
|
||||
secretNamespace: default
|
||||
creationPolicy: "Orphan"
|
||||
template:
|
||||
includeAllSecrets: true
|
||||
data:
|
||||
NEW_KEY_NAME: "{{ .KEY.SecretPath }} {{ .KEY.Value }}"
|
||||
KEY_WITH_BINARY_VALUE: "{{ .KEY.SecretPath }} {{ .KEY.Value }}"
|
||||
managedKubeSecretReferences:
|
||||
- secretName: managed-secret
|
||||
secretNamespace: default
|
||||
creationPolicy: "Orphan"
|
||||
template:
|
||||
includeAllSecrets: true
|
||||
data:
|
||||
NEW_KEY_NAME: "{{ .KEY.SecretPath }} {{ .KEY.Value }}"
|
||||
KEY_WITH_BINARY_VALUE: "{{ .KEY.SecretPath }} {{ .KEY.Value }}"
|
||||
```
|
||||
|
||||
## CRD properties
|
||||
@ -541,18 +541,32 @@ The managed secret properties specify where to store the secrets retrieved from
|
||||
This includes defining the name and namespace of the Kubernetes secret that will hold these secrets.
|
||||
The Infisical operator will automatically create the Kubernetes secret in the specified name/namespace and ensure it stays up-to-date.
|
||||
|
||||
<Accordion title="managedSecretReference">
|
||||
<Note>
|
||||
|
||||
The `managedSecretReference` field is deprecated and will be removed in a future release.
|
||||
Replace it with `managedKubeSecretReferences`, which now accepts an array of references to support multiple managed secrets in a single InfisicalSecret CRD.
|
||||
|
||||
Example:
|
||||
```yaml
|
||||
managedKubeSecretReferences:
|
||||
- secretName: managed-secret
|
||||
secretNamespace: default
|
||||
creationPolicy: "Orphan"
|
||||
```
|
||||
</Note>
|
||||
|
||||
<Accordion title="managedKubeSecretReferences">
|
||||
</Accordion>
|
||||
<Accordion title="managedSecretReference.secretName">
|
||||
<Accordion title="managedKubeSecretReferences[].secretName">
|
||||
The name of the managed Kubernetes secret to be created
|
||||
</Accordion>
|
||||
<Accordion title="managedSecretReference.secretNamespace">
|
||||
<Accordion title="managedKubeSecretReferences[].secretNamespace">
|
||||
The namespace of the managed Kubernetes secret to be created.
|
||||
</Accordion>
|
||||
<Accordion title="managedSecretReference.secretType">
|
||||
<Accordion title="managedKubeSecretReferences[].secretType">
|
||||
Override the default Opaque type for managed secrets with this field. Useful for creating kubernetes.io/dockerconfigjson secrets.
|
||||
</Accordion>
|
||||
<Accordion title="managedSecretReference.creationPolicy">
|
||||
<Accordion title="managedKubeSecretReferences[].creationPolicy">
|
||||
Creation polices allow you to control whether or not owner references should be added to the managed Kubernetes secret that is generated by the Infisical operator.
|
||||
This is useful for tools such as ArgoCD, where every resource requires an owner reference; otherwise, it will be pruned automatically.
|
||||
|
||||
@ -573,18 +587,18 @@ This is useful for tools such as ArgoCD, where every resource requires an owner
|
||||
Fetching secrets from Infisical as is via the operator may not be enough. This is where templating functionality may be helpful.
|
||||
Using Go templates, you can format, combine, and create new key-value pairs from secrets fetched from Infisical before storing them as Kubernetes Secrets.
|
||||
|
||||
<Accordion title="managedSecretReference.template">
|
||||
<Accordion title="managedKubeSecretReferences[].template">
|
||||
</Accordion>
|
||||
<Accordion title="managedSecretReference.template.includeAllSecrets">
|
||||
<Accordion title="managedKubeSecretReferences[].template.includeAllSecrets">
|
||||
This property controls what secrets are included in your managed secret when using templates.
|
||||
When set to `true`, all secrets fetched from your Infisical project will be added into your managed Kubernetes secret resource.
|
||||
**Use this option when you would like to sync all secrets from Infisical to Kubernetes but want to template a subset of them.**
|
||||
|
||||
When set to `false`, only secrets defined in the `managedSecretReference.template.data` field of the template will be included in the managed secret.
|
||||
When set to `false`, only secrets defined in the `managedKubeSecretReferences[].template.data` field of the template will be included in the managed secret.
|
||||
Use this option when you would like to sync **only** a subset of secrets from Infisical to Kubernetes.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="managedSecretReference.template.data">
|
||||
<Accordion title="managedKubeSecretReferences[].template.data">
|
||||
Define secret keys and their corresponding templates.
|
||||
Each data value uses a Golang template with access to all secrets retrieved from the specified scope.
|
||||
|
||||
@ -600,16 +614,16 @@ type TemplateSecret struct {
|
||||
#### Example template configuration:
|
||||
|
||||
```yaml
|
||||
managedSecretReference:
|
||||
secretName: managed-secret
|
||||
secretNamespace: default
|
||||
template:
|
||||
includeAllSecrets: true
|
||||
data:
|
||||
# Create new secret key that doesn't exist in your Infisical project using values of other secrets
|
||||
NEW_KEY: "{{ .DB_PASSWORD.Value }}"
|
||||
# Override an existing secret key in Infisical project with a new value using values of other secrets
|
||||
API_URL: "https://api.{{.COMPANY_NAME.Value}}.{{.REGION.Value}}.com"
|
||||
managedKubeSecretReferences:
|
||||
- secretName: managed-secret
|
||||
secretNamespace: default
|
||||
template:
|
||||
includeAllSecrets: true
|
||||
data:
|
||||
# Create new secret key that doesn't exist in your Infisical project using values of other secrets
|
||||
NEW_KEY: "{{ .DB_PASSWORD.Value }}"
|
||||
# Override an existing secret key in Infisical project with a new value using values of other secrets
|
||||
API_URL: "https://api.{{.COMPANY_NAME.Value}}.{{.REGION.Value}}.com"
|
||||
```
|
||||
|
||||
For this example, let's assume the following secrets exist in your Infisical project:
|
||||
@ -652,13 +666,13 @@ The example below assumes that the `BINARY_KEY_BASE64` secret is stored as a bas
|
||||
The resulting managed secret will contain the decoded value of `BINARY_KEY_BASE64`.
|
||||
|
||||
```yaml
|
||||
managedSecretReference:
|
||||
secretName: managed-secret
|
||||
secretNamespace: default
|
||||
template:
|
||||
includeAllSecrets: true
|
||||
data:
|
||||
BINARY_KEY: "{{ decodeBase64ToBytes .BINARY_KEY_BASE64.Value }}"
|
||||
managedKubeSecretReferences:
|
||||
secretName: managed-secret
|
||||
secretNamespace: default
|
||||
template:
|
||||
includeAllSecrets: true
|
||||
data:
|
||||
BINARY_KEY: "{{ decodeBase64ToBytes .BINARY_KEY_BASE64.Value }}"
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
@ -913,7 +927,7 @@ spec:
|
||||
..
|
||||
authentication:
|
||||
...
|
||||
managedSecretReference:
|
||||
managedKubeSecretReferences:
|
||||
...
|
||||
```
|
||||
|
||||
@ -934,4 +948,4 @@ metadata:
|
||||
type: Opaque
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
</Accordion>
|
@ -347,16 +347,26 @@
|
||||
"group": "App Connections",
|
||||
"pages": [
|
||||
"integrations/app-connections/overview",
|
||||
"integrations/app-connections/aws",
|
||||
"integrations/app-connections/github"
|
||||
{
|
||||
"group": "Connections",
|
||||
"pages": [
|
||||
"integrations/app-connections/aws",
|
||||
"integrations/app-connections/github"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Secret Syncs",
|
||||
"pages": [
|
||||
"integrations/secret-syncs/overview",
|
||||
"integrations/secret-syncs/aws-parameter-store",
|
||||
"integrations/secret-syncs/github"
|
||||
{
|
||||
"group": "Syncs",
|
||||
"pages": [
|
||||
"integrations/secret-syncs/aws-parameter-store",
|
||||
"integrations/secret-syncs/github"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -34,12 +34,6 @@ From local development to production, Infisical SDKs provide the easiest way for
|
||||
## FAQ
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Isn't it inefficient if my app makes a request every time it needs a secret?">
|
||||
The client SDK caches every secret and implements a 5-minute waiting period before re-requesting it. The waiting period can be controlled by
|
||||
setting the `cacheTTL` parameter at the time of initializing the client.
|
||||
|
||||
Note: The exact parameter name may differ depending on the language.
|
||||
</Accordion>
|
||||
<Accordion title="What if a request for a secret fails?">
|
||||
The SDK caches every secret and falls back to the cached value if a request fails. If no cached
|
||||
value ever-existed, the SDK falls back to whatever value is on the process environment.
|
||||
|
@ -16,5 +16,6 @@ export {
|
||||
useGetIntegrationAuthTeamCityBuildConfigs,
|
||||
useGetIntegrationAuthTeams,
|
||||
useGetIntegrationAuthVercelBranches,
|
||||
useGetIntegrationAuthVercelCustomEnvironments,
|
||||
useSaveIntegrationAccessToken
|
||||
} from "./queries";
|
||||
|
@ -21,7 +21,8 @@ import {
|
||||
Team,
|
||||
TeamCityBuildConfig,
|
||||
TGetIntegrationAuthOctopusDeployScopeValuesDTO,
|
||||
TOctopusDeployVariableSetScopeValues
|
||||
TOctopusDeployVariableSetScopeValues,
|
||||
VercelEnvironment
|
||||
} from "./types";
|
||||
|
||||
const integrationAuthKeys = {
|
||||
@ -132,7 +133,9 @@ const integrationAuthKeys = {
|
||||
}: TGetIntegrationAuthOctopusDeployScopeValuesDTO) =>
|
||||
[{ integrationAuthId }, "getIntegrationAuthOctopusDeployScopeValues", params] as const,
|
||||
getIntegrationAuthCircleCIOrganizations: (integrationAuthId: string) =>
|
||||
[{ integrationAuthId }, "getIntegrationAuthCircleCIOrganizations"] as const
|
||||
[{ integrationAuthId }, "getIntegrationAuthCircleCIOrganizations"] as const,
|
||||
getIntegrationAuthVercelCustomEnv: (integrationAuthId: string, teamId: string) =>
|
||||
[{ integrationAuthId, teamId }, "integrationAuthVercelCustomEnv"] as const
|
||||
};
|
||||
|
||||
const fetchIntegrationAuthById = async (integrationAuthId: string) => {
|
||||
@ -362,6 +365,29 @@ const fetchIntegrationAuthQoveryScopes = async ({
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const fetchIntegrationAuthVercelCustomEnvironments = async ({
|
||||
integrationAuthId,
|
||||
teamId
|
||||
}: {
|
||||
integrationAuthId: string;
|
||||
teamId: string;
|
||||
}) => {
|
||||
const {
|
||||
data: { environments }
|
||||
} = await apiRequest.get<{
|
||||
environments: {
|
||||
appId: string;
|
||||
customEnvironments: VercelEnvironment[];
|
||||
}[];
|
||||
}>(`/api/v1/integration-auth/${integrationAuthId}/vercel/custom-environments`, {
|
||||
params: {
|
||||
teamId
|
||||
}
|
||||
});
|
||||
|
||||
return environments;
|
||||
};
|
||||
|
||||
const fetchIntegrationAuthHerokuPipelines = async ({
|
||||
integrationAuthId
|
||||
}: {
|
||||
@ -730,6 +756,24 @@ export const useGetIntegrationAuthQoveryScopes = ({
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetIntegrationAuthVercelCustomEnvironments = ({
|
||||
integrationAuthId,
|
||||
teamId
|
||||
}: {
|
||||
integrationAuthId: string;
|
||||
teamId: string;
|
||||
}) => {
|
||||
return useQuery({
|
||||
queryKey: integrationAuthKeys.getIntegrationAuthVercelCustomEnv(integrationAuthId, teamId),
|
||||
queryFn: () =>
|
||||
fetchIntegrationAuthVercelCustomEnvironments({
|
||||
integrationAuthId,
|
||||
teamId
|
||||
}),
|
||||
enabled: Boolean(teamId && integrationAuthId)
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetIntegrationAuthHerokuPipelines = ({
|
||||
integrationAuthId
|
||||
}: {
|
||||
|
@ -43,6 +43,11 @@ export type Environment = {
|
||||
environmentId: string;
|
||||
};
|
||||
|
||||
export type VercelEnvironment = {
|
||||
id: string;
|
||||
slug: string;
|
||||
};
|
||||
|
||||
export type ChecklyGroup = {
|
||||
name: string;
|
||||
groupId: number;
|
||||
|
@ -289,34 +289,36 @@ export const SecretSyncsTable = ({ secretSyncs }: Props) => {
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="thin-scrollbar max-h-[70vh] overflow-y-auto" align="end">
|
||||
<DropdownMenuLabel>Status</DropdownMenuLabel>
|
||||
{Object.values(SecretSyncStatus).map((status) => (
|
||||
<DropdownMenuItem
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setFilters((prev) => ({
|
||||
...prev,
|
||||
status: prev.status.includes(status)
|
||||
? prev.status.filter((s) => s !== status)
|
||||
: [...prev.status, status]
|
||||
}));
|
||||
}}
|
||||
key={status}
|
||||
icon={
|
||||
filters.status.includes(status) && (
|
||||
<FontAwesomeIcon className="text-primary" icon={faCheckCircle} />
|
||||
)
|
||||
}
|
||||
iconPos="right"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<FontAwesomeIcon
|
||||
icon={STATUS_ICON_MAP[status].icon}
|
||||
className={STATUS_ICON_MAP[status].className}
|
||||
/>
|
||||
<span className="capitalize">{STATUS_ICON_MAP[status].name}</span>
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
{[SecretSyncStatus.Running, SecretSyncStatus.Succeeded, SecretSyncStatus.Failed].map(
|
||||
(status) => (
|
||||
<DropdownMenuItem
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setFilters((prev) => ({
|
||||
...prev,
|
||||
status: prev.status.includes(status)
|
||||
? prev.status.filter((s) => s !== status)
|
||||
: [...prev.status, status]
|
||||
}));
|
||||
}}
|
||||
key={status}
|
||||
icon={
|
||||
filters.status.includes(status) && (
|
||||
<FontAwesomeIcon className="text-primary" icon={faCheckCircle} />
|
||||
)
|
||||
}
|
||||
iconPos="right"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<FontAwesomeIcon
|
||||
icon={STATUS_ICON_MAP[status].icon}
|
||||
className={STATUS_ICON_MAP[status].className}
|
||||
/>
|
||||
<span className="capitalize">{STATUS_ICON_MAP[status].name}</span>
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
)
|
||||
)}
|
||||
<DropdownMenuLabel>Service</DropdownMenuLabel>
|
||||
{secretSyncs.length ? (
|
||||
[...new Set(secretSyncs.map(({ destination }) => destination))].map((destination) => {
|
||||
|
@ -180,10 +180,12 @@ export const SecretListView = ({
|
||||
|
||||
try {
|
||||
// personal secret change
|
||||
let personalAction = false;
|
||||
if (overrideAction === "deleted") {
|
||||
await handleSecretOperation("delete", SecretType.Personal, oldKey, {
|
||||
secretId: orgSecret.idOverride
|
||||
});
|
||||
personalAction = true;
|
||||
} else if (overrideAction && idOverride) {
|
||||
await handleSecretOperation("update", SecretType.Personal, oldKey, {
|
||||
value: valueOverride,
|
||||
@ -191,14 +193,16 @@ export const SecretListView = ({
|
||||
secretId: orgSecret.idOverride,
|
||||
skipMultilineEncoding: modSecret.skipMultilineEncoding
|
||||
});
|
||||
personalAction = true;
|
||||
} else if (overrideAction) {
|
||||
await handleSecretOperation("create", SecretType.Personal, oldKey, {
|
||||
value: valueOverride
|
||||
});
|
||||
personalAction = true;
|
||||
}
|
||||
|
||||
// shared secret change
|
||||
if (!isSharedSecUnchanged) {
|
||||
if (!isSharedSecUnchanged && !personalAction) {
|
||||
await handleSecretOperation("update", SecretType.Shared, oldKey, {
|
||||
value,
|
||||
tags: tagIds,
|
||||
@ -232,10 +236,11 @@ export const SecretListView = ({
|
||||
});
|
||||
handlePopUpClose("secretDetail");
|
||||
createNotification({
|
||||
type: isProtectedBranch ? "info" : "success",
|
||||
text: isProtectedBranch
|
||||
? "Requested changes have been sent for review"
|
||||
: "Successfully saved secrets"
|
||||
type: isProtectedBranch && !personalAction ? "info" : "success",
|
||||
text:
|
||||
isProtectedBranch && !personalAction
|
||||
? "Requested changes have been sent for review"
|
||||
: "Successfully saved secrets"
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
@ -283,7 +288,12 @@ export const SecretListView = ({
|
||||
text: "Failed to delete secret"
|
||||
});
|
||||
}
|
||||
}, [(popUp.deleteSecret?.data as SecretV3RawSanitized)?.key, environment, secretPath]);
|
||||
}, [
|
||||
(popUp.deleteSecret?.data as SecretV3RawSanitized)?.key,
|
||||
environment,
|
||||
secretPath,
|
||||
isProtectedBranch
|
||||
]);
|
||||
|
||||
// for optimization on minimise re-rendering of secret items
|
||||
const onCreateTag = useCallback(() => handlePopUpOpen("createTag"), []);
|
||||
|
@ -4,7 +4,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { format } from "date-fns";
|
||||
|
||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||
import { SecretSyncLabel } from "@app/components/secret-syncs";
|
||||
import { SecretSyncLabel, SecretSyncStatusBadge } from "@app/components/secret-syncs";
|
||||
import { IconButton } from "@app/components/v2";
|
||||
import { ProjectPermissionSub } from "@app/context";
|
||||
import { ProjectPermissionSecretSyncActions } from "@app/context/ProjectPermissionContext/types";
|
||||
@ -57,6 +57,11 @@ export const SecretSyncDetailsSection = ({ secretSync, onEditDetails }: Props) =
|
||||
<div className="space-y-3">
|
||||
<SecretSyncLabel label="Name">{name}</SecretSyncLabel>
|
||||
<SecretSyncLabel label="Description">{description}</SecretSyncLabel>
|
||||
{syncStatus && (
|
||||
<SecretSyncLabel label="Status">
|
||||
<SecretSyncStatusBadge status={syncStatus} />
|
||||
</SecretSyncLabel>
|
||||
)}
|
||||
{lastSyncedAt && (
|
||||
<SecretSyncLabel label="Last Synced">
|
||||
{format(new Date(lastSyncedAt), "yyyy-MM-dd, hh:mm aaa")}
|
||||
|
@ -19,7 +19,7 @@ const schema = z.object({
|
||||
environmentName: z
|
||||
.string()
|
||||
.min(1, { message: "Environment Name field must be at least 1 character" }),
|
||||
environmentSlug: slugSchema()
|
||||
environmentSlug: slugSchema({ max: 64 })
|
||||
});
|
||||
|
||||
export type FormData = z.infer<typeof schema>;
|
||||
|
@ -17,7 +17,7 @@ type Props = {
|
||||
|
||||
const schema = z.object({
|
||||
name: z.string(),
|
||||
slug: slugSchema({ min: 1 })
|
||||
slug: slugSchema({ min: 1, max: 64 })
|
||||
});
|
||||
|
||||
export type FormData = z.infer<typeof schema>;
|
||||
|
@ -99,15 +99,10 @@ export const TeamcityConfigurePage = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const filteredBuildConfigs = targetBuildConfigs?.concat({
|
||||
name: "",
|
||||
buildConfigId: ""
|
||||
});
|
||||
|
||||
return integrationAuth &&
|
||||
selectedSourceEnvironment &&
|
||||
integrationAuthApps &&
|
||||
filteredBuildConfigs &&
|
||||
targetBuildConfigs &&
|
||||
targetAppId ? (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Helmet>
|
||||
@ -185,7 +180,7 @@ export const TeamcityConfigurePage = () => {
|
||||
))
|
||||
) : (
|
||||
<SelectItem value="none" key="target-app-none">
|
||||
No project found
|
||||
No projects found
|
||||
</SelectItem>
|
||||
)}
|
||||
</Select>
|
||||
@ -195,15 +190,22 @@ export const TeamcityConfigurePage = () => {
|
||||
value={targetBuildConfigId}
|
||||
onValueChange={(val) => setTargetBuildConfigId(val)}
|
||||
className="w-full border border-mineshaft-500"
|
||||
isDisabled={targetBuildConfigs.length === 0}
|
||||
>
|
||||
{filteredBuildConfigs.map((buildConfig: any) => (
|
||||
<SelectItem
|
||||
value={buildConfig.buildConfigId}
|
||||
key={`target-build-config-${buildConfig.buildConfigId}`}
|
||||
>
|
||||
{buildConfig.name}
|
||||
{targetBuildConfigs.length ? (
|
||||
targetBuildConfigs.map((buildConfig: any) => (
|
||||
<SelectItem
|
||||
value={buildConfig.buildConfigId}
|
||||
key={`target-build-config-${buildConfig.buildConfigId}`}
|
||||
>
|
||||
{buildConfig.name}
|
||||
</SelectItem>
|
||||
))
|
||||
) : (
|
||||
<SelectItem value="none" key="target-app-none">
|
||||
No build configs found
|
||||
</SelectItem>
|
||||
))}
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<Button
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { Helmet } from "react-helmet";
|
||||
import {
|
||||
faArrowUpRightFromSquare,
|
||||
@ -25,7 +25,8 @@ import { useCreateIntegration } from "@app/hooks/api";
|
||||
import {
|
||||
useGetIntegrationAuthApps,
|
||||
useGetIntegrationAuthById,
|
||||
useGetIntegrationAuthVercelBranches
|
||||
useGetIntegrationAuthVercelBranches,
|
||||
useGetIntegrationAuthVercelCustomEnvironments
|
||||
} from "@app/hooks/api/integrationAuth";
|
||||
import { IntegrationSyncBehavior } from "@app/hooks/api/integrations/types";
|
||||
|
||||
@ -75,6 +76,11 @@ export const VercelConfigurePage = () => {
|
||||
teamId: integrationAuth?.teamId as string
|
||||
});
|
||||
|
||||
const { data: customEnvironments } = useGetIntegrationAuthVercelCustomEnvironments({
|
||||
teamId: integrationAuth?.teamId as string,
|
||||
integrationAuthId: integrationAuthId as string
|
||||
});
|
||||
|
||||
const { data: branches } = useGetIntegrationAuthVercelBranches({
|
||||
integrationAuthId: integrationAuthId as string,
|
||||
appId: targetAppId
|
||||
@ -135,6 +141,26 @@ export const VercelConfigurePage = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const selectedVercelEnvironments = useMemo(() => {
|
||||
let selectedEnvironments = vercelEnvironments;
|
||||
|
||||
const environments = customEnvironments?.find(
|
||||
(e) => e.appId === targetAppId
|
||||
)?.customEnvironments;
|
||||
|
||||
if (environments && environments.length > 0) {
|
||||
selectedEnvironments = [
|
||||
...selectedEnvironments,
|
||||
...environments.map((env) => ({
|
||||
name: env.slug,
|
||||
slug: env.id
|
||||
}))
|
||||
];
|
||||
}
|
||||
|
||||
return selectedEnvironments;
|
||||
}, [targetAppId, customEnvironments]);
|
||||
|
||||
return integrationAuth &&
|
||||
selectedSourceEnvironment &&
|
||||
integrationAuthApps &&
|
||||
@ -210,7 +236,13 @@ export const VercelConfigurePage = () => {
|
||||
>
|
||||
<Select
|
||||
value={targetAppId}
|
||||
onValueChange={(val) => setTargetAppId(val)}
|
||||
onValueChange={(val) => {
|
||||
if (vercelEnvironments.every((env) => env.slug !== targetEnvironment)) {
|
||||
setTargetEnvironment(vercelEnvironments[0].slug);
|
||||
}
|
||||
|
||||
setTargetAppId(val);
|
||||
}}
|
||||
className="w-full border border-mineshaft-500"
|
||||
isDisabled={integrationAuthApps.length === 0}
|
||||
>
|
||||
@ -236,7 +268,7 @@ export const VercelConfigurePage = () => {
|
||||
onValueChange={(val) => setTargetEnvironment(val)}
|
||||
className="w-full border border-mineshaft-500"
|
||||
>
|
||||
{vercelEnvironments.map((vercelEnvironment) => (
|
||||
{selectedVercelEnvironments.map((vercelEnvironment) => (
|
||||
<SelectItem
|
||||
value={vercelEnvironment.slug}
|
||||
key={`target-environment-${vercelEnvironment.slug}`}
|
||||
|
@ -5462,4 +5462,4 @@ export const routeTree = rootRoute
|
||||
}
|
||||
}
|
||||
}
|
||||
ROUTE_MANIFEST_END */
|
||||
ROUTE_MANIFEST_END */
|
@ -13,9 +13,9 @@ type: application
|
||||
# This is the chart version. This version number should be incremented each time you make changes
|
||||
# to the chart and its templates, including the app version.
|
||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||
version: v0.8.5
|
||||
version: v0.8.6
|
||||
# This is the version number of the application being deployed. This version number should be
|
||||
# incremented each time you make changes to the application. Versions are not expected to
|
||||
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||
# It is recommended to use it with quotes.
|
||||
appVersion: "v0.8.5"
|
||||
appVersion: "v0.8.6"
|
||||
|
@ -1,4 +1,3 @@
|
||||
{{- if .Values.installCRDs }}
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
@ -262,6 +261,48 @@ spec:
|
||||
hostAPI:
|
||||
description: Infisical host to pull secrets from
|
||||
type: string
|
||||
managedKubeSecretReferences:
|
||||
items:
|
||||
properties:
|
||||
creationPolicy:
|
||||
default: Orphan
|
||||
description: 'The Kubernetes Secret creation policy. Enum with
|
||||
values: ''Owner'', ''Orphan''. Owner creates the secret and
|
||||
sets .metadata.ownerReferences of the InfisicalSecret CRD that
|
||||
created it. Orphan will not set the secret owner. This will
|
||||
result in the secret being orphaned and not deleted when the
|
||||
resource is deleted.'
|
||||
type: string
|
||||
secretName:
|
||||
description: The name of the Kubernetes Secret
|
||||
type: string
|
||||
secretNamespace:
|
||||
description: The name space where the Kubernetes Secret is located
|
||||
type: string
|
||||
secretType:
|
||||
default: Opaque
|
||||
description: 'The Kubernetes Secret type (experimental feature).
|
||||
More info: https://kubernetes.io/docs/concepts/configuration/secret/#secret-types'
|
||||
type: string
|
||||
template:
|
||||
description: The template to transform the secret data
|
||||
properties:
|
||||
data:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: The template key values
|
||||
type: object
|
||||
includeAllSecrets:
|
||||
description: This injects all retrieved secrets into the top
|
||||
level of your template. Secrets defined in the template
|
||||
will take precedence over the injected ones.
|
||||
type: boolean
|
||||
type: object
|
||||
required:
|
||||
- secretName
|
||||
- secretNamespace
|
||||
type: object
|
||||
type: array
|
||||
managedSecretReference:
|
||||
properties:
|
||||
creationPolicy:
|
||||
@ -338,7 +379,6 @@ spec:
|
||||
- secretNamespace
|
||||
type: object
|
||||
required:
|
||||
- managedSecretReference
|
||||
- resyncInterval
|
||||
type: object
|
||||
status:
|
||||
@ -425,5 +465,4 @@ status:
|
||||
kind: ""
|
||||
plural: ""
|
||||
conditions: []
|
||||
storedVersions: []
|
||||
{{- end }}
|
||||
storedVersions: []
|
@ -32,7 +32,7 @@ controllerManager:
|
||||
- ALL
|
||||
image:
|
||||
repository: infisical/kubernetes-operator
|
||||
tag: v0.8.5
|
||||
tag: v0.8.6
|
||||
resources:
|
||||
limits:
|
||||
cpu: 500m
|
||||
|
@ -134,9 +134,12 @@ type InfisicalSecretSpec struct {
|
||||
// +kubebuilder:validation:Optional
|
||||
Authentication Authentication `json:"authentication"`
|
||||
|
||||
// +kubebuilder:validation:Required
|
||||
// +kubebuilder:validation:Optional
|
||||
ManagedSecretReference ManagedKubeSecretConfig `json:"managedSecretReference"`
|
||||
|
||||
// +kubebuilder:validation:Optional
|
||||
ManagedKubeSecretReferences []ManagedKubeSecretConfig `json:"managedKubeSecretReferences"`
|
||||
|
||||
// +kubebuilder:default:=60
|
||||
ResyncInterval int `json:"resyncInterval"`
|
||||
|
||||
|
@ -565,6 +565,13 @@ func (in *InfisicalSecretSpec) DeepCopyInto(out *InfisicalSecretSpec) {
|
||||
out.TokenSecretReference = in.TokenSecretReference
|
||||
out.Authentication = in.Authentication
|
||||
in.ManagedSecretReference.DeepCopyInto(&out.ManagedSecretReference)
|
||||
if in.ManagedKubeSecretReferences != nil {
|
||||
in, out := &in.ManagedKubeSecretReferences, &out.ManagedKubeSecretReferences
|
||||
*out = make([]ManagedKubeSecretConfig, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
out.TLS = in.TLS
|
||||
}
|
||||
|
||||
|
@ -261,6 +261,48 @@ spec:
|
||||
hostAPI:
|
||||
description: Infisical host to pull secrets from
|
||||
type: string
|
||||
managedKubeSecretReferences:
|
||||
items:
|
||||
properties:
|
||||
creationPolicy:
|
||||
default: Orphan
|
||||
description: 'The Kubernetes Secret creation policy. Enum with
|
||||
values: ''Owner'', ''Orphan''. Owner creates the secret and
|
||||
sets .metadata.ownerReferences of the InfisicalSecret CRD
|
||||
that created it. Orphan will not set the secret owner. This
|
||||
will result in the secret being orphaned and not deleted when
|
||||
the resource is deleted.'
|
||||
type: string
|
||||
secretName:
|
||||
description: The name of the Kubernetes Secret
|
||||
type: string
|
||||
secretNamespace:
|
||||
description: The name space where the Kubernetes Secret is located
|
||||
type: string
|
||||
secretType:
|
||||
default: Opaque
|
||||
description: 'The Kubernetes Secret type (experimental feature).
|
||||
More info: https://kubernetes.io/docs/concepts/configuration/secret/#secret-types'
|
||||
type: string
|
||||
template:
|
||||
description: The template to transform the secret data
|
||||
properties:
|
||||
data:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: The template key values
|
||||
type: object
|
||||
includeAllSecrets:
|
||||
description: This injects all retrieved secrets into the
|
||||
top level of your template. Secrets defined in the template
|
||||
will take precedence over the injected ones.
|
||||
type: boolean
|
||||
type: object
|
||||
required:
|
||||
- secretName
|
||||
- secretNamespace
|
||||
type: object
|
||||
type: array
|
||||
managedSecretReference:
|
||||
properties:
|
||||
creationPolicy:
|
||||
@ -339,7 +381,6 @@ spec:
|
||||
- secretNamespace
|
||||
type: object
|
||||
required:
|
||||
- managedSecretReference
|
||||
- resyncInterval
|
||||
type: object
|
||||
status:
|
||||
|
@ -97,11 +97,11 @@ spec:
|
||||
secretsPath: "/path"
|
||||
recursive: true
|
||||
|
||||
managedSecretReference:
|
||||
secretName: managed-secret
|
||||
secretNamespace: default
|
||||
creationPolicy: "Orphan" ## Owner | Orphan
|
||||
# secretType: kubernetes.io/dockerconfigjson
|
||||
managedSecretReferences:
|
||||
- secretName: managed-secret
|
||||
secretNamespace: default
|
||||
creationPolicy: "Orphan" ## Owner | Orphan
|
||||
# secretType: kubernetes.io/dockerconfigjson
|
||||
|
||||
# # To be depreciated soon
|
||||
# tokenSecretReference:
|
||||
|
@ -1,108 +0,0 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/Infisical/infisical/k8-operator/api/v1alpha1"
|
||||
"github.com/Infisical/infisical/k8-operator/packages/constants"
|
||||
"github.com/go-logr/logr"
|
||||
v1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
const DEPLOYMENT_SECRET_NAME_ANNOTATION_PREFIX = "secrets.infisical.com/managed-secret"
|
||||
const AUTO_RELOAD_DEPLOYMENT_ANNOTATION = "secrets.infisical.com/auto-reload" // needs to be set to true for a deployment to start auto redeploying
|
||||
|
||||
func (r *InfisicalSecretReconciler) ReconcileDeploymentsWithManagedSecrets(ctx context.Context, logger logr.Logger, infisicalSecret v1alpha1.InfisicalSecret) (int, error) {
|
||||
listOfDeployments := &v1.DeploymentList{}
|
||||
err := r.Client.List(ctx, listOfDeployments, &client.ListOptions{Namespace: infisicalSecret.Spec.ManagedSecretReference.SecretNamespace})
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("unable to get deployments in the [namespace=%v] [err=%v]", infisicalSecret.Spec.ManagedSecretReference.SecretNamespace, err)
|
||||
}
|
||||
|
||||
managedKubeSecretNameAndNamespace := types.NamespacedName{
|
||||
Namespace: infisicalSecret.Spec.ManagedSecretReference.SecretNamespace,
|
||||
Name: infisicalSecret.Spec.ManagedSecretReference.SecretName,
|
||||
}
|
||||
|
||||
managedKubeSecret := &corev1.Secret{}
|
||||
err = r.Client.Get(ctx, managedKubeSecretNameAndNamespace, managedKubeSecret)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("unable to fetch Kubernetes secret to update deployment: %v", err)
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
// Iterate over the deployments and check if they use the managed secret
|
||||
for _, deployment := range listOfDeployments.Items {
|
||||
deployment := deployment
|
||||
if deployment.Annotations[AUTO_RELOAD_DEPLOYMENT_ANNOTATION] == "true" && r.IsDeploymentUsingManagedSecret(deployment, infisicalSecret) {
|
||||
// Start a goroutine to reconcile the deployment
|
||||
wg.Add(1)
|
||||
go func(d v1.Deployment, s corev1.Secret) {
|
||||
defer wg.Done()
|
||||
if err := r.ReconcileDeployment(ctx, logger, d, s); err != nil {
|
||||
logger.Error(err, fmt.Sprintf("unable to reconcile deployment with [name=%v]. Will try next requeue", deployment.ObjectMeta.Name))
|
||||
}
|
||||
}(deployment, *managedKubeSecret)
|
||||
}
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// Check if the deployment uses managed secrets
|
||||
func (r *InfisicalSecretReconciler) IsDeploymentUsingManagedSecret(deployment v1.Deployment, infisicalSecret v1alpha1.InfisicalSecret) bool {
|
||||
managedSecretName := infisicalSecret.Spec.ManagedSecretReference.SecretName
|
||||
for _, container := range deployment.Spec.Template.Spec.Containers {
|
||||
for _, envFrom := range container.EnvFrom {
|
||||
if envFrom.SecretRef != nil && envFrom.SecretRef.LocalObjectReference.Name == managedSecretName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for _, env := range container.Env {
|
||||
if env.ValueFrom != nil && env.ValueFrom.SecretKeyRef != nil && env.ValueFrom.SecretKeyRef.LocalObjectReference.Name == managedSecretName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, volume := range deployment.Spec.Template.Spec.Volumes {
|
||||
if volume.Secret != nil && volume.Secret.SecretName == managedSecretName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// This function ensures that a deployment is in sync with a Kubernetes secret by comparing their versions.
|
||||
// If the version of the secret is different from the version annotation on the deployment, the annotation is updated to trigger a restart of the deployment.
|
||||
func (r *InfisicalSecretReconciler) ReconcileDeployment(ctx context.Context, logger logr.Logger, deployment v1.Deployment, secret corev1.Secret) error {
|
||||
annotationKey := fmt.Sprintf("%s.%s", DEPLOYMENT_SECRET_NAME_ANNOTATION_PREFIX, secret.Name)
|
||||
annotationValue := secret.Annotations[constants.SECRET_VERSION_ANNOTATION]
|
||||
|
||||
if deployment.Annotations[annotationKey] == annotationValue &&
|
||||
deployment.Spec.Template.Annotations[annotationKey] == annotationValue {
|
||||
logger.Info(fmt.Sprintf("The [deploymentName=%v] is already using the most up to date managed secrets. No action required.", deployment.ObjectMeta.Name))
|
||||
return nil
|
||||
}
|
||||
|
||||
logger.Info(fmt.Sprintf("Deployment is using outdated managed secret. Starting re-deployment [deploymentName=%v]", deployment.ObjectMeta.Name))
|
||||
|
||||
if deployment.Spec.Template.Annotations == nil {
|
||||
deployment.Spec.Template.Annotations = make(map[string]string)
|
||||
}
|
||||
|
||||
deployment.Annotations[annotationKey] = annotationValue
|
||||
deployment.Spec.Template.Annotations[annotationKey] = annotationValue
|
||||
|
||||
if err := r.Client.Update(ctx, &deployment); err != nil {
|
||||
return fmt.Errorf("failed to update deployment annotation: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
@ -13,6 +13,8 @@ import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/event"
|
||||
"sigs.k8s.io/controller-runtime/pkg/predicate"
|
||||
|
||||
defaultErrors "errors"
|
||||
|
||||
secretsv1alpha1 "github.com/Infisical/infisical/k8-operator/api/v1alpha1"
|
||||
"github.com/Infisical/infisical/k8-operator/packages/api"
|
||||
controllerhelpers "github.com/Infisical/infisical/k8-operator/packages/controllerhelpers"
|
||||
@ -35,8 +37,6 @@ func (r *InfisicalSecretReconciler) GetLogger(req ctrl.Request) logr.Logger {
|
||||
return r.BaseLogger.WithValues("infisicalsecret", req.NamespacedName)
|
||||
}
|
||||
|
||||
var resourceVariablesMap map[string]util.ResourceVariables = make(map[string]util.ResourceVariables)
|
||||
|
||||
//+kubebuilder:rbac:groups=secrets.infisical.com,resources=infisicalsecrets,verbs=get;list;watch;create;update;patch;delete
|
||||
//+kubebuilder:rbac:groups=secrets.infisical.com,resources=infisicalsecrets/status,verbs=get;update;patch
|
||||
//+kubebuilder:rbac:groups=secrets.infisical.com,resources=infisicalsecrets/finalizers,verbs=update
|
||||
@ -71,6 +71,30 @@ func (r *InfisicalSecretReconciler) Reconcile(ctx context.Context, req ctrl.Requ
|
||||
}
|
||||
}
|
||||
|
||||
// It's important we don't directly modify the CRD object, so we create a copy of it and move existing data into it.
|
||||
managedKubeSecretReferences := infisicalSecretCRD.Spec.ManagedKubeSecretReferences
|
||||
|
||||
if infisicalSecretCRD.Spec.ManagedSecretReference.SecretName != "" && managedKubeSecretReferences != nil && len(managedKubeSecretReferences) > 0 {
|
||||
errMessage := "InfisicalSecret CRD cannot have both managedSecretReference and managedKubeSecretReferences"
|
||||
logger.Error(defaultErrors.New(errMessage), errMessage)
|
||||
return ctrl.Result{}, defaultErrors.New(errMessage)
|
||||
}
|
||||
|
||||
if infisicalSecretCRD.Spec.ManagedSecretReference.SecretName != "" {
|
||||
logger.Info("\n\n\nThe field `managedSecretReference` will be deprecated in the near future, please use `managedKubeSecretReferences` instead.\n\nRefer to the documentation for more information: https://infisical.com/docs/integrations/platforms/kubernetes/infisical-secret-crd\n\n\n")
|
||||
|
||||
if managedKubeSecretReferences == nil {
|
||||
managedKubeSecretReferences = []secretsv1alpha1.ManagedKubeSecretConfig{}
|
||||
}
|
||||
managedKubeSecretReferences = append(managedKubeSecretReferences, infisicalSecretCRD.Spec.ManagedSecretReference)
|
||||
}
|
||||
|
||||
if len(managedKubeSecretReferences) == 0 {
|
||||
errMessage := "InfisicalSecret CRD must have at least one managed secret reference set in the `managedKubeSecretReferences` field"
|
||||
logger.Error(defaultErrors.New(errMessage), errMessage)
|
||||
return ctrl.Result{}, defaultErrors.New(errMessage)
|
||||
}
|
||||
|
||||
// Remove finalizers if they exist. This is to support previous InfisicalSecret CRD's that have finalizers on them.
|
||||
// In order to delete secrets with finalizers, we first remove the finalizers so we can use the simplified and improved deletion process
|
||||
if !infisicalSecretCRD.ObjectMeta.DeletionTimestamp.IsZero() && len(infisicalSecretCRD.ObjectMeta.Finalizers) > 0 {
|
||||
@ -127,7 +151,7 @@ func (r *InfisicalSecretReconciler) Reconcile(ctx context.Context, req ctrl.Requ
|
||||
api.API_CA_CERTIFICATE = ""
|
||||
}
|
||||
|
||||
err = r.ReconcileInfisicalSecret(ctx, logger, infisicalSecretCRD)
|
||||
err = r.ReconcileInfisicalSecret(ctx, logger, infisicalSecretCRD, managedKubeSecretReferences)
|
||||
r.SetReadyToSyncSecretsConditions(ctx, &infisicalSecretCRD, err)
|
||||
|
||||
if err != nil {
|
||||
@ -138,7 +162,7 @@ func (r *InfisicalSecretReconciler) Reconcile(ctx context.Context, req ctrl.Requ
|
||||
}, nil
|
||||
}
|
||||
|
||||
numDeployments, err := controllerhelpers.ReconcileDeploymentsWithManagedSecrets(ctx, r.Client, logger, infisicalSecretCRD.Spec.ManagedSecretReference)
|
||||
numDeployments, err := controllerhelpers.ReconcileDeploymentsWithMultipleManagedSecrets(ctx, r.Client, logger, managedKubeSecretReferences)
|
||||
r.SetInfisicalAutoRedeploymentReady(ctx, logger, &infisicalSecretCRD, numDeployments, err)
|
||||
if err != nil {
|
||||
logger.Error(err, fmt.Sprintf("unable to reconcile auto redeployment. Will requeue after [requeueTime=%v]", requeueTime))
|
||||
|
@ -165,10 +165,10 @@ var infisicalSecretTemplateFunctions = template.FuncMap{
|
||||
},
|
||||
}
|
||||
|
||||
func (r *InfisicalSecretReconciler) createInfisicalManagedKubeSecret(ctx context.Context, logger logr.Logger, infisicalSecret v1alpha1.InfisicalSecret, secretsFromAPI []model.SingleEnvironmentVariable, ETag string) error {
|
||||
func (r *InfisicalSecretReconciler) createInfisicalManagedKubeSecret(ctx context.Context, logger logr.Logger, infisicalSecret v1alpha1.InfisicalSecret, managedSecretReference v1alpha1.ManagedKubeSecretConfig, secretsFromAPI []model.SingleEnvironmentVariable, ETag string) error {
|
||||
plainProcessedSecrets := make(map[string][]byte)
|
||||
secretType := infisicalSecret.Spec.ManagedSecretReference.SecretType
|
||||
managedTemplateData := infisicalSecret.Spec.ManagedSecretReference.Template
|
||||
secretType := managedSecretReference.SecretType
|
||||
managedTemplateData := managedSecretReference.Template
|
||||
|
||||
if managedTemplateData == nil || managedTemplateData.IncludeAllSecrets {
|
||||
for _, secret := range secretsFromAPI {
|
||||
@ -226,8 +226,8 @@ func (r *InfisicalSecretReconciler) createInfisicalManagedKubeSecret(ctx context
|
||||
// create a new secret as specified by the managed secret spec of CRD
|
||||
newKubeSecretInstance := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: infisicalSecret.Spec.ManagedSecretReference.SecretName,
|
||||
Namespace: infisicalSecret.Spec.ManagedSecretReference.SecretNamespace,
|
||||
Name: managedSecretReference.SecretName,
|
||||
Namespace: managedSecretReference.SecretNamespace,
|
||||
Annotations: annotations,
|
||||
Labels: labels,
|
||||
},
|
||||
@ -235,7 +235,7 @@ func (r *InfisicalSecretReconciler) createInfisicalManagedKubeSecret(ctx context
|
||||
Data: plainProcessedSecrets,
|
||||
}
|
||||
|
||||
if infisicalSecret.Spec.ManagedSecretReference.CreationPolicy == "Owner" {
|
||||
if managedSecretReference.CreationPolicy == "Owner" {
|
||||
// Set InfisicalSecret instance as the owner and controller of the managed secret
|
||||
err := ctrl.SetControllerReference(&infisicalSecret, newKubeSecretInstance, r.Scheme)
|
||||
if err != nil {
|
||||
@ -252,8 +252,8 @@ func (r *InfisicalSecretReconciler) createInfisicalManagedKubeSecret(ctx context
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *InfisicalSecretReconciler) updateInfisicalManagedKubeSecret(ctx context.Context, logger logr.Logger, infisicalSecret v1alpha1.InfisicalSecret, managedKubeSecret corev1.Secret, secretsFromAPI []model.SingleEnvironmentVariable, ETag string) error {
|
||||
managedTemplateData := infisicalSecret.Spec.ManagedSecretReference.Template
|
||||
func (r *InfisicalSecretReconciler) updateInfisicalManagedKubeSecret(ctx context.Context, logger logr.Logger, managedSecretReference v1alpha1.ManagedKubeSecretConfig, managedKubeSecret corev1.Secret, secretsFromAPI []model.SingleEnvironmentVariable, ETag string) error {
|
||||
managedTemplateData := managedSecretReference.Template
|
||||
|
||||
plainProcessedSecrets := make(map[string][]byte)
|
||||
if managedTemplateData == nil || managedTemplateData.IncludeAllSecrets {
|
||||
@ -337,7 +337,7 @@ func (r *InfisicalSecretReconciler) updateResourceVariables(infisicalSecret v1al
|
||||
infisicalSecretResourceVariablesMap[string(infisicalSecret.UID)] = resourceVariables
|
||||
}
|
||||
|
||||
func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context, logger logr.Logger, infisicalSecret v1alpha1.InfisicalSecret) error {
|
||||
func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context, logger logr.Logger, infisicalSecret v1alpha1.InfisicalSecret, managedKubeSecretReferences []v1alpha1.ManagedKubeSecretConfig) error {
|
||||
|
||||
resourceVariables := r.getResourceVariables(infisicalSecret)
|
||||
infisicalClient := resourceVariables.InfisicalClient
|
||||
@ -361,72 +361,84 @@ func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context
|
||||
})
|
||||
}
|
||||
|
||||
// Look for managed secret by name and namespace
|
||||
managedKubeSecret, err := util.GetKubeSecretByNamespacedName(ctx, r.Client, types.NamespacedName{
|
||||
Name: infisicalSecret.Spec.ManagedSecretReference.SecretName,
|
||||
Namespace: infisicalSecret.Spec.ManagedSecretReference.SecretNamespace,
|
||||
})
|
||||
for _, managedSecretReference := range managedKubeSecretReferences {
|
||||
// Look for managed secret by name and namespace
|
||||
managedKubeSecret, err := util.GetKubeSecretByNamespacedName(ctx, r.Client, types.NamespacedName{
|
||||
Name: managedSecretReference.SecretName,
|
||||
Namespace: managedSecretReference.SecretNamespace,
|
||||
})
|
||||
|
||||
if err != nil && !k8Errors.IsNotFound(err) {
|
||||
return fmt.Errorf("something went wrong when fetching the managed Kubernetes secret [%w]", err)
|
||||
}
|
||||
|
||||
// Get exiting Etag if exists
|
||||
secretVersionBasedOnETag := ""
|
||||
if managedKubeSecret != nil {
|
||||
secretVersionBasedOnETag = managedKubeSecret.Annotations[constants.SECRET_VERSION_ANNOTATION]
|
||||
}
|
||||
|
||||
var plainTextSecretsFromApi []model.SingleEnvironmentVariable
|
||||
var updateDetails model.RequestUpdateUpdateDetails
|
||||
|
||||
if authDetails.AuthStrategy == util.AuthStrategy.SERVICE_ACCOUNT { // Service Account // ! Legacy auth method
|
||||
serviceAccountCreds, err := r.getInfisicalServiceAccountCredentialsFromKubeSecret(ctx, infisicalSecret)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ReconcileInfisicalSecret: unable to get service account creds from kube secret [err=%s]", err)
|
||||
}
|
||||
|
||||
plainTextSecretsFromApi, updateDetails, err = util.GetPlainTextSecretsViaServiceAccount(infisicalClient, serviceAccountCreds, infisicalSecret.Spec.Authentication.ServiceAccount.ProjectId, infisicalSecret.Spec.Authentication.ServiceAccount.EnvironmentName, secretVersionBasedOnETag)
|
||||
if err != nil {
|
||||
return fmt.Errorf("\nfailed to get secrets because [err=%v]", err)
|
||||
}
|
||||
|
||||
logger.Info("ReconcileInfisicalSecret: Fetched secrets via service account")
|
||||
|
||||
} else if authDetails.AuthStrategy == util.AuthStrategy.SERVICE_TOKEN { // Service Tokens // ! Legacy / Deprecated auth method
|
||||
infisicalToken, err := r.getInfisicalTokenFromKubeSecret(ctx, infisicalSecret)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ReconcileInfisicalSecret: unable to get service token from kube secret [err=%s]", err)
|
||||
}
|
||||
|
||||
envSlug := infisicalSecret.Spec.Authentication.ServiceToken.SecretsScope.EnvSlug
|
||||
secretsPath := infisicalSecret.Spec.Authentication.ServiceToken.SecretsScope.SecretsPath
|
||||
recursive := infisicalSecret.Spec.Authentication.ServiceToken.SecretsScope.Recursive
|
||||
|
||||
plainTextSecretsFromApi, updateDetails, err = util.GetPlainTextSecretsViaServiceToken(infisicalClient, infisicalToken, secretVersionBasedOnETag, envSlug, secretsPath, recursive)
|
||||
if err != nil {
|
||||
return fmt.Errorf("\nfailed to get secrets because [err=%v]", err)
|
||||
}
|
||||
|
||||
logger.Info("ReconcileInfisicalSecret: Fetched secrets via [type=SERVICE_TOKEN]")
|
||||
|
||||
} else if authDetails.IsMachineIdentityAuth { // * Machine Identity authentication, the SDK will be authenticated at this point
|
||||
plainTextSecretsFromApi, updateDetails, err = util.GetPlainTextSecretsViaMachineIdentity(infisicalClient, secretVersionBasedOnETag, authDetails.MachineIdentityScope)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("\nfailed to get secrets because [err=%v]", err)
|
||||
}
|
||||
|
||||
logger.Info(fmt.Sprintf("ReconcileInfisicalSecret: Fetched secrets via machine identity [type=%v]", authDetails.AuthStrategy))
|
||||
|
||||
} else {
|
||||
return errors.New("no authentication method provided yet. Please configure a authentication method then try again")
|
||||
}
|
||||
|
||||
if managedKubeSecret == nil {
|
||||
return r.createInfisicalManagedKubeSecret(ctx, logger, infisicalSecret, plainTextSecretsFromApi, updateDetails.ETag)
|
||||
} else {
|
||||
return r.updateInfisicalManagedKubeSecret(ctx, logger, infisicalSecret, *managedKubeSecret, plainTextSecretsFromApi, updateDetails.ETag)
|
||||
if err != nil && !k8Errors.IsNotFound(err) {
|
||||
return fmt.Errorf("something went wrong when fetching the managed Kubernetes secret [%w]", err)
|
||||
}
|
||||
|
||||
// Get exiting Etag if exists
|
||||
secretVersionBasedOnETag := ""
|
||||
if managedKubeSecret != nil {
|
||||
secretVersionBasedOnETag = managedKubeSecret.Annotations[constants.SECRET_VERSION_ANNOTATION]
|
||||
}
|
||||
|
||||
var plainTextSecretsFromApi []model.SingleEnvironmentVariable
|
||||
var updateDetails model.RequestUpdateUpdateDetails
|
||||
|
||||
if authDetails.AuthStrategy == util.AuthStrategy.SERVICE_ACCOUNT { // Service Account // ! Legacy auth method
|
||||
serviceAccountCreds, err := r.getInfisicalServiceAccountCredentialsFromKubeSecret(ctx, infisicalSecret)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ReconcileInfisicalSecret: unable to get service account creds from kube secret [err=%s]", err)
|
||||
}
|
||||
|
||||
plainTextSecretsFromApi, updateDetails, err = util.GetPlainTextSecretsViaServiceAccount(infisicalClient, serviceAccountCreds, infisicalSecret.Spec.Authentication.ServiceAccount.ProjectId, infisicalSecret.Spec.Authentication.ServiceAccount.EnvironmentName, secretVersionBasedOnETag)
|
||||
if err != nil {
|
||||
return fmt.Errorf("\nfailed to get secrets because [err=%v]", err)
|
||||
}
|
||||
|
||||
logger.Info("ReconcileInfisicalSecret: Fetched secrets via service account")
|
||||
|
||||
} else if authDetails.AuthStrategy == util.AuthStrategy.SERVICE_TOKEN { // Service Tokens // ! Legacy / Deprecated auth method
|
||||
infisicalToken, err := r.getInfisicalTokenFromKubeSecret(ctx, infisicalSecret)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ReconcileInfisicalSecret: unable to get service token from kube secret [err=%s]", err)
|
||||
}
|
||||
|
||||
envSlug := infisicalSecret.Spec.Authentication.ServiceToken.SecretsScope.EnvSlug
|
||||
secretsPath := infisicalSecret.Spec.Authentication.ServiceToken.SecretsScope.SecretsPath
|
||||
recursive := infisicalSecret.Spec.Authentication.ServiceToken.SecretsScope.Recursive
|
||||
|
||||
plainTextSecretsFromApi, updateDetails, err = util.GetPlainTextSecretsViaServiceToken(infisicalClient, infisicalToken, secretVersionBasedOnETag, envSlug, secretsPath, recursive)
|
||||
if err != nil {
|
||||
return fmt.Errorf("\nfailed to get secrets because [err=%v]", err)
|
||||
}
|
||||
|
||||
logger.Info("ReconcileInfisicalSecret: Fetched secrets via [type=SERVICE_TOKEN]")
|
||||
|
||||
} else if authDetails.IsMachineIdentityAuth { // * Machine Identity authentication, the SDK will be authenticated at this point
|
||||
plainTextSecretsFromApi, updateDetails, err = util.GetPlainTextSecretsViaMachineIdentity(infisicalClient, secretVersionBasedOnETag, authDetails.MachineIdentityScope)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("\nfailed to get secrets because [err=%v]", err)
|
||||
}
|
||||
|
||||
logger.Info(fmt.Sprintf("ReconcileInfisicalSecret: Fetched secrets via machine identity [type=%v]", authDetails.AuthStrategy))
|
||||
|
||||
} else {
|
||||
return errors.New("no authentication method provided. Please configure a authentication method then try again")
|
||||
}
|
||||
|
||||
if !updateDetails.Modified {
|
||||
logger.Info("ReconcileInfisicalSecret: No secrets modified so reconcile not needed")
|
||||
continue
|
||||
}
|
||||
|
||||
if managedKubeSecret == nil {
|
||||
if err := r.createInfisicalManagedKubeSecret(ctx, logger, infisicalSecret, managedSecretReference, plainTextSecretsFromApi, updateDetails.ETag); err != nil {
|
||||
return fmt.Errorf("failed to create managed secret [err=%s]", err)
|
||||
}
|
||||
} else {
|
||||
if err := r.updateInfisicalManagedKubeSecret(ctx, logger, managedSecretReference, *managedKubeSecret, plainTextSecretsFromApi, updateDetails.ETag); err != nil {
|
||||
return fmt.Errorf("failed to update managed secret [err=%s]", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -59,6 +59,17 @@ func ReconcileDeploymentsWithManagedSecrets(ctx context.Context, client controll
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func ReconcileDeploymentsWithMultipleManagedSecrets(ctx context.Context, client controllerClient.Client, logger logr.Logger, managedSecrets []v1alpha1.ManagedKubeSecretConfig) (int, error) {
|
||||
for _, managedSecret := range managedSecrets {
|
||||
_, err := ReconcileDeploymentsWithManagedSecrets(ctx, client, logger, managedSecret)
|
||||
if err != nil {
|
||||
logger.Error(err, fmt.Sprintf("unable to reconcile deployments with managed secret [name=%v]", managedSecret.SecretName))
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// Check if the deployment uses managed secrets
|
||||
func IsDeploymentUsingManagedSecret(deployment v1.Deployment, managedSecret v1alpha1.ManagedKubeSecretConfig) bool {
|
||||
managedSecretName := managedSecret.SecretName
|
||||
|
Reference in New Issue
Block a user