Skip to content

feat(dynamic-secrets): GCP IAM #3552

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,10 @@ export const dynamicSecretLeaseServiceFactory = ({
const expireAt = new Date(dynamicSecretLease.expireAt.getTime() + ms(selectedTTL));
if (maxTTL) {
const maxExpiryDate = new Date(dynamicSecretLease.createdAt.getTime() + ms(maxTTL));
if (expireAt > maxExpiryDate) throw new BadRequestError({ message: "TTL cannot be larger than max ttl" });
if (expireAt > maxExpiryDate)
throw new BadRequestError({
message: "The requested renewal would exceed the maximum allowed lease duration. Please choose a shorter TTL"
});
}

const { entityId } = await selectedProvider.renew(
Expand Down
103 changes: 103 additions & 0 deletions backend/src/ee/services/dynamic-secret/providers/gcp-iam.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { gaxios, Impersonated, JWT } from "google-auth-library";
import { GetAccessTokenResponse } from "google-auth-library/build/src/auth/oauth2client";

import { getConfig } from "@app/lib/config/env";
import { BadRequestError, InternalServerError } from "@app/lib/errors";
import { alphaNumericNanoId } from "@app/lib/nanoid";

import { DynamicSecretGcpIamSchema, TDynamicProviderFns } from "./models";

export const GcpIamProvider = (): TDynamicProviderFns => {
const validateProviderInputs = async (inputs: unknown) => {
const providerInputs = await DynamicSecretGcpIamSchema.parseAsync(inputs);
return providerInputs;
};

const $getToken = async (serviceAccountEmail: string, ttl: number): Promise<string> => {
const appCfg = getConfig();
if (!appCfg.INF_APP_CONNECTION_GCP_SERVICE_ACCOUNT_CREDENTIAL) {
throw new InternalServerError({
message: "Environment variable has not been configured: INF_APP_CONNECTION_GCP_SERVICE_ACCOUNT_CREDENTIAL"
});
}

const credJson = JSON.parse(appCfg.INF_APP_CONNECTION_GCP_SERVICE_ACCOUNT_CREDENTIAL) as {
client_email: string;
private_key: string;
};

const sourceClient = new JWT({
email: credJson.client_email,
key: credJson.private_key,
scopes: ["https://www.googleapis.com/auth/cloud-platform"]
});

const impersonatedCredentials = new Impersonated({
sourceClient,
targetPrincipal: serviceAccountEmail,
lifetime: ttl,
delegates: [],
targetScopes: ["https://www.googleapis.com/auth/iam", "https://www.googleapis.com/auth/cloud-platform"]
});

let tokenResponse: GetAccessTokenResponse | undefined;
try {
tokenResponse = await impersonatedCredentials.getAccessToken();
} catch (error) {
let message = "Unable to validate connection";
if (error instanceof gaxios.GaxiosError) {
message = error.message;
}

throw new BadRequestError({
message
});
}

if (!tokenResponse || !tokenResponse.token) {
throw new BadRequestError({
message: "Unable to validate connection"
});
}

return tokenResponse.token;
};

const validateConnection = async (inputs: unknown) => {
const providerInputs = await validateProviderInputs(inputs);
await $getToken(providerInputs.serviceAccountEmail, 10);
return true;
};

const create = async (inputs: unknown, expireAt: number) => {
const providerInputs = await validateProviderInputs(inputs);

const now = Math.floor(Date.now() / 1000);
const ttl = Math.max(Math.floor(expireAt / 1000) - now, 0);

const token = await $getToken(providerInputs.serviceAccountEmail, ttl);
const entityId = alphaNumericNanoId(32);

return { entityId, data: { SERVICE_ACCOUNT_EMAIL: providerInputs.serviceAccountEmail, TOKEN: token } };
};

const revoke = async (_inputs: unknown, entityId: string) => {
// There's no way to revoke GCP IAM access tokens
return { entityId };
};

const renew = async (inputs: unknown, entityId: string, expireAt: number) => {
// To renew a token it must be re-created
const data = await create(inputs, expireAt);

return { ...data, entityId };
};

return {
validateProviderInputs,
validateConnection,
create,
revoke,
renew
};
};
4 changes: 3 additions & 1 deletion backend/src/ee/services/dynamic-secret/providers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { AwsIamProvider } from "./aws-iam";
import { AzureEntraIDProvider } from "./azure-entra-id";
import { CassandraProvider } from "./cassandra";
import { ElasticSearchProvider } from "./elastic-search";
import { GcpIamProvider } from "./gcp-iam";
import { LdapProvider } from "./ldap";
import { DynamicSecretProviders, TDynamicProviderFns } from "./models";
import { MongoAtlasProvider } from "./mongo-atlas";
Expand Down Expand Up @@ -38,5 +39,6 @@ export const buildDynamicSecretProviders = ({
[DynamicSecretProviders.SapHana]: SapHanaProvider(),
[DynamicSecretProviders.Snowflake]: SnowflakeProvider(),
[DynamicSecretProviders.Totp]: TotpProvider(),
[DynamicSecretProviders.SapAse]: SapAseProvider()
[DynamicSecretProviders.SapAse]: SapAseProvider(),
[DynamicSecretProviders.GcpIam]: GcpIamProvider()
});
10 changes: 8 additions & 2 deletions backend/src/ee/services/dynamic-secret/providers/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,10 @@ export const DynamicSecretTotpSchema = z.discriminatedUnion("configType", [
})
]);

export const DynamicSecretGcpIamSchema = z.object({
serviceAccountEmail: z.string().email().trim().min(1, "Service account email required")
});

export enum DynamicSecretProviders {
SqlDatabase = "sql-database",
Cassandra = "cassandra",
Expand All @@ -320,7 +324,8 @@ export enum DynamicSecretProviders {
SapHana = "sap-hana",
Snowflake = "snowflake",
Totp = "totp",
SapAse = "sap-ase"
SapAse = "sap-ase",
GcpIam = "gcp-iam"
}

export const DynamicSecretProviderSchema = z.discriminatedUnion("type", [
Expand All @@ -338,7 +343,8 @@ export const DynamicSecretProviderSchema = z.discriminatedUnion("type", [
z.object({ type: z.literal(DynamicSecretProviders.AzureEntraID), inputs: AzureEntraIDSchema }),
z.object({ type: z.literal(DynamicSecretProviders.Ldap), inputs: LdapSchema }),
z.object({ type: z.literal(DynamicSecretProviders.Snowflake), inputs: DynamicSecretSnowflakeSchema }),
z.object({ type: z.literal(DynamicSecretProviders.Totp), inputs: DynamicSecretTotpSchema })
z.object({ type: z.literal(DynamicSecretProviders.Totp), inputs: DynamicSecretTotpSchema }),
z.object({ type: z.literal(DynamicSecretProviders.GcpIam), inputs: DynamicSecretGcpIamSchema })
]);

export type TDynamicProviderFns = {
Expand Down
24 changes: 12 additions & 12 deletions docs/documentation/platform/dynamic-secrets/aws-elasticache.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -99,39 +99,39 @@ The Infisical AWS ElastiCache dynamic secret allows you to generate AWS ElastiCa
![Modify ElastiCache Statements Modal](/images/platform/dynamic-secrets/modify-elasticache-statement.png)
</Step>
<Step title="Click `Submit`">
After submitting the form, you will see a dynamic secret created in the dashboard.
After submitting the form, you will see a dynamic secret created in the dashboard.

<Note>
If this step fails, you may have to add the CA certificate.
If this step fails, you may have to add the CA certificate.
</Note>

</Step>
<Step title="Generate dynamic secrets">
Once you've successfully configured the dynamic secret, you're ready to generate on-demand credentials.
To do this, simply click on the 'Generate' button which appears when hovering over the dynamic secret item.
Once you've successfully configured the dynamic secret, you're ready to generate on-demand credentials.
To do this, simply click on the 'Generate' button which appears when hovering over the dynamic secret item.
Alternatively, you can initiate the creation of a new lease by selecting 'New Lease' from the dynamic secret lease list section.

![Dynamic Secret](/images/platform/dynamic-secrets/dynamic-secret-generate-redis.png)
![Dynamic Secret](/images/platform/dynamic-secrets/dynamic-secret-lease-empty-redis.png)
![Dynamic Secret](/images/platform/dynamic-secrets/dynamic-secret-generate.png)
![Dynamic Secret](/images/platform/dynamic-secrets/dynamic-secret-lease-empty.png)

When generating these secrets, it's important to specify a Time-to-Live (TTL) duration. This will dictate how long the credentials are valid for.
When generating these secrets, it's important to specify a Time-to-Live (TTL) duration. This will dictate how long the credentials are valid for.

![Provision Lease](/images/platform/dynamic-secrets/provision-lease.png)

<Tip>
Ensure that the TTL for the lease fall within the maximum TTL defined when configuring the dynamic secret.
Ensure that the TTL for the lease falls within the maximum TTL defined when configuring the dynamic secret.
</Tip>


Once you click the `Submit` button, a new secret lease will be generated and the credentials from it will be shown to you.
Once you click the `Submit` button, a new secret lease will be generated and the credentials from it will be shown to you.

![Provision Lease](/images/platform/dynamic-secrets/lease-values.png)
</Step>
</Steps>

## Audit or Revoke Leases
Once you have created one or more leases, you will be able to access them by clicking on the respective dynamic secret item on the dashboard.
This will allow you to see the expiration time of the lease or delete a lease before it's set time to live.
Once you have created one or more leases, you will be able to access them by clicking on the respective dynamic secret item on the dashboard.
This will allow you to see the expiration time of the lease or delete a lease before its set time to live.

![Provision Lease](/images/platform/dynamic-secrets/lease-data.png)

Expand All @@ -141,4 +141,4 @@ To extend the life of the generated dynamic secret leases past its initial time

<Warning>
Lease renewals cannot exceed the maximum TTL set when configuring the dynamic secret
</Warning>
</Warning>
20 changes: 10 additions & 10 deletions docs/documentation/platform/dynamic-secrets/azure-entra-id.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -123,35 +123,35 @@ Click on Add assignments. Search for the application name you created and select

</Step>
<Step title="Click `Submit`">
After submitting the form, you will see a dynamic secrets for each user created in the dashboard.
After submitting the form, you will see a dynamic secret for each user created in the dashboard.
</Step>

<Step title="Generate dynamic secrets">
Once you've successfully configured the dynamic secret, you're ready to generate on-demand credentials.
To do this, simply click on the 'Generate' button which appears when hovering over the dynamic secret item.
Once you've successfully configured the dynamic secret, you're ready to generate on-demand credentials.
To do this, simply click on the 'Generate' button which appears when hovering over the dynamic secret item.
Alternatively, you can initiate the creation of a new lease by selecting 'New Lease' from the dynamic secret lease list section.

![Dynamic Secret](/images/platform/dynamic-secrets/dynamic-secret-generate-redis.png)
![Dynamic Secret](/images/platform/dynamic-secrets/dynamic-secret-lease-empty-redis.png)
![Dynamic Secret](/images/platform/dynamic-secrets/dynamic-secret-generate.png)
![Dynamic Secret](/images/platform/dynamic-secrets/dynamic-secret-lease-empty.png)

When generating these secrets, it's important to specify a Time-to-Live (TTL) duration. This will dictate how long the credentials are valid for.
When generating these secrets, it's important to specify a Time-to-Live (TTL) duration. This will dictate how long the credentials are valid for.

![Provision Lease](/images/platform/dynamic-secrets/provision-lease.png)

<Tip>
Ensure that the TTL for the lease fall within the maximum TTL defined when configuring the dynamic secret.
Ensure that the TTL for the lease falls within the maximum TTL defined when configuring the dynamic secret.
</Tip>


Once you click the `Submit` button, a new secret lease will be generated and the credentials from it will be shown to you.
Once you click the `Submit` button, a new secret lease will be generated and the credentials from it will be shown to you.

![Provision Lease](/images/platform/dynamic-secrets/dynamic-secret-ad-lease.png)
</Step>
</Steps>

## Audit or Revoke Leases
Once you have created one or more leases, you will be able to access them by clicking on the respective dynamic secret item on the dashboard.
This will allow you to see the expiration time of the lease or delete a lease before it's set time to live.
Once you have created one or more leases, you will be able to access them by clicking on the respective dynamic secret item on the dashboard.
This will allow you to see the expiration time of the lease or delete a lease before its set time to live.

![Provision Lease](/images/platform/dynamic-secrets/lease-data.png)

Expand Down
22 changes: 11 additions & 11 deletions docs/documentation/platform/dynamic-secrets/elastic-search.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -82,39 +82,39 @@ The Infisical Elasticsearch dynamic secret allows you to generate Elasticsearch

</Step>
<Step title="Click `Submit`">
After submitting the form, you will see a dynamic secret created in the dashboard.
After submitting the form, you will see a dynamic secret created in the dashboard.

<Note>
If this step fails, you may have to add the CA certificate.
If this step fails, you may have to add the CA certificate.
</Note>

</Step>
<Step title="Generate dynamic secrets">
Once you've successfully configured the dynamic secret, you're ready to generate on-demand credentials.
To do this, simply click on the 'Generate' button which appears when hovering over the dynamic secret item.
Once you've successfully configured the dynamic secret, you're ready to generate on-demand credentials.
To do this, simply click on the 'Generate' button which appears when hovering over the dynamic secret item.
Alternatively, you can initiate the creation of a new lease by selecting 'New Lease' from the dynamic secret lease list section.

![Dynamic Secret](/images/platform/dynamic-secrets/dynamic-secret-generate-redis.png)
![Dynamic Secret](/images/platform/dynamic-secrets/dynamic-secret-lease-empty-redis.png)
![Dynamic Secret](/images/platform/dynamic-secrets/dynamic-secret-generate.png)
![Dynamic Secret](/images/platform/dynamic-secrets/dynamic-secret-lease-empty.png)

When generating these secrets, it's important to specify a Time-to-Live (TTL) duration. This will dictate how long the credentials are valid for.
When generating these secrets, it's important to specify a Time-to-Live (TTL) duration. This will dictate how long the credentials are valid for.

![Provision Lease](/images/platform/dynamic-secrets/provision-lease.png)

<Tip>
Ensure that the TTL for the lease fall within the maximum TTL defined when configuring the dynamic secret.
Ensure that the TTL for the lease falls within the maximum TTL defined when configuring the dynamic secret.
</Tip>


Once you click the `Submit` button, a new secret lease will be generated and the credentials from it will be shown to you.
Once you click the `Submit` button, a new secret lease will be generated and the credentials from it will be shown to you.

![Provision Lease](/images/platform/dynamic-secrets/lease-values.png)
</Step>
</Steps>

## Audit or Revoke Leases
Once you have created one or more leases, you will be able to access them by clicking on the respective dynamic secret item on the dashboard.
This will allow you to see the expiration time of the lease or delete a lease before it's set time to live.
Once you have created one or more leases, you will be able to access them by clicking on the respective dynamic secret item on the dashboard.
This will allow you to see the expiration time of the lease or delete a lease before its set time to live.

![Provision Lease](/images/platform/dynamic-secrets/lease-data.png)

Expand Down
Loading
Loading