Merge pull request #4328 from Infisical/feat/error-log

feat: better error notification for dynamic secret
This commit is contained in:
Akhil Mohan
2025-08-08 22:59:12 +05:30
committed by GitHub
20 changed files with 1152 additions and 499 deletions
@@ -15,6 +15,7 @@ import { z } from "zod";
import { CustomAWSHasher } from "@app/lib/aws/hashing"; import { CustomAWSHasher } from "@app/lib/aws/hashing";
import { crypto } from "@app/lib/crypto"; import { crypto } from "@app/lib/crypto";
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError } from "@app/lib/errors";
import { sanitizeString } from "@app/lib/fn";
import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars"; import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars";
import { DynamicSecretAwsElastiCacheSchema, TDynamicProviderFns } from "./models"; import { DynamicSecretAwsElastiCacheSchema, TDynamicProviderFns } from "./models";
@@ -170,14 +171,29 @@ export const AwsElastiCacheDatabaseProvider = (): TDynamicProviderFns => {
}; };
const validateConnection = async (inputs: unknown) => { const validateConnection = async (inputs: unknown) => {
const providerInputs = await validateProviderInputs(inputs); const providerInputs = await validateProviderInputs(inputs);
await ElastiCacheUserManager( try {
{ await ElastiCacheUserManager(
accessKeyId: providerInputs.accessKeyId, {
secretAccessKey: providerInputs.secretAccessKey accessKeyId: providerInputs.accessKeyId,
}, secretAccessKey: providerInputs.secretAccessKey
providerInputs.region },
).verifyCredentials(providerInputs.clusterName); providerInputs.region
return true; ).verifyCredentials(providerInputs.clusterName);
return true;
} catch (err) {
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error)?.message,
tokens: [
providerInputs.accessKeyId,
providerInputs.secretAccessKey,
providerInputs.clusterName,
providerInputs.region
]
});
throw new BadRequestError({
message: `Failed to connect with provider: ${sanitizedErrorMessage}`
});
}
}; };
const create = async (data: { const create = async (data: {
@@ -206,21 +222,37 @@ export const AwsElastiCacheDatabaseProvider = (): TDynamicProviderFns => {
const parsedStatement = CreateElastiCacheUserSchema.parse(JSON.parse(creationStatement)); const parsedStatement = CreateElastiCacheUserSchema.parse(JSON.parse(creationStatement));
await ElastiCacheUserManager( try {
{ await ElastiCacheUserManager(
accessKeyId: providerInputs.accessKeyId, {
secretAccessKey: providerInputs.secretAccessKey accessKeyId: providerInputs.accessKeyId,
}, secretAccessKey: providerInputs.secretAccessKey
providerInputs.region },
).createUser(parsedStatement, providerInputs.clusterName); providerInputs.region
).createUser(parsedStatement, providerInputs.clusterName);
return { return {
entityId: leaseUsername, entityId: leaseUsername,
data: { data: {
DB_USERNAME: leaseUsername, DB_USERNAME: leaseUsername,
DB_PASSWORD: leasePassword DB_PASSWORD: leasePassword
} }
}; };
} catch (err) {
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error)?.message,
tokens: [
leaseUsername,
leasePassword,
providerInputs.accessKeyId,
providerInputs.secretAccessKey,
providerInputs.clusterName
]
});
throw new BadRequestError({
message: `Failed to create lease from provider: ${sanitizedErrorMessage}`
});
}
}; };
const revoke = async (inputs: unknown, entityId: string) => { const revoke = async (inputs: unknown, entityId: string) => {
@@ -229,15 +261,25 @@ export const AwsElastiCacheDatabaseProvider = (): TDynamicProviderFns => {
const revokeStatement = handlebars.compile(providerInputs.revocationStatement)({ username: entityId }); const revokeStatement = handlebars.compile(providerInputs.revocationStatement)({ username: entityId });
const parsedStatement = DeleteElasticCacheUserSchema.parse(JSON.parse(revokeStatement)); const parsedStatement = DeleteElasticCacheUserSchema.parse(JSON.parse(revokeStatement));
await ElastiCacheUserManager( try {
{ await ElastiCacheUserManager(
accessKeyId: providerInputs.accessKeyId, {
secretAccessKey: providerInputs.secretAccessKey accessKeyId: providerInputs.accessKeyId,
}, secretAccessKey: providerInputs.secretAccessKey
providerInputs.region },
).deleteUser(parsedStatement); providerInputs.region
).deleteUser(parsedStatement);
return { entityId }; return { entityId };
} catch (err) {
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error)?.message,
tokens: [entityId, providerInputs.accessKeyId, providerInputs.secretAccessKey, providerInputs.clusterName]
});
throw new BadRequestError({
message: `Failed to revoke lease from provider: ${sanitizedErrorMessage}`
});
}
}; };
const renew = async (_inputs: unknown, entityId: string) => { const renew = async (_inputs: unknown, entityId: string) => {
@@ -23,6 +23,7 @@ import { CustomAWSHasher } from "@app/lib/aws/hashing";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { crypto } from "@app/lib/crypto/cryptography"; import { crypto } from "@app/lib/crypto/cryptography";
import { BadRequestError, UnauthorizedError } from "@app/lib/errors"; import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
import { sanitizeString } from "@app/lib/fn";
import { alphaNumericNanoId } from "@app/lib/nanoid"; import { alphaNumericNanoId } from "@app/lib/nanoid";
import { AwsIamAuthType, DynamicSecretAwsIamSchema, TDynamicProviderFns } from "./models"; import { AwsIamAuthType, DynamicSecretAwsIamSchema, TDynamicProviderFns } from "./models";
@@ -118,22 +119,39 @@ export const AwsIamProvider = (): TDynamicProviderFns => {
const validateConnection = async (inputs: unknown, { projectId }: { projectId: string }) => { const validateConnection = async (inputs: unknown, { projectId }: { projectId: string }) => {
const providerInputs = await validateProviderInputs(inputs); const providerInputs = await validateProviderInputs(inputs);
const client = await $getClient(providerInputs, projectId); try {
const isConnected = await client const client = await $getClient(providerInputs, projectId);
.send(new GetUserCommand({})) const isConnected = await client
.then(() => true) .send(new GetUserCommand({}))
.catch((err) => { .then(() => true)
const message = (err as Error)?.message; .catch((err) => {
if ( const message = (err as Error)?.message;
(providerInputs.method === AwsIamAuthType.AssumeRole || providerInputs.method === AwsIamAuthType.IRSA) && if (
// assume role will throw an error asking to provider username, but if so this has access in aws correctly (providerInputs.method === AwsIamAuthType.AssumeRole || providerInputs.method === AwsIamAuthType.IRSA) &&
message.includes("Must specify userName when calling with non-User credentials") // assume role will throw an error asking to provider username, but if so this has access in aws correctly
) { message.includes("Must specify userName when calling with non-User credentials")
return true; ) {
} return true;
throw err; }
throw err;
});
return isConnected;
} catch (err) {
const sensitiveTokens = [];
if (providerInputs.method === AwsIamAuthType.AccessKey) {
sensitiveTokens.push(providerInputs.accessKey, providerInputs.secretAccessKey);
}
if (providerInputs.method === AwsIamAuthType.AssumeRole) {
sensitiveTokens.push(providerInputs.roleArn);
}
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error)?.message,
tokens: sensitiveTokens
}); });
return isConnected; throw new BadRequestError({
message: `Failed to connect with provider: ${sanitizedErrorMessage}`
});
}
}; };
const create = async (data: { const create = async (data: {
@@ -162,62 +180,81 @@ export const AwsIamProvider = (): TDynamicProviderFns => {
awsTags.push(...additionalTags); awsTags.push(...additionalTags);
} }
const createUserRes = await client.send( try {
new CreateUserCommand({ const createUserRes = await client.send(
Path: awsPath, new CreateUserCommand({
PermissionsBoundary: permissionBoundaryPolicyArn || undefined, Path: awsPath,
Tags: awsTags, PermissionsBoundary: permissionBoundaryPolicyArn || undefined,
UserName: username Tags: awsTags,
}) UserName: username
);
if (!createUserRes.User) throw new BadRequestError({ message: "Failed to create AWS IAM User" });
if (userGroups) {
await Promise.all(
userGroups
.split(",")
.filter(Boolean)
.map((group) =>
client.send(new AddUserToGroupCommand({ UserName: createUserRes?.User?.UserName, GroupName: group }))
)
);
}
if (policyArns) {
await Promise.all(
policyArns
.split(",")
.filter(Boolean)
.map((policyArn) =>
client.send(new AttachUserPolicyCommand({ UserName: createUserRes?.User?.UserName, PolicyArn: policyArn }))
)
);
}
if (policyDocument) {
await client.send(
new PutUserPolicyCommand({
UserName: createUserRes.User.UserName,
PolicyName: `infisical-dynamic-policy-${alphaNumericNanoId(4)}`,
PolicyDocument: policyDocument
}) })
); );
}
const createAccessKeyRes = await client.send( if (!createUserRes.User) throw new BadRequestError({ message: "Failed to create AWS IAM User" });
new CreateAccessKeyCommand({ if (userGroups) {
UserName: createUserRes.User.UserName await Promise.all(
}) userGroups
); .split(",")
if (!createAccessKeyRes.AccessKey) .filter(Boolean)
throw new BadRequestError({ message: "Failed to create AWS IAM User access key" }); .map((group) =>
client.send(new AddUserToGroupCommand({ UserName: createUserRes?.User?.UserName, GroupName: group }))
return { )
entityId: username, );
data: {
ACCESS_KEY: createAccessKeyRes.AccessKey.AccessKeyId,
SECRET_ACCESS_KEY: createAccessKeyRes.AccessKey.SecretAccessKey,
USERNAME: username
} }
}; if (policyArns) {
await Promise.all(
policyArns
.split(",")
.filter(Boolean)
.map((policyArn) =>
client.send(
new AttachUserPolicyCommand({ UserName: createUserRes?.User?.UserName, PolicyArn: policyArn })
)
)
);
}
if (policyDocument) {
await client.send(
new PutUserPolicyCommand({
UserName: createUserRes.User.UserName,
PolicyName: `infisical-dynamic-policy-${alphaNumericNanoId(4)}`,
PolicyDocument: policyDocument
})
);
}
const createAccessKeyRes = await client.send(
new CreateAccessKeyCommand({
UserName: createUserRes.User.UserName
})
);
if (!createAccessKeyRes.AccessKey)
throw new BadRequestError({ message: "Failed to create AWS IAM User access key" });
return {
entityId: username,
data: {
ACCESS_KEY: createAccessKeyRes.AccessKey.AccessKeyId,
SECRET_ACCESS_KEY: createAccessKeyRes.AccessKey.SecretAccessKey,
USERNAME: username
}
};
} catch (err) {
const sensitiveTokens = [username];
if (providerInputs.method === AwsIamAuthType.AccessKey) {
sensitiveTokens.push(providerInputs.accessKey, providerInputs.secretAccessKey);
}
if (providerInputs.method === AwsIamAuthType.AssumeRole) {
sensitiveTokens.push(providerInputs.roleArn);
}
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error)?.message,
tokens: sensitiveTokens
});
throw new BadRequestError({
message: `Failed to create lease from provider: ${sanitizedErrorMessage}`
});
}
}; };
const revoke = async (inputs: unknown, entityId: string, metadata: { projectId: string }) => { const revoke = async (inputs: unknown, entityId: string, metadata: { projectId: string }) => {
@@ -278,8 +315,25 @@ export const AwsIamProvider = (): TDynamicProviderFns => {
) )
); );
await client.send(new DeleteUserCommand({ UserName: username })); try {
return { entityId: username }; await client.send(new DeleteUserCommand({ UserName: username }));
return { entityId: username };
} catch (err) {
const sensitiveTokens = [username];
if (providerInputs.method === AwsIamAuthType.AccessKey) {
sensitiveTokens.push(providerInputs.accessKey, providerInputs.secretAccessKey);
}
if (providerInputs.method === AwsIamAuthType.AssumeRole) {
sensitiveTokens.push(providerInputs.roleArn);
}
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error)?.message,
tokens: sensitiveTokens
});
throw new BadRequestError({
message: `Failed to revoke lease from provider: ${sanitizedErrorMessage}`
});
}
}; };
const renew = async (_inputs: unknown, entityId: string) => { const renew = async (_inputs: unknown, entityId: string) => {
@@ -2,6 +2,7 @@ import axios from "axios";
import { customAlphabet } from "nanoid"; import { customAlphabet } from "nanoid";
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError } from "@app/lib/errors";
import { sanitizeString } from "@app/lib/fn";
import { AzureEntraIDSchema, TDynamicProviderFns } from "./models"; import { AzureEntraIDSchema, TDynamicProviderFns } from "./models";
@@ -51,45 +52,82 @@ export const AzureEntraIDProvider = (): TDynamicProviderFns & {
const validateConnection = async (inputs: unknown) => { const validateConnection = async (inputs: unknown) => {
const providerInputs = await validateProviderInputs(inputs); const providerInputs = await validateProviderInputs(inputs);
const data = await $getToken(providerInputs.tenantId, providerInputs.applicationId, providerInputs.clientSecret); try {
return data.success; const data = await $getToken(providerInputs.tenantId, providerInputs.applicationId, providerInputs.clientSecret);
return data.success;
} catch (err) {
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error)?.message,
tokens: [providerInputs.clientSecret, providerInputs.applicationId, providerInputs.tenantId]
});
throw new BadRequestError({
message: `Failed to connect with provider: ${sanitizedErrorMessage}`
});
}
}; };
const create = async ({ inputs }: { inputs: unknown }) => { const create = async ({ inputs }: { inputs: unknown }) => {
const providerInputs = await validateProviderInputs(inputs); const providerInputs = await validateProviderInputs(inputs);
const data = await $getToken(providerInputs.tenantId, providerInputs.applicationId, providerInputs.clientSecret);
if (!data.success) {
throw new BadRequestError({ message: "Failed to authorize to Microsoft Entra ID" });
}
const password = generatePassword(); const password = generatePassword();
try {
const response = await axios.patch( const data = await $getToken(providerInputs.tenantId, providerInputs.applicationId, providerInputs.clientSecret);
`${MSFT_GRAPH_API_URL}/users/${providerInputs.userId}`, if (!data.success) {
{ throw new BadRequestError({ message: "Failed to authorize to Microsoft Entra ID" });
passwordProfile: {
forceChangePasswordNextSignIn: false,
password
}
},
{
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${data.token}`
}
} }
);
if (response.status !== 204) {
throw new BadRequestError({ message: "Failed to update password" });
}
return { entityId: providerInputs.userId, data: { email: providerInputs.email, password } }; const response = await axios.patch(
`${MSFT_GRAPH_API_URL}/users/${providerInputs.userId}`,
{
passwordProfile: {
forceChangePasswordNextSignIn: false,
password
}
},
{
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${data.token}`
}
}
);
if (response.status !== 204) {
throw new BadRequestError({ message: "Failed to update password" });
}
return { entityId: providerInputs.userId, data: { email: providerInputs.email, password } };
} catch (err) {
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error)?.message,
tokens: [
providerInputs.clientSecret,
providerInputs.applicationId,
providerInputs.userId,
providerInputs.email,
password
]
});
throw new BadRequestError({
message: `Failed to create lease from provider: ${sanitizedErrorMessage}`
});
}
}; };
const revoke = async (inputs: unknown, entityId: string) => { const revoke = async (inputs: unknown, entityId: string) => {
// Creates a new password const providerInputs = await validateProviderInputs(inputs);
await create({ inputs }); try {
return { entityId }; // Creates a new password
await create({ inputs });
return { entityId };
} catch (err) {
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error)?.message,
tokens: [providerInputs.clientSecret, providerInputs.applicationId, entityId]
});
throw new BadRequestError({
message: `Failed to revoke lease from provider: ${sanitizedErrorMessage}`
});
}
}; };
const fetchAzureEntraIdUsers = async (tenantId: string, applicationId: string, clientSecret: string) => { const fetchAzureEntraIdUsers = async (tenantId: string, applicationId: string, clientSecret: string) => {
@@ -3,6 +3,8 @@ import handlebars from "handlebars";
import { customAlphabet } from "nanoid"; import { customAlphabet } from "nanoid";
import { z } from "zod"; import { z } from "zod";
import { BadRequestError } from "@app/lib/errors";
import { sanitizeString } from "@app/lib/fn";
import { alphaNumericNanoId } from "@app/lib/nanoid"; import { alphaNumericNanoId } from "@app/lib/nanoid";
import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars"; import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars";
@@ -71,9 +73,24 @@ export const CassandraProvider = (): TDynamicProviderFns => {
const providerInputs = await validateProviderInputs(inputs); const providerInputs = await validateProviderInputs(inputs);
const client = await $getClient(providerInputs); const client = await $getClient(providerInputs);
const isConnected = await client.execute("SELECT * FROM system_schema.keyspaces").then(() => true); try {
await client.shutdown(); const isConnected = await client.execute("SELECT * FROM system_schema.keyspaces").then(() => true);
return isConnected; await client.shutdown();
return isConnected;
} catch (err) {
const tokens = [providerInputs.password, providerInputs.username];
if (providerInputs.keyspace) {
tokens.push(providerInputs.keyspace);
}
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error)?.message,
tokens
});
await client.shutdown();
throw new BadRequestError({
message: `Failed to connect with provider: ${sanitizedErrorMessage}`
});
}
}; };
const create = async (data: { const create = async (data: {
@@ -89,23 +106,39 @@ export const CassandraProvider = (): TDynamicProviderFns => {
const username = generateUsername(usernameTemplate, identity); const username = generateUsername(usernameTemplate, identity);
const password = generatePassword(); const password = generatePassword();
const { keyspace } = providerInputs; const { keyspace } = providerInputs;
const expiration = new Date(expireAt).toISOString();
const creationStatement = handlebars.compile(providerInputs.creationStatement, { noEscape: true })({ try {
username, const expiration = new Date(expireAt).toISOString();
password,
expiration,
keyspace
});
const queries = creationStatement.toString().split(";").filter(Boolean); const creationStatement = handlebars.compile(providerInputs.creationStatement, { noEscape: true })({
for (const query of queries) { username,
// eslint-disable-next-line password,
await client.execute(query); expiration,
keyspace
});
const queries = creationStatement.toString().split(";").filter(Boolean);
for (const query of queries) {
// eslint-disable-next-line
await client.execute(query);
}
await client.shutdown();
return { entityId: username, data: { DB_USERNAME: username, DB_PASSWORD: password } };
} catch (err) {
const tokens = [username, password];
if (keyspace) {
tokens.push(keyspace);
}
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error)?.message,
tokens
});
await client.shutdown();
throw new BadRequestError({
message: `Failed to create lease from provider: ${sanitizedErrorMessage}`
});
} }
await client.shutdown();
return { entityId: username, data: { DB_USERNAME: username, DB_PASSWORD: password } };
}; };
const revoke = async (inputs: unknown, entityId: string) => { const revoke = async (inputs: unknown, entityId: string) => {
@@ -115,14 +148,29 @@ export const CassandraProvider = (): TDynamicProviderFns => {
const username = entityId; const username = entityId;
const { keyspace } = providerInputs; const { keyspace } = providerInputs;
const revokeStatement = handlebars.compile(providerInputs.revocationStatement)({ username, keyspace }); try {
const queries = revokeStatement.toString().split(";").filter(Boolean); const revokeStatement = handlebars.compile(providerInputs.revocationStatement)({ username, keyspace });
for (const query of queries) { const queries = revokeStatement.toString().split(";").filter(Boolean);
// eslint-disable-next-line for (const query of queries) {
await client.execute(query); // eslint-disable-next-line
await client.execute(query);
}
await client.shutdown();
return { entityId: username };
} catch (err) {
const tokens = [username];
if (keyspace) {
tokens.push(keyspace);
}
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error)?.message,
tokens
});
await client.shutdown();
throw new BadRequestError({
message: `Failed to revoke lease from provider: ${sanitizedErrorMessage}`
});
} }
await client.shutdown();
return { entityId: username };
}; };
const renew = async (inputs: unknown, entityId: string, expireAt: number) => { const renew = async (inputs: unknown, entityId: string, expireAt: number) => {
@@ -130,21 +178,36 @@ export const CassandraProvider = (): TDynamicProviderFns => {
if (!providerInputs.renewStatement) return { entityId }; if (!providerInputs.renewStatement) return { entityId };
const client = await $getClient(providerInputs); const client = await $getClient(providerInputs);
const expiration = new Date(expireAt).toISOString();
const { keyspace } = providerInputs; const { keyspace } = providerInputs;
const renewStatement = handlebars.compile(providerInputs.renewStatement)({ try {
username: entityId, const expiration = new Date(expireAt).toISOString();
keyspace,
expiration const renewStatement = handlebars.compile(providerInputs.renewStatement)({
}); username: entityId,
const queries = renewStatement.toString().split(";").filter(Boolean); keyspace,
for await (const query of queries) { expiration
await client.execute(query); });
const queries = renewStatement.toString().split(";").filter(Boolean);
for await (const query of queries) {
await client.execute(query);
}
await client.shutdown();
return { entityId };
} catch (err) {
const tokens = [entityId];
if (keyspace) {
tokens.push(keyspace);
}
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error)?.message,
tokens
});
await client.shutdown();
throw new BadRequestError({
message: `Failed to renew lease from provider: ${sanitizedErrorMessage}`
});
} }
await client.shutdown();
return { entityId };
}; };
return { return {
@@ -2,6 +2,8 @@ import { Client as ElasticSearchClient } from "@elastic/elasticsearch";
import { customAlphabet } from "nanoid"; import { customAlphabet } from "nanoid";
import { z } from "zod"; import { z } from "zod";
import { BadRequestError } from "@app/lib/errors";
import { sanitizeString } from "@app/lib/fn";
import { alphaNumericNanoId } from "@app/lib/nanoid"; import { alphaNumericNanoId } from "@app/lib/nanoid";
import { verifyHostInputValidity } from "../dynamic-secret-fns"; import { verifyHostInputValidity } from "../dynamic-secret-fns";
@@ -63,12 +65,24 @@ export const ElasticSearchProvider = (): TDynamicProviderFns => {
const providerInputs = await validateProviderInputs(inputs); const providerInputs = await validateProviderInputs(inputs);
const connection = await $getClient(providerInputs); const connection = await $getClient(providerInputs);
const infoResponse = await connection try {
.info() const infoResponse = await connection.info().then(() => true);
.then(() => true) return infoResponse;
.catch(() => false); } catch (err) {
const tokens = [];
return infoResponse; if (providerInputs.auth.type === ElasticSearchAuthTypes.ApiKey) {
tokens.push(providerInputs.auth.apiKey, providerInputs.auth.apiKeyId);
} else {
tokens.push(providerInputs.auth.username, providerInputs.auth.password);
}
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error)?.message,
tokens
});
throw new BadRequestError({
message: `Failed to connect with provider: ${sanitizedErrorMessage}`
});
}
}; };
const create = async (data: { inputs: unknown; usernameTemplate?: string | null; identity?: { name: string } }) => { const create = async (data: { inputs: unknown; usernameTemplate?: string | null; identity?: { name: string } }) => {
@@ -79,27 +93,49 @@ export const ElasticSearchProvider = (): TDynamicProviderFns => {
const username = generateUsername(usernameTemplate, identity); const username = generateUsername(usernameTemplate, identity);
const password = generatePassword(); const password = generatePassword();
await connection.security.putUser({ try {
username, await connection.security.putUser({
password, username,
full_name: "Managed by Infisical.com", password,
roles: providerInputs.roles full_name: "Managed by Infisical.com",
}); roles: providerInputs.roles
});
await connection.close(); await connection.close();
return { entityId: username, data: { DB_USERNAME: username, DB_PASSWORD: password } }; return { entityId: username, data: { DB_USERNAME: username, DB_PASSWORD: password } };
} catch (err) {
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error)?.message,
tokens: [username, password]
});
await connection.close();
throw new BadRequestError({
message: `Failed to create lease from provider: ${sanitizedErrorMessage}`
});
}
}; };
const revoke = async (inputs: unknown, entityId: string) => { const revoke = async (inputs: unknown, entityId: string) => {
const providerInputs = await validateProviderInputs(inputs); const providerInputs = await validateProviderInputs(inputs);
const connection = await $getClient(providerInputs); const connection = await $getClient(providerInputs);
await connection.security.deleteUser({ try {
username: entityId await connection.security.deleteUser({
}); username: entityId
});
await connection.close(); await connection.close();
return { entityId }; return { entityId };
} catch (err) {
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error)?.message,
tokens: [entityId]
});
await connection.close();
throw new BadRequestError({
message: `Failed to revoke lease from provider: ${sanitizedErrorMessage}`
});
}
}; };
const renew = async (_inputs: unknown, entityId: string) => { const renew = async (_inputs: unknown, entityId: string) => {
@@ -3,6 +3,7 @@ import { GetAccessTokenResponse } from "google-auth-library/build/src/auth/oauth
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { BadRequestError, InternalServerError } from "@app/lib/errors"; import { BadRequestError, InternalServerError } from "@app/lib/errors";
import { sanitizeString } from "@app/lib/fn";
import { alphaNumericNanoId } from "@app/lib/nanoid"; import { alphaNumericNanoId } from "@app/lib/nanoid";
import { DynamicSecretGcpIamSchema, TDynamicProviderFns } from "./models"; import { DynamicSecretGcpIamSchema, TDynamicProviderFns } from "./models";
@@ -65,8 +66,18 @@ export const GcpIamProvider = (): TDynamicProviderFns => {
const validateConnection = async (inputs: unknown) => { const validateConnection = async (inputs: unknown) => {
const providerInputs = await validateProviderInputs(inputs); const providerInputs = await validateProviderInputs(inputs);
await $getToken(providerInputs.serviceAccountEmail, 10); try {
return true; await $getToken(providerInputs.serviceAccountEmail, 10);
return true;
} catch (err) {
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error)?.message,
tokens: [providerInputs.serviceAccountEmail]
});
throw new BadRequestError({
message: `Failed to connect with provider: ${sanitizedErrorMessage}`
});
}
}; };
const create = async (data: { inputs: unknown; expireAt: number }) => { const create = async (data: { inputs: unknown; expireAt: number }) => {
@@ -74,13 +85,23 @@ export const GcpIamProvider = (): TDynamicProviderFns => {
const providerInputs = await validateProviderInputs(inputs); const providerInputs = await validateProviderInputs(inputs);
const now = Math.floor(Date.now() / 1000); try {
const ttl = Math.max(Math.floor(expireAt / 1000) - now, 0); 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 token = await $getToken(providerInputs.serviceAccountEmail, ttl);
const entityId = alphaNumericNanoId(32); const entityId = alphaNumericNanoId(32);
return { entityId, data: { SERVICE_ACCOUNT_EMAIL: providerInputs.serviceAccountEmail, TOKEN: token } }; return { entityId, data: { SERVICE_ACCOUNT_EMAIL: providerInputs.serviceAccountEmail, TOKEN: token } };
} catch (err) {
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error)?.message,
tokens: [providerInputs.serviceAccountEmail]
});
throw new BadRequestError({
message: `Failed to create lease from provider: ${sanitizedErrorMessage}`
});
}
}; };
const revoke = async (_inputs: unknown, entityId: string) => { const revoke = async (_inputs: unknown, entityId: string) => {
@@ -89,10 +110,21 @@ export const GcpIamProvider = (): TDynamicProviderFns => {
}; };
const renew = async (inputs: unknown, entityId: string, expireAt: number) => { const renew = async (inputs: unknown, entityId: string, expireAt: number) => {
// To renew a token it must be re-created try {
const data = await create({ inputs, expireAt }); // To renew a token it must be re-created
const data = await create({ inputs, expireAt });
return { ...data, entityId }; return { ...data, entityId };
} catch (err) {
const providerInputs = await validateProviderInputs(inputs);
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error)?.message,
tokens: [providerInputs.serviceAccountEmail]
});
throw new BadRequestError({
message: `Failed to renew lease from provider: ${sanitizedErrorMessage}`
});
}
}; };
return { return {
@@ -3,6 +3,7 @@ import jwt from "jsonwebtoken";
import { crypto } from "@app/lib/crypto"; import { crypto } from "@app/lib/crypto";
import { BadRequestError, InternalServerError } from "@app/lib/errors"; import { BadRequestError, InternalServerError } from "@app/lib/errors";
import { sanitizeString } from "@app/lib/fn";
import { alphaNumericNanoId } from "@app/lib/nanoid"; import { alphaNumericNanoId } from "@app/lib/nanoid";
import { IntegrationUrls } from "@app/services/integration-auth/integration-list"; import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
@@ -89,26 +90,46 @@ export const GithubProvider = (): TDynamicProviderFns => {
const validateConnection = async (inputs: unknown) => { const validateConnection = async (inputs: unknown) => {
const providerInputs = await validateProviderInputs(inputs); const providerInputs = await validateProviderInputs(inputs);
await $generateGitHubInstallationAccessToken(providerInputs); try {
return true; await $generateGitHubInstallationAccessToken(providerInputs);
return true;
} catch (err) {
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error)?.message,
tokens: [providerInputs.privateKey, String(providerInputs.appId), String(providerInputs.installationId)]
});
throw new BadRequestError({
message: `Failed to connect with provider: ${sanitizedErrorMessage}`
});
}
}; };
const create = async (data: { inputs: unknown }) => { const create = async (data: { inputs: unknown }) => {
const { inputs } = data; const { inputs } = data;
const providerInputs = await validateProviderInputs(inputs); const providerInputs = await validateProviderInputs(inputs);
const ghTokenData = await $generateGitHubInstallationAccessToken(providerInputs); try {
const entityId = alphaNumericNanoId(32); const ghTokenData = await $generateGitHubInstallationAccessToken(providerInputs);
const entityId = alphaNumericNanoId(32);
return { return {
entityId, entityId,
data: { data: {
TOKEN: ghTokenData.token, TOKEN: ghTokenData.token,
EXPIRES_AT: ghTokenData.expires_at, EXPIRES_AT: ghTokenData.expires_at,
PERMISSIONS: ghTokenData.permissions, PERMISSIONS: ghTokenData.permissions,
REPOSITORY_SELECTION: ghTokenData.repository_selection REPOSITORY_SELECTION: ghTokenData.repository_selection
} }
}; };
} catch (err) {
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error)?.message,
tokens: [providerInputs.privateKey, String(providerInputs.appId), String(providerInputs.installationId)]
});
throw new BadRequestError({
message: `Failed to create lease from provider: ${sanitizedErrorMessage}`
});
}
}; };
const revoke = async () => { const revoke = async () => {
@@ -2,7 +2,8 @@ import axios, { AxiosError } from "axios";
import handlebars from "handlebars"; import handlebars from "handlebars";
import https from "https"; import https from "https";
import { BadRequestError, InternalServerError } from "@app/lib/errors"; import { BadRequestError } from "@app/lib/errors";
import { sanitizeString } from "@app/lib/fn";
import { GatewayHttpProxyActions, GatewayProxyProtocol, withGatewayProxy } from "@app/lib/gateway"; import { GatewayHttpProxyActions, GatewayProxyProtocol, withGatewayProxy } from "@app/lib/gateway";
import { alphaNumericNanoId } from "@app/lib/nanoid"; import { alphaNumericNanoId } from "@app/lib/nanoid";
import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator"; import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator";
@@ -356,8 +357,12 @@ export const KubernetesProvider = ({ gatewayService }: TKubernetesProviderDTO):
errorMessage = (error.response?.data as { message: string }).message; errorMessage = (error.response?.data as { message: string }).message;
} }
throw new InternalServerError({ const sanitizedErrorMessage = sanitizeString({
message: `Failed to validate connection: ${errorMessage}` unsanitizedString: errorMessage,
tokens: [providerInputs.clusterToken || ""]
});
throw new BadRequestError({
message: `Failed to connect with provider: ${sanitizedErrorMessage}`
}); });
} }
}; };
@@ -602,8 +607,12 @@ export const KubernetesProvider = ({ gatewayService }: TKubernetesProviderDTO):
errorMessage = (error.response?.data as { message: string }).message; errorMessage = (error.response?.data as { message: string }).message;
} }
throw new InternalServerError({ const sanitizedErrorMessage = sanitizeString({
message: `Failed to create dynamic secret: ${errorMessage}` unsanitizedString: errorMessage,
tokens: [providerInputs.clusterToken || ""]
});
throw new BadRequestError({
message: `Failed to create lease from provider: ${sanitizedErrorMessage}`
}); });
} }
}; };
@@ -683,50 +692,65 @@ export const KubernetesProvider = ({ gatewayService }: TKubernetesProviderDTO):
}; };
if (providerInputs.credentialType === KubernetesCredentialType.Dynamic) { if (providerInputs.credentialType === KubernetesCredentialType.Dynamic) {
const rawUrl = try {
providerInputs.authMethod === KubernetesAuthMethod.Gateway const rawUrl =
? GATEWAY_AUTH_DEFAULT_URL providerInputs.authMethod === KubernetesAuthMethod.Gateway
: providerInputs.url || ""; ? GATEWAY_AUTH_DEFAULT_URL
: providerInputs.url || "";
const url = new URL(rawUrl); const url = new URL(rawUrl);
const k8sGatewayHost = url.hostname; const k8sGatewayHost = url.hostname;
const k8sPort = url.port ? Number(url.port) : 443; const k8sPort = url.port ? Number(url.port) : 443;
const k8sHost = `${url.protocol}//${url.hostname}`; const k8sHost = `${url.protocol}//${url.hostname}`;
const httpsAgent = const httpsAgent =
providerInputs.ca && providerInputs.sslEnabled providerInputs.ca && providerInputs.sslEnabled
? new https.Agent({ ? new https.Agent({
ca: providerInputs.ca, ca: providerInputs.ca,
rejectUnauthorized: true rejectUnauthorized: true
}) })
: undefined; : undefined;
if (providerInputs.gatewayId) { if (providerInputs.gatewayId) {
if (providerInputs.authMethod === KubernetesAuthMethod.Gateway) { if (providerInputs.authMethod === KubernetesAuthMethod.Gateway) {
await $gatewayProxyWrapper( await $gatewayProxyWrapper(
{ {
gatewayId: providerInputs.gatewayId, gatewayId: providerInputs.gatewayId,
targetHost: k8sHost, targetHost: k8sHost,
targetPort: k8sPort, targetPort: k8sPort,
httpsAgent, httpsAgent,
reviewTokenThroughGateway: true reviewTokenThroughGateway: true
}, },
serviceAccountDynamicCallback serviceAccountDynamicCallback
); );
} else {
await $gatewayProxyWrapper(
{
gatewayId: providerInputs.gatewayId,
targetHost: k8sGatewayHost,
targetPort: k8sPort,
httpsAgent,
reviewTokenThroughGateway: false
},
serviceAccountDynamicCallback
);
}
} else { } else {
await $gatewayProxyWrapper( await serviceAccountDynamicCallback(k8sHost, k8sPort, httpsAgent);
{
gatewayId: providerInputs.gatewayId,
targetHost: k8sGatewayHost,
targetPort: k8sPort,
httpsAgent,
reviewTokenThroughGateway: false
},
serviceAccountDynamicCallback
);
} }
} else { } catch (error) {
await serviceAccountDynamicCallback(k8sHost, k8sPort, httpsAgent); let errorMessage = error instanceof Error ? error.message : "Unknown error";
if (axios.isAxiosError(error) && (error.response?.data as { message: string })?.message) {
errorMessage = (error.response?.data as { message: string }).message;
}
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: errorMessage,
tokens: [entityId, providerInputs.clusterToken || ""]
});
throw new BadRequestError({
message: `Failed to revoke lease from provider: ${sanitizedErrorMessage}`
});
} }
} }
@@ -6,6 +6,7 @@ import RE2 from "re2";
import { z } from "zod"; import { z } from "zod";
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError } from "@app/lib/errors";
import { sanitizeString } from "@app/lib/fn";
import { alphaNumericNanoId } from "@app/lib/nanoid"; import { alphaNumericNanoId } from "@app/lib/nanoid";
import { LdapCredentialType, LdapSchema, TDynamicProviderFns } from "./models"; import { LdapCredentialType, LdapSchema, TDynamicProviderFns } from "./models";
@@ -91,8 +92,18 @@ export const LdapProvider = (): TDynamicProviderFns => {
const validateConnection = async (inputs: unknown) => { const validateConnection = async (inputs: unknown) => {
const providerInputs = await validateProviderInputs(inputs); const providerInputs = await validateProviderInputs(inputs);
const client = await $getClient(providerInputs); try {
return client.connected; const client = await $getClient(providerInputs);
return client.connected;
} catch (err) {
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error)?.message,
tokens: [providerInputs.bindpass, providerInputs.binddn]
});
throw new BadRequestError({
message: `Failed to connect with provider: ${sanitizedErrorMessage}`
});
}
}; };
const executeLdif = async (client: ldapjs.Client, ldif_file: string) => { const executeLdif = async (client: ldapjs.Client, ldif_file: string) => {
@@ -205,11 +216,11 @@ export const LdapProvider = (): TDynamicProviderFns => {
if (providerInputs.credentialType === LdapCredentialType.Static) { if (providerInputs.credentialType === LdapCredentialType.Static) {
const dnRegex = new RE2("^dn:\\s*(.+)", "m"); const dnRegex = new RE2("^dn:\\s*(.+)", "m");
const dnMatch = dnRegex.exec(providerInputs.rotationLdif); const dnMatch = dnRegex.exec(providerInputs.rotationLdif);
const username = dnMatch?.[1];
if (!username) throw new BadRequestError({ message: "Username not found from Ldif" });
const password = generatePassword();
if (dnMatch) { if (dnMatch) {
const username = dnMatch[1];
const password = generatePassword();
const generatedLdif = generateLDIF({ username, password, ldifTemplate: providerInputs.rotationLdif }); const generatedLdif = generateLDIF({ username, password, ldifTemplate: providerInputs.rotationLdif });
try { try {
@@ -217,7 +228,11 @@ export const LdapProvider = (): TDynamicProviderFns => {
return { entityId: username, data: { DN_ARRAY: dnArray, USERNAME: username, PASSWORD: password } }; return { entityId: username, data: { DN_ARRAY: dnArray, USERNAME: username, PASSWORD: password } };
} catch (err) { } catch (err) {
throw new BadRequestError({ message: (err as Error).message }); const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error)?.message,
tokens: [username, password, providerInputs.binddn, providerInputs.bindpass]
});
throw new BadRequestError({ message: sanitizedErrorMessage });
} }
} else { } else {
throw new BadRequestError({ throw new BadRequestError({
@@ -238,7 +253,11 @@ export const LdapProvider = (): TDynamicProviderFns => {
const rollbackLdif = generateLDIF({ username, password, ldifTemplate: providerInputs.rollbackLdif }); const rollbackLdif = generateLDIF({ username, password, ldifTemplate: providerInputs.rollbackLdif });
await executeLdif(client, rollbackLdif); await executeLdif(client, rollbackLdif);
} }
throw new BadRequestError({ message: (err as Error).message }); const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error)?.message,
tokens: [username, password, providerInputs.binddn, providerInputs.bindpass]
});
throw new BadRequestError({ message: sanitizedErrorMessage });
} }
} }
}; };
@@ -262,7 +281,11 @@ export const LdapProvider = (): TDynamicProviderFns => {
return { entityId: username, data: { DN_ARRAY: dnArray, USERNAME: username, PASSWORD: password } }; return { entityId: username, data: { DN_ARRAY: dnArray, USERNAME: username, PASSWORD: password } };
} catch (err) { } catch (err) {
throw new BadRequestError({ message: (err as Error).message }); const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error)?.message,
tokens: [username, password, providerInputs.binddn, providerInputs.bindpass]
});
throw new BadRequestError({ message: sanitizedErrorMessage });
} }
} else { } else {
throw new BadRequestError({ throw new BadRequestError({
@@ -278,7 +301,7 @@ export const LdapProvider = (): TDynamicProviderFns => {
return { entityId }; return { entityId };
}; };
const renew = async (inputs: unknown, entityId: string) => { const renew = async (_inputs: unknown, entityId: string) => {
// No renewal necessary // No renewal necessary
return { entityId }; return { entityId };
}; };
@@ -3,6 +3,8 @@ import { customAlphabet } from "nanoid";
import { z } from "zod"; import { z } from "zod";
import { createDigestAuthRequestInterceptor } from "@app/lib/axios/digest-auth"; import { createDigestAuthRequestInterceptor } from "@app/lib/axios/digest-auth";
import { BadRequestError } from "@app/lib/errors";
import { sanitizeString } from "@app/lib/fn";
import { alphaNumericNanoId } from "@app/lib/nanoid"; import { alphaNumericNanoId } from "@app/lib/nanoid";
import { DynamicSecretMongoAtlasSchema, TDynamicProviderFns } from "./models"; import { DynamicSecretMongoAtlasSchema, TDynamicProviderFns } from "./models";
@@ -49,19 +51,25 @@ export const MongoAtlasProvider = (): TDynamicProviderFns => {
const providerInputs = await validateProviderInputs(inputs); const providerInputs = await validateProviderInputs(inputs);
const client = await $getClient(providerInputs); const client = await $getClient(providerInputs);
const isConnected = await client({ try {
method: "GET", const isConnected = await client({
url: `v2/groups/${providerInputs.groupId}/databaseUsers`, method: "GET",
params: { itemsPerPage: 1 } url: `v2/groups/${providerInputs.groupId}/databaseUsers`,
}) params: { itemsPerPage: 1 }
.then(() => true) }).then(() => true);
.catch((error) => { return isConnected;
if ((error as AxiosError).response) { } catch (error) {
throw new Error(JSON.stringify((error as AxiosError).response?.data)); const errorMessage = (error as AxiosError).response
} ? JSON.stringify((error as AxiosError).response?.data)
throw error; : (error as Error)?.message;
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: errorMessage,
tokens: [providerInputs.adminPublicKey, providerInputs.adminPrivateKey, providerInputs.groupId]
}); });
return isConnected; throw new BadRequestError({
message: `Failed to connect with provider: ${sanitizedErrorMessage}`
});
}
}; };
const create = async (data: { const create = async (data: {
@@ -77,25 +85,39 @@ export const MongoAtlasProvider = (): TDynamicProviderFns => {
const username = generateUsername(usernameTemplate, identity); const username = generateUsername(usernameTemplate, identity);
const password = generatePassword(); const password = generatePassword();
const expiration = new Date(expireAt).toISOString(); const expiration = new Date(expireAt).toISOString();
await client({ try {
method: "POST", await client({
url: `/v2/groups/${providerInputs.groupId}/databaseUsers`, method: "POST",
data: { url: `/v2/groups/${providerInputs.groupId}/databaseUsers`,
roles: providerInputs.roles, data: {
scopes: providerInputs.scopes, roles: providerInputs.roles,
deleteAfterDate: expiration, scopes: providerInputs.scopes,
username, deleteAfterDate: expiration,
password, username,
databaseName: "admin", password,
groupId: providerInputs.groupId databaseName: "admin",
} groupId: providerInputs.groupId
}).catch((error) => { }
if ((error as AxiosError).response) { });
throw new Error(JSON.stringify((error as AxiosError).response?.data)); return { entityId: username, data: { DB_USERNAME: username, DB_PASSWORD: password } };
} } catch (error) {
throw error; const errorMessage = (error as AxiosError).response
}); ? JSON.stringify((error as AxiosError).response?.data)
return { entityId: username, data: { DB_USERNAME: username, DB_PASSWORD: password } }; : (error as Error)?.message;
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: errorMessage,
tokens: [
username,
password,
providerInputs.adminPublicKey,
providerInputs.adminPrivateKey,
providerInputs.groupId
]
});
throw new BadRequestError({
message: `Failed to create lease from provider: ${sanitizedErrorMessage}`
});
}
}; };
const revoke = async (inputs: unknown, entityId: string) => { const revoke = async (inputs: unknown, entityId: string) => {
@@ -111,15 +133,23 @@ export const MongoAtlasProvider = (): TDynamicProviderFns => {
throw err; throw err;
}); });
if (isExisting) { if (isExisting) {
await client({ try {
method: "DELETE", await client({
url: `/v2/groups/${providerInputs.groupId}/databaseUsers/admin/${username}` method: "DELETE",
}).catch((error) => { url: `/v2/groups/${providerInputs.groupId}/databaseUsers/admin/${username}`
if ((error as AxiosError).response) { });
throw new Error(JSON.stringify((error as AxiosError).response?.data)); } catch (error) {
} const errorMessage = (error as AxiosError).response
throw error; ? JSON.stringify((error as AxiosError).response?.data)
}); : (error as Error)?.message;
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: errorMessage,
tokens: [username, providerInputs.adminPublicKey, providerInputs.adminPrivateKey, providerInputs.groupId]
});
throw new BadRequestError({
message: `Failed to revoke lease from provider: ${sanitizedErrorMessage}`
});
}
} }
return { entityId: username }; return { entityId: username };
@@ -132,21 +162,29 @@ export const MongoAtlasProvider = (): TDynamicProviderFns => {
const username = entityId; const username = entityId;
const expiration = new Date(expireAt).toISOString(); const expiration = new Date(expireAt).toISOString();
await client({ try {
method: "PATCH", await client({
url: `/v2/groups/${providerInputs.groupId}/databaseUsers/admin/${username}`, method: "PATCH",
data: { url: `/v2/groups/${providerInputs.groupId}/databaseUsers/admin/${username}`,
deleteAfterDate: expiration, data: {
databaseName: "admin", deleteAfterDate: expiration,
groupId: providerInputs.groupId databaseName: "admin",
} groupId: providerInputs.groupId
}).catch((error) => { }
if ((error as AxiosError).response) { });
throw new Error(JSON.stringify((error as AxiosError).response?.data)); return { entityId: username };
} } catch (error) {
throw error; const errorMessage = (error as AxiosError).response
}); ? JSON.stringify((error as AxiosError).response?.data)
return { entityId: username }; : (error as Error)?.message;
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: errorMessage,
tokens: [username, providerInputs.adminPublicKey, providerInputs.adminPrivateKey, providerInputs.groupId]
});
throw new BadRequestError({
message: `Failed to renew lease from provider: ${sanitizedErrorMessage}`
});
}
}; };
return { return {
@@ -2,6 +2,8 @@ import { MongoClient } from "mongodb";
import { customAlphabet } from "nanoid"; import { customAlphabet } from "nanoid";
import { z } from "zod"; import { z } from "zod";
import { BadRequestError } from "@app/lib/errors";
import { sanitizeString } from "@app/lib/fn";
import { alphaNumericNanoId } from "@app/lib/nanoid"; import { alphaNumericNanoId } from "@app/lib/nanoid";
import { verifyHostInputValidity } from "../dynamic-secret-fns"; import { verifyHostInputValidity } from "../dynamic-secret-fns";
@@ -51,13 +53,24 @@ export const MongoDBProvider = (): TDynamicProviderFns => {
const providerInputs = await validateProviderInputs(inputs); const providerInputs = await validateProviderInputs(inputs);
const client = await $getClient(providerInputs); const client = await $getClient(providerInputs);
const isConnected = await client try {
.db(providerInputs.database) const isConnected = await client
.command({ ping: 1 }) .db(providerInputs.database)
.then(() => true); .command({ ping: 1 })
.then(() => true);
await client.close(); await client.close();
return isConnected; return isConnected;
} catch (err) {
await client.close();
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error)?.message,
tokens: [providerInputs.password, providerInputs.username, providerInputs.database, providerInputs.host]
});
throw new BadRequestError({
message: `Failed to connect with provider: ${sanitizedErrorMessage}`
});
}
}; };
const create = async (data: { inputs: unknown; usernameTemplate?: string | null; identity?: { name: string } }) => { const create = async (data: { inputs: unknown; usernameTemplate?: string | null; identity?: { name: string } }) => {
@@ -68,16 +81,27 @@ export const MongoDBProvider = (): TDynamicProviderFns => {
const username = generateUsername(usernameTemplate, identity); const username = generateUsername(usernameTemplate, identity);
const password = generatePassword(); const password = generatePassword();
const db = client.db(providerInputs.database); try {
const db = client.db(providerInputs.database);
await db.command({ await db.command({
createUser: username, createUser: username,
pwd: password, pwd: password,
roles: providerInputs.roles roles: providerInputs.roles
}); });
await client.close(); await client.close();
return { entityId: username, data: { DB_USERNAME: username, DB_PASSWORD: password } }; return { entityId: username, data: { DB_USERNAME: username, DB_PASSWORD: password } };
} catch (err) {
await client.close();
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error)?.message,
tokens: [username, password, providerInputs.password, providerInputs.username, providerInputs.database]
});
throw new BadRequestError({
message: `Failed to create lease from provider: ${sanitizedErrorMessage}`
});
}
}; };
const revoke = async (inputs: unknown, entityId: string) => { const revoke = async (inputs: unknown, entityId: string) => {
@@ -86,13 +110,24 @@ export const MongoDBProvider = (): TDynamicProviderFns => {
const username = entityId; const username = entityId;
const db = client.db(providerInputs.database); try {
await db.command({ const db = client.db(providerInputs.database);
dropUser: username await db.command({
}); dropUser: username
await client.close(); });
await client.close();
return { entityId: username }; return { entityId: username };
} catch (err) {
await client.close();
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error)?.message,
tokens: [username, providerInputs.password, providerInputs.username, providerInputs.database]
});
throw new BadRequestError({
message: `Failed to revoke lease from provider: ${sanitizedErrorMessage}`
});
}
}; };
const renew = async (_inputs: unknown, entityId: string) => { const renew = async (_inputs: unknown, entityId: string) => {
@@ -3,6 +3,8 @@ import https from "https";
import { customAlphabet } from "nanoid"; import { customAlphabet } from "nanoid";
import { z } from "zod"; import { z } from "zod";
import { BadRequestError } from "@app/lib/errors";
import { sanitizeString } from "@app/lib/fn";
import { logger } from "@app/lib/logger"; import { logger } from "@app/lib/logger";
import { alphaNumericNanoId } from "@app/lib/nanoid"; import { alphaNumericNanoId } from "@app/lib/nanoid";
@@ -110,11 +112,19 @@ export const RabbitMqProvider = (): TDynamicProviderFns => {
const validateConnection = async (inputs: unknown) => { const validateConnection = async (inputs: unknown) => {
const providerInputs = await validateProviderInputs(inputs); const providerInputs = await validateProviderInputs(inputs);
const connection = await $getClient(providerInputs); try {
const connection = await $getClient(providerInputs);
const infoResponse = await connection.get("/whoami").then(() => true); const infoResponse = await connection.get("/whoami").then(() => true);
return infoResponse;
return infoResponse; } catch (err) {
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error)?.message,
tokens: [providerInputs.password, providerInputs.username, providerInputs.host]
});
throw new BadRequestError({
message: `Failed to connect with provider: ${sanitizedErrorMessage}`
});
}
}; };
const create = async (data: { inputs: unknown; usernameTemplate?: string | null; identity?: { name: string } }) => { const create = async (data: { inputs: unknown; usernameTemplate?: string | null; identity?: { name: string } }) => {
@@ -125,26 +135,44 @@ export const RabbitMqProvider = (): TDynamicProviderFns => {
const username = generateUsername(usernameTemplate, identity); const username = generateUsername(usernameTemplate, identity);
const password = generatePassword(); const password = generatePassword();
await createRabbitMqUser({ try {
axiosInstance: connection, await createRabbitMqUser({
virtualHost: providerInputs.virtualHost, axiosInstance: connection,
createUser: { virtualHost: providerInputs.virtualHost,
password, createUser: {
username, password,
tags: [...(providerInputs.tags ?? []), "infisical-user"] username,
} tags: [...(providerInputs.tags ?? []), "infisical-user"]
}); }
});
return { entityId: username, data: { DB_USERNAME: username, DB_PASSWORD: password } }; return { entityId: username, data: { DB_USERNAME: username, DB_PASSWORD: password } };
} catch (err) {
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error)?.message,
tokens: [username, password, providerInputs.password, providerInputs.username]
});
throw new BadRequestError({
message: `Failed to create lease from provider: ${sanitizedErrorMessage}`
});
}
}; };
const revoke = async (inputs: unknown, entityId: string) => { const revoke = async (inputs: unknown, entityId: string) => {
const providerInputs = await validateProviderInputs(inputs); const providerInputs = await validateProviderInputs(inputs);
const connection = await $getClient(providerInputs); const connection = await $getClient(providerInputs);
await deleteRabbitMqUser({ axiosInstance: connection, usernameToDelete: entityId }); try {
await deleteRabbitMqUser({ axiosInstance: connection, usernameToDelete: entityId });
return { entityId }; return { entityId };
} catch (err) {
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error)?.message,
tokens: [entityId, providerInputs.password, providerInputs.username]
});
throw new BadRequestError({
message: `Failed to revoke lease from provider: ${sanitizedErrorMessage}`
});
}
}; };
const renew = async (_inputs: unknown, entityId: string) => { const renew = async (_inputs: unknown, entityId: string) => {
@@ -4,6 +4,7 @@ import { customAlphabet } from "nanoid";
import { z } from "zod"; import { z } from "zod";
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError } from "@app/lib/errors";
import { sanitizeString } from "@app/lib/fn";
import { alphaNumericNanoId } from "@app/lib/nanoid"; import { alphaNumericNanoId } from "@app/lib/nanoid";
import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars"; import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars";
@@ -112,14 +113,27 @@ export const RedisDatabaseProvider = (): TDynamicProviderFns => {
const validateConnection = async (inputs: unknown) => { const validateConnection = async (inputs: unknown) => {
const providerInputs = await validateProviderInputs(inputs); const providerInputs = await validateProviderInputs(inputs);
const connection = await $getClient(providerInputs); let connection;
try {
const pingResponse = await connection connection = await $getClient(providerInputs);
.ping() const pingResponse = await connection.ping().then(() => true);
.then(() => true) await connection.quit();
.catch(() => false); return pingResponse;
} catch (err) {
return pingResponse; if (connection) await connection.quit();
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error)?.message,
tokens: [
providerInputs.password || "",
providerInputs.username,
providerInputs.host,
String(providerInputs.port)
]
});
throw new BadRequestError({
message: `Failed to connect with provider: ${sanitizedErrorMessage}`
});
}
}; };
const create = async (data: { const create = async (data: {
@@ -144,10 +158,20 @@ export const RedisDatabaseProvider = (): TDynamicProviderFns => {
const queries = creationStatement.toString().split(";").filter(Boolean); const queries = creationStatement.toString().split(";").filter(Boolean);
await executeTransactions(connection, queries); try {
await executeTransactions(connection, queries);
await connection.quit(); await connection.quit();
return { entityId: username, data: { DB_USERNAME: username, DB_PASSWORD: password } }; return { entityId: username, data: { DB_USERNAME: username, DB_PASSWORD: password } };
} catch (err) {
await connection.quit();
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error)?.message,
tokens: [username, password, providerInputs.password || "", providerInputs.username]
});
throw new BadRequestError({
message: `Failed to create lease from provider: ${sanitizedErrorMessage}`
});
}
}; };
const revoke = async (inputs: unknown, entityId: string) => { const revoke = async (inputs: unknown, entityId: string) => {
@@ -159,10 +183,20 @@ export const RedisDatabaseProvider = (): TDynamicProviderFns => {
const revokeStatement = handlebars.compile(providerInputs.revocationStatement)({ username }); const revokeStatement = handlebars.compile(providerInputs.revocationStatement)({ username });
const queries = revokeStatement.toString().split(";").filter(Boolean); const queries = revokeStatement.toString().split(";").filter(Boolean);
await executeTransactions(connection, queries); try {
await executeTransactions(connection, queries);
await connection.quit(); await connection.quit();
return { entityId: username }; return { entityId: username };
} catch (err) {
await connection.quit();
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error)?.message,
tokens: [username, providerInputs.password || "", providerInputs.username]
});
throw new BadRequestError({
message: `Failed to revoke lease from provider: ${sanitizedErrorMessage}`
});
}
}; };
const renew = async (inputs: unknown, entityId: string, expireAt: number) => { const renew = async (inputs: unknown, entityId: string, expireAt: number) => {
@@ -176,13 +210,23 @@ export const RedisDatabaseProvider = (): TDynamicProviderFns => {
const renewStatement = handlebars.compile(providerInputs.renewStatement)({ username, expiration }); const renewStatement = handlebars.compile(providerInputs.renewStatement)({ username, expiration });
if (renewStatement) { try {
const queries = renewStatement.toString().split(";").filter(Boolean); if (renewStatement) {
await executeTransactions(connection, queries); const queries = renewStatement.toString().split(";").filter(Boolean);
await executeTransactions(connection, queries);
}
await connection.quit();
return { entityId: username };
} catch (err) {
await connection.quit();
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error)?.message,
tokens: [username, providerInputs.password || "", providerInputs.username]
});
throw new BadRequestError({
message: `Failed to renew lease from provider: ${sanitizedErrorMessage}`
});
} }
await connection.quit();
return { entityId: username };
}; };
return { return {
@@ -4,6 +4,7 @@ import odbc from "odbc";
import { z } from "zod"; import { z } from "zod";
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError } from "@app/lib/errors";
import { sanitizeString } from "@app/lib/fn";
import { alphaNumericNanoId } from "@app/lib/nanoid"; import { alphaNumericNanoId } from "@app/lib/nanoid";
import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars"; import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars";
@@ -67,25 +68,41 @@ export const SapAseProvider = (): TDynamicProviderFns => {
const validateConnection = async (inputs: unknown) => { const validateConnection = async (inputs: unknown) => {
const providerInputs = await validateProviderInputs(inputs); const providerInputs = await validateProviderInputs(inputs);
const masterClient = await $getClient(providerInputs, true); let masterClient;
const client = await $getClient(providerInputs); let client;
try {
masterClient = await $getClient(providerInputs, true);
client = await $getClient(providerInputs);
const [resultFromMasterDatabase] = await masterClient.query<{ version: string }>("SELECT @@VERSION AS version"); const [resultFromMasterDatabase] = await masterClient.query<{ version: string }>("SELECT @@VERSION AS version");
const [resultFromSelectedDatabase] = await client.query<{ version: string }>("SELECT @@VERSION AS version"); const [resultFromSelectedDatabase] = await client.query<{ version: string }>("SELECT @@VERSION AS version");
if (!resultFromSelectedDatabase.version) { if (!resultFromSelectedDatabase.version) {
throw new BadRequestError({
message: "Failed to validate SAP ASE connection, version query failed"
});
}
if (resultFromMasterDatabase.version !== resultFromSelectedDatabase.version) {
throw new BadRequestError({
message: "Failed to validate SAP ASE connection (master), version mismatch"
});
}
await masterClient.close();
await client.close();
return true;
} catch (err) {
if (masterClient) await masterClient.close();
if (client) await client.close();
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error)?.message,
tokens: [providerInputs.password, providerInputs.username, providerInputs.host, providerInputs.database]
});
throw new BadRequestError({ throw new BadRequestError({
message: "Failed to validate SAP ASE connection, version query failed" message: `Failed to connect with provider: ${sanitizedErrorMessage}`
}); });
} }
if (resultFromMasterDatabase.version !== resultFromSelectedDatabase.version) {
throw new BadRequestError({
message: "Failed to validate SAP ASE connection (master), version mismatch"
});
}
return true;
}; };
const create = async (data: { inputs: unknown; usernameTemplate?: string | null; identity?: { name: string } }) => { const create = async (data: { inputs: unknown; usernameTemplate?: string | null; identity?: { name: string } }) => {
@@ -105,16 +122,26 @@ export const SapAseProvider = (): TDynamicProviderFns => {
const queries = creationStatement.trim().replaceAll("\n", "").split(";").filter(Boolean); const queries = creationStatement.trim().replaceAll("\n", "").split(";").filter(Boolean);
for await (const query of queries) { try {
// If it's an adduser query, we need to first call sp_addlogin on the MASTER database. for await (const query of queries) {
// If not done, then the newly created user won't be able to authenticate. // If it's an adduser query, we need to first call sp_addlogin on the MASTER database.
await (query.startsWith(SapCommands.CreateLogin) ? masterClient : client).query(query); // If not done, then the newly created user won't be able to authenticate.
await (query.startsWith(SapCommands.CreateLogin) ? masterClient : client).query(query);
}
await masterClient.close();
await client.close();
return { entityId: username, data: { DB_USERNAME: username, DB_PASSWORD: password } };
} catch (err) {
await masterClient.close();
await client.close();
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error)?.message,
tokens: [username, password, providerInputs.password, providerInputs.username, providerInputs.database]
});
throw new BadRequestError({
message: `Failed to create lease from provider: ${sanitizedErrorMessage}`
});
} }
await masterClient.close();
await client.close();
return { entityId: username, data: { DB_USERNAME: username, DB_PASSWORD: password } };
}; };
const revoke = async (inputs: unknown, username: string) => { const revoke = async (inputs: unknown, username: string) => {
@@ -140,14 +167,24 @@ export const SapAseProvider = (): TDynamicProviderFns => {
} }
} }
for await (const query of queries) { try {
await (query.startsWith(SapCommands.DropLogin) ? masterClient : client).query(query); for await (const query of queries) {
await (query.startsWith(SapCommands.DropLogin) ? masterClient : client).query(query);
}
await masterClient.close();
await client.close();
return { entityId: username };
} catch (err) {
await masterClient.close();
await client.close();
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error)?.message,
tokens: [username, providerInputs.password, providerInputs.username, providerInputs.database]
});
throw new BadRequestError({
message: `Failed to revoke lease from provider: ${sanitizedErrorMessage}`
});
} }
await masterClient.close();
await client.close();
return { entityId: username };
}; };
const renew = async (_: unknown, username: string) => { const renew = async (_: unknown, username: string) => {
@@ -10,6 +10,7 @@ import { customAlphabet } from "nanoid";
import { z } from "zod"; import { z } from "zod";
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError } from "@app/lib/errors";
import { sanitizeString } from "@app/lib/fn";
import { alphaNumericNanoId } from "@app/lib/nanoid"; import { alphaNumericNanoId } from "@app/lib/nanoid";
import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars"; import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars";
@@ -83,19 +84,26 @@ export const SapHanaProvider = (): TDynamicProviderFns => {
const validateConnection = async (inputs: unknown) => { const validateConnection = async (inputs: unknown) => {
const providerInputs = await validateProviderInputs(inputs); const providerInputs = await validateProviderInputs(inputs);
const client = await $getClient(providerInputs); try {
const client = await $getClient(providerInputs);
const testResult = await new Promise<boolean>((resolve, reject) => { const testResult = await new Promise<boolean>((resolve, reject) => {
client.exec("SELECT 1 FROM DUMMY;", (err: any) => { client.exec("SELECT 1 FROM DUMMY;", (err: any) => {
if (err) { if (err) {
reject(); return reject(err);
} }
resolve(true);
resolve(true); });
}); });
}); return testResult;
} catch (err) {
return testResult; const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error)?.message,
tokens: [providerInputs.password, providerInputs.username, providerInputs.host]
});
throw new BadRequestError({
message: `Failed to connect with provider: ${sanitizedErrorMessage}`
});
}
}; };
const create = async (data: { const create = async (data: {
@@ -119,18 +127,22 @@ export const SapHanaProvider = (): TDynamicProviderFns => {
}); });
const queries = creationStatement.toString().split(";").filter(Boolean); const queries = creationStatement.toString().split(";").filter(Boolean);
for await (const query of queries) { try {
await new Promise((resolve, reject) => { for await (const query of queries) {
client.exec(query, (err: any) => { await new Promise((resolve, reject) => {
if (err) { client.exec(query, (err: any) => {
reject( if (err) return reject(err);
new BadRequestError({ resolve(true);
message: err.message });
})
);
}
resolve(true);
}); });
}
} catch (err) {
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error)?.message,
tokens: [username, password, providerInputs.password, providerInputs.username]
});
throw new BadRequestError({
message: `Failed to create lease from provider: ${sanitizedErrorMessage}`
}); });
} }
@@ -142,18 +154,24 @@ export const SapHanaProvider = (): TDynamicProviderFns => {
const client = await $getClient(providerInputs); const client = await $getClient(providerInputs);
const revokeStatement = handlebars.compile(providerInputs.revocationStatement)({ username }); const revokeStatement = handlebars.compile(providerInputs.revocationStatement)({ username });
const queries = revokeStatement.toString().split(";").filter(Boolean); const queries = revokeStatement.toString().split(";").filter(Boolean);
for await (const query of queries) { try {
await new Promise((resolve, reject) => { for await (const query of queries) {
client.exec(query, (err: any) => { await new Promise((resolve, reject) => {
if (err) { client.exec(query, (err: any) => {
reject( if (err) {
new BadRequestError({ reject(err);
message: err.message }
}) resolve(true);
); });
}
resolve(true);
}); });
}
} catch (err) {
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error)?.message,
tokens: [username, providerInputs.password, providerInputs.username]
});
throw new BadRequestError({
message: `Failed to revoke lease from provider: ${sanitizedErrorMessage}`
}); });
} }
@@ -174,16 +192,20 @@ export const SapHanaProvider = (): TDynamicProviderFns => {
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
client.exec(query, (err: any) => { client.exec(query, (err: any) => {
if (err) { if (err) {
reject( reject(err);
new BadRequestError({
message: err.message
})
);
} }
resolve(true); resolve(true);
}); });
}); });
} }
} catch (err) {
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error)?.message,
tokens: [entityId, providerInputs.password, providerInputs.username]
});
throw new BadRequestError({
message: `Failed to renew lease from provider: ${sanitizedErrorMessage}`
});
} finally { } finally {
client.disconnect(); client.disconnect();
} }
@@ -4,6 +4,7 @@ import snowflake from "snowflake-sdk";
import { z } from "zod"; import { z } from "zod";
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError } from "@app/lib/errors";
import { sanitizeString } from "@app/lib/fn";
import { alphaNumericNanoId } from "@app/lib/nanoid"; import { alphaNumericNanoId } from "@app/lib/nanoid";
import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars"; import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars";
@@ -69,12 +70,10 @@ export const SnowflakeProvider = (): TDynamicProviderFns => {
const validateConnection = async (inputs: unknown) => { const validateConnection = async (inputs: unknown) => {
const providerInputs = await validateProviderInputs(inputs); const providerInputs = await validateProviderInputs(inputs);
const client = await $getClient(providerInputs); let client;
let isValidConnection: boolean;
try { try {
isValidConnection = await Promise.race([ client = await $getClient(providerInputs);
const isValidConnection = await Promise.race([
client.isValidAsync(), client.isValidAsync(),
new Promise((resolve) => { new Promise((resolve) => {
setTimeout(resolve, 10000); setTimeout(resolve, 10000);
@@ -82,11 +81,18 @@ export const SnowflakeProvider = (): TDynamicProviderFns => {
throw new BadRequestError({ message: "Unable to establish connection - verify credentials" }); throw new BadRequestError({ message: "Unable to establish connection - verify credentials" });
}) })
]); ]);
return isValidConnection;
} catch (err) {
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error)?.message,
tokens: [providerInputs.password, providerInputs.username, providerInputs.accountId, providerInputs.orgId]
});
throw new BadRequestError({
message: `Failed to connect with provider: ${sanitizedErrorMessage}`
});
} finally { } finally {
client.destroy(noop); if (client) client.destroy(noop);
} }
return isValidConnection;
}; };
const create = async (data: { const create = async (data: {
@@ -116,13 +122,19 @@ export const SnowflakeProvider = (): TDynamicProviderFns => {
sqlText: creationStatement, sqlText: creationStatement,
complete(err) { complete(err) {
if (err) { if (err) {
return reject(new BadRequestError({ name: "CreateLease", message: err.message })); return reject(err);
} }
return resolve(true); return resolve(true);
} }
}); });
}); });
} catch (err) {
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error).message,
tokens: [username, password, providerInputs.password, providerInputs.username]
});
throw new BadRequestError({ message: `Failed to create lease from provider: ${sanitizedErrorMessage}` });
} finally { } finally {
client.destroy(noop); client.destroy(noop);
} }
@@ -143,13 +155,19 @@ export const SnowflakeProvider = (): TDynamicProviderFns => {
sqlText: revokeStatement, sqlText: revokeStatement,
complete(err) { complete(err) {
if (err) { if (err) {
return reject(new BadRequestError({ name: "RevokeLease", message: err.message })); return reject(err);
} }
return resolve(true); return resolve(true);
} }
}); });
}); });
} catch (err) {
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error).message,
tokens: [username, providerInputs.password, providerInputs.username]
});
throw new BadRequestError({ message: `Failed to revoke lease from provider: ${sanitizedErrorMessage}` });
} finally { } finally {
client.destroy(noop); client.destroy(noop);
} }
@@ -175,13 +193,19 @@ export const SnowflakeProvider = (): TDynamicProviderFns => {
sqlText: renewStatement, sqlText: renewStatement,
complete(err) { complete(err) {
if (err) { if (err) {
return reject(new BadRequestError({ name: "RenewLease", message: err.message })); return reject(err);
} }
return resolve(true); return resolve(true);
} }
}); });
}); });
} catch (err) {
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error).message,
tokens: [entityId, providerInputs.password, providerInputs.username]
});
throw new BadRequestError({ message: `Failed to renew lease from provider: ${sanitizedErrorMessage}` });
} finally { } finally {
client.destroy(noop); client.destroy(noop);
} }
@@ -3,6 +3,8 @@ import knex from "knex";
import { z } from "zod"; import { z } from "zod";
import { crypto } from "@app/lib/crypto/cryptography"; import { crypto } from "@app/lib/crypto/cryptography";
import { BadRequestError } from "@app/lib/errors";
import { sanitizeString } from "@app/lib/fn";
import { GatewayProxyProtocol, withGatewayProxy } from "@app/lib/gateway"; import { GatewayProxyProtocol, withGatewayProxy } from "@app/lib/gateway";
import { alphaNumericNanoId } from "@app/lib/nanoid"; import { alphaNumericNanoId } from "@app/lib/nanoid";
import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars"; import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars";
@@ -212,8 +214,19 @@ export const SqlDatabaseProvider = ({ gatewayService }: TSqlDatabaseProviderDTO)
// oracle needs from keyword // oracle needs from keyword
const testStatement = providerInputs.client === SqlProviders.Oracle ? "SELECT 1 FROM DUAL" : "SELECT 1"; const testStatement = providerInputs.client === SqlProviders.Oracle ? "SELECT 1 FROM DUAL" : "SELECT 1";
isConnected = await db.raw(testStatement).then(() => true); try {
await db.destroy(); isConnected = await db.raw(testStatement).then(() => true);
} catch (err) {
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error)?.message,
tokens: [providerInputs.username]
});
throw new BadRequestError({
message: `Failed to connect with provider: ${sanitizedErrorMessage}`
});
} finally {
await db.destroy();
}
}; };
if (providerInputs.gatewayId) { if (providerInputs.gatewayId) {
@@ -233,13 +246,13 @@ export const SqlDatabaseProvider = ({ gatewayService }: TSqlDatabaseProviderDTO)
const { inputs, expireAt, usernameTemplate, identity } = data; const { inputs, expireAt, usernameTemplate, identity } = data;
const providerInputs = await validateProviderInputs(inputs); const providerInputs = await validateProviderInputs(inputs);
const { database } = providerInputs;
const username = generateUsername(providerInputs.client, usernameTemplate, identity); const username = generateUsername(providerInputs.client, usernameTemplate, identity);
const password = generatePassword(providerInputs.client, providerInputs.passwordRequirements); const password = generatePassword(providerInputs.client, providerInputs.passwordRequirements);
const gatewayCallback = async (host = providerInputs.host, port = providerInputs.port) => { const gatewayCallback = async (host = providerInputs.host, port = providerInputs.port) => {
const db = await $getClient({ ...providerInputs, port, host }); const db = await $getClient({ ...providerInputs, port, host });
try { try {
const { database } = providerInputs;
const expiration = new Date(expireAt).toISOString(); const expiration = new Date(expireAt).toISOString();
const creationStatement = handlebars.compile(providerInputs.creationStatement, { noEscape: true })({ const creationStatement = handlebars.compile(providerInputs.creationStatement, { noEscape: true })({
@@ -256,6 +269,14 @@ export const SqlDatabaseProvider = ({ gatewayService }: TSqlDatabaseProviderDTO)
await tx.raw(query); await tx.raw(query);
} }
}); });
} catch (err) {
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error)?.message,
tokens: [username, password, database]
});
throw new BadRequestError({
message: `Failed to create lease from provider: ${sanitizedErrorMessage}`
});
} finally { } finally {
await db.destroy(); await db.destroy();
} }
@@ -283,6 +304,14 @@ export const SqlDatabaseProvider = ({ gatewayService }: TSqlDatabaseProviderDTO)
await tx.raw(query); await tx.raw(query);
} }
}); });
} catch (err) {
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error)?.message,
tokens: [username, database]
});
throw new BadRequestError({
message: `Failed to revoke lease from provider: ${sanitizedErrorMessage}`
});
} finally { } finally {
await db.destroy(); await db.destroy();
} }
@@ -319,6 +348,14 @@ export const SqlDatabaseProvider = ({ gatewayService }: TSqlDatabaseProviderDTO)
} }
}); });
} }
} catch (err) {
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error)?.message,
tokens: [database]
});
throw new BadRequestError({
message: `Failed to renew lease from provider: ${sanitizedErrorMessage}`
});
} finally { } finally {
await db.destroy(); await db.destroy();
} }
@@ -1,6 +1,8 @@
import { authenticator } from "otplib"; import { authenticator } from "otplib";
import { HashAlgorithms } from "otplib/core"; import { HashAlgorithms } from "otplib/core";
import { BadRequestError } from "@app/lib/errors";
import { sanitizeString } from "@app/lib/fn";
import { alphaNumericNanoId } from "@app/lib/nanoid"; import { alphaNumericNanoId } from "@app/lib/nanoid";
import { DynamicSecretTotpSchema, TDynamicProviderFns, TotpConfigType } from "./models"; import { DynamicSecretTotpSchema, TDynamicProviderFns, TotpConfigType } from "./models";
@@ -12,62 +14,84 @@ export const TotpProvider = (): TDynamicProviderFns => {
return providerInputs; return providerInputs;
}; };
const validateConnection = async () => { const validateConnection = async (inputs: unknown) => {
return true; try {
await validateProviderInputs(inputs);
return true;
} catch (err) {
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error)?.message,
tokens: []
});
throw new BadRequestError({
message: `Failed to connect with provider: ${sanitizedErrorMessage}`
});
}
}; };
const create = async (inputs: unknown) => { const create = async (data: { inputs: unknown }) => {
const providerInputs = await validateProviderInputs(inputs); const { inputs } = data;
try {
const providerInputs = await validateProviderInputs(inputs);
const entityId = alphaNumericNanoId(32); const entityId = alphaNumericNanoId(32);
const authenticatorInstance = authenticator.clone(); const authenticatorInstance = authenticator.clone();
let secret: string; let secret: string;
let period: number | null | undefined; let period: number | null | undefined;
let digits: number | null | undefined; let digits: number | null | undefined;
let algorithm: HashAlgorithms | null | undefined; let algorithm: HashAlgorithms | null | undefined;
if (providerInputs.configType === TotpConfigType.URL) { if (providerInputs.configType === TotpConfigType.URL) {
const urlObj = new URL(providerInputs.url); const urlObj = new URL(providerInputs.url);
secret = urlObj.searchParams.get("secret") as string; secret = urlObj.searchParams.get("secret") as string;
const periodFromUrl = urlObj.searchParams.get("period"); const periodFromUrl = urlObj.searchParams.get("period");
const digitsFromUrl = urlObj.searchParams.get("digits"); const digitsFromUrl = urlObj.searchParams.get("digits");
const algorithmFromUrl = urlObj.searchParams.get("algorithm"); const algorithmFromUrl = urlObj.searchParams.get("algorithm");
if (periodFromUrl) { if (periodFromUrl) {
period = +periodFromUrl; period = +periodFromUrl;
}
if (digitsFromUrl) {
digits = +digitsFromUrl;
}
if (algorithmFromUrl) {
algorithm = algorithmFromUrl.toLowerCase() as HashAlgorithms;
}
} else {
secret = providerInputs.secret;
period = providerInputs.period;
digits = providerInputs.digits;
algorithm = providerInputs.algorithm as unknown as HashAlgorithms;
} }
if (digitsFromUrl) { if (digits) {
digits = +digitsFromUrl; authenticatorInstance.options = { digits };
} }
if (algorithmFromUrl) { if (algorithm) {
algorithm = algorithmFromUrl.toLowerCase() as HashAlgorithms; authenticatorInstance.options = { algorithm };
} }
} else {
secret = providerInputs.secret;
period = providerInputs.period;
digits = providerInputs.digits;
algorithm = providerInputs.algorithm as unknown as HashAlgorithms;
}
if (digits) { if (period) {
authenticatorInstance.options = { digits }; authenticatorInstance.options = { step: period };
} }
if (algorithm) { return {
authenticatorInstance.options = { algorithm }; entityId,
data: { TOTP: authenticatorInstance.generate(secret), TIME_REMAINING: authenticatorInstance.timeRemaining() }
};
} catch (err) {
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error)?.message,
tokens: []
});
throw new BadRequestError({
message: `Failed to create lease from provider: ${sanitizedErrorMessage}`
});
} }
if (period) {
authenticatorInstance.options = { step: period };
}
return {
entityId,
data: { TOTP: authenticatorInstance.generate(secret), TIME_REMAINING: authenticatorInstance.timeRemaining() }
};
}; };
const revoke = async (_inputs: unknown, entityId: string) => { const revoke = async (_inputs: unknown, entityId: string) => {
@@ -4,6 +4,7 @@ import { z } from "zod";
import { crypto } from "@app/lib/crypto/cryptography"; import { crypto } from "@app/lib/crypto/cryptography";
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError } from "@app/lib/errors";
import { sanitizeString } from "@app/lib/fn";
import { GatewayProxyProtocol, withGatewayProxy } from "@app/lib/gateway"; import { GatewayProxyProtocol, withGatewayProxy } from "@app/lib/gateway";
import { logger } from "@app/lib/logger"; import { logger } from "@app/lib/logger";
import { alphaNumericNanoId } from "@app/lib/nanoid"; import { alphaNumericNanoId } from "@app/lib/nanoid";
@@ -275,6 +276,14 @@ export const VerticaProvider = ({ gatewayService }: TVerticaProviderDTO): TDynam
await client.raw(trimmedQuery); await client.raw(trimmedQuery);
} }
} }
} catch (err) {
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error)?.message,
tokens: [username, password, providerInputs.username, providerInputs.password]
});
throw new BadRequestError({
message: `Failed to create lease from provider: ${sanitizedErrorMessage}`
});
} finally { } finally {
if (client) await client.destroy(); if (client) await client.destroy();
} }
@@ -339,6 +348,14 @@ export const VerticaProvider = ({ gatewayService }: TVerticaProviderDTO): TDynam
await client.raw(trimmedQuery); await client.raw(trimmedQuery);
} }
} }
} catch (err) {
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error)?.message,
tokens: [username, providerInputs.username, providerInputs.password]
});
throw new BadRequestError({
message: `Failed to revoke lease from provider: ${sanitizedErrorMessage}`
});
} finally { } finally {
if (client) await client.destroy(); if (client) await client.destroy();
} }
+14
View File
@@ -19,3 +19,17 @@ export const prefixWithSlash = (str: string) => {
const vowelRegex = new RE2(/^[aeiou]/i); const vowelRegex = new RE2(/^[aeiou]/i);
export const startsWithVowel = (str: string) => vowelRegex.test(str); export const startsWithVowel = (str: string) => vowelRegex.test(str);
const pickWordsRegex = new RE2(/(\W+)/);
export const sanitizeString = (dto: { unsanitizedString: string; tokens: string[] }) => {
const words = dto.unsanitizedString.split(pickWordsRegex);
const redactionSet = new Set(dto.tokens.filter(Boolean));
const sanitizedWords = words.map((el) => {
if (redactionSet.has(el)) {
return "[REDACTED]";
}
return el;
});
return sanitizedWords.join("");
};