Skip to content

Commit

Permalink
Merge pull request #2646 from Infisical/feat/sap-hana-dynamic-secrets
Browse files Browse the repository at this point in the history
feat: SAP HANA dynamic secrets
  • Loading branch information
sheensantoscapadngan authored Oct 29, 2024
2 parents 25c26f2 + 3841394 commit 39fdeab
Show file tree
Hide file tree
Showing 23 changed files with 1,137 additions and 118 deletions.
23 changes: 23 additions & 0 deletions backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@
"google-auth-library": "^9.9.0",
"googleapis": "^137.1.0",
"handlebars": "^4.7.8",
"hdb": "^0.19.10",
"ioredis": "^5.3.2",
"jmespath": "^0.16.0",
"jsonwebtoken": "^9.0.2",
Expand Down
4 changes: 4 additions & 0 deletions backend/src/@types/hdb.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
declare module "hdb" {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Untyped, the function returns `any`.
function createClient(options): any;
}
20 changes: 20 additions & 0 deletions backend/src/ee/services/dynamic-secret/dynamic-secret-fns.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { getConfig } from "@app/lib/config/env";
import { BadRequestError } from "@app/lib/errors";
import { getDbConnectionHost } from "@app/lib/knex";

export const verifyHostInputValidity = (host: string) => {
const appCfg = getConfig();
const dbHost = appCfg.DB_HOST || getDbConnectionHost(appCfg.DB_CONNECTION_URI);

if (
appCfg.isCloud &&
// localhost
// internal ips
(host === "host.docker.internal" || host.match(/^10\.\d+\.\d+\.\d+/) || host.match(/^192\.168\.\d+\.\d+/))
)
throw new BadRequestError({ message: "Invalid db host" });

if (host === "localhost" || host === "127.0.0.1" || dbHost === host) {
throw new BadRequestError({ message: "Invalid db host" });
}
};
20 changes: 2 additions & 18 deletions backend/src/ee/services/dynamic-secret/providers/elastic-search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@ import { Client as ElasticSearchClient } from "@elastic/elasticsearch";
import { customAlphabet } from "nanoid";
import { z } from "zod";

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

import { verifyHostInputValidity } from "../dynamic-secret-fns";
import { DynamicSecretElasticSearchSchema, ElasticSearchAuthTypes, TDynamicProviderFns } from "./models";

const generatePassword = () => {
Expand All @@ -19,23 +18,8 @@ const generateUsername = () => {

export const ElasticSearchProvider = (): TDynamicProviderFns => {
const validateProviderInputs = async (inputs: unknown) => {
const appCfg = getConfig();
const isCloud = Boolean(appCfg.LICENSE_SERVER_KEY); // quick and dirty way to check if its cloud or not

const providerInputs = await DynamicSecretElasticSearchSchema.parseAsync(inputs);
if (
isCloud &&
// localhost
// internal ips
(providerInputs.host === "host.docker.internal" ||
providerInputs.host.match(/^10\.\d+\.\d+\.\d+/) ||
providerInputs.host.match(/^192\.168\.\d+\.\d+/))
) {
throw new BadRequestError({ message: "Invalid db host" });
}
if (providerInputs.host === "localhost" || providerInputs.host === "127.0.0.1") {
throw new BadRequestError({ message: "Invalid db host" });
}
verifyHostInputValidity(providerInputs.host);

return providerInputs;
};
Expand Down
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 @@ -9,6 +9,7 @@ import { MongoAtlasProvider } from "./mongo-atlas";
import { MongoDBProvider } from "./mongo-db";
import { RabbitMqProvider } from "./rabbit-mq";
import { RedisDatabaseProvider } from "./redis";
import { SapHanaProvider } from "./sap-hana";
import { SqlDatabaseProvider } from "./sql-database";

export const buildDynamicSecretProviders = () => ({
Expand All @@ -22,5 +23,6 @@ export const buildDynamicSecretProviders = () => ({
[DynamicSecretProviders.ElasticSearch]: ElasticSearchProvider(),
[DynamicSecretProviders.RabbitMq]: RabbitMqProvider(),
[DynamicSecretProviders.AzureEntraID]: AzureEntraIDProvider(),
[DynamicSecretProviders.Ldap]: LdapProvider()
[DynamicSecretProviders.Ldap]: LdapProvider(),
[DynamicSecretProviders.SapHana]: SapHanaProvider()
});
15 changes: 14 additions & 1 deletion backend/src/ee/services/dynamic-secret/providers/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,17 @@ export const DynamicSecretMongoDBSchema = z.object({
)
});

export const DynamicSecretSapHanaSchema = z.object({
host: z.string().trim().toLowerCase(),
port: z.number(),
username: z.string().trim(),
password: z.string().trim(),
creationStatement: z.string().trim(),
revocationStatement: z.string().trim(),
renewStatement: z.string().trim().optional(),
ca: z.string().optional()
});

export const AzureEntraIDSchema = z.object({
tenantId: z.string().trim().min(1),
userId: z.string().trim().min(1),
Expand Down Expand Up @@ -196,14 +207,16 @@ export enum DynamicSecretProviders {
MongoDB = "mongo-db",
RabbitMq = "rabbit-mq",
AzureEntraID = "azure-entra-id",
Ldap = "ldap"
Ldap = "ldap",
SapHana = "sap-hana"
}

export const DynamicSecretProviderSchema = z.discriminatedUnion("type", [
z.object({ type: z.literal(DynamicSecretProviders.SqlDatabase), inputs: DynamicSecretSqlDBSchema }),
z.object({ type: z.literal(DynamicSecretProviders.Cassandra), inputs: DynamicSecretCassandraSchema }),
z.object({ type: z.literal(DynamicSecretProviders.AwsIam), inputs: DynamicSecretAwsIamSchema }),
z.object({ type: z.literal(DynamicSecretProviders.Redis), inputs: DynamicSecretRedisDBSchema }),
z.object({ type: z.literal(DynamicSecretProviders.SapHana), inputs: DynamicSecretSapHanaSchema }),
z.object({ type: z.literal(DynamicSecretProviders.AwsElastiCache), inputs: DynamicSecretAwsElastiCacheSchema }),
z.object({ type: z.literal(DynamicSecretProviders.MongoAtlas), inputs: DynamicSecretMongoAtlasSchema }),
z.object({ type: z.literal(DynamicSecretProviders.ElasticSearch), inputs: DynamicSecretElasticSearchSchema }),
Expand Down
19 changes: 2 additions & 17 deletions backend/src/ee/services/dynamic-secret/providers/mongo-db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@ import { MongoClient } from "mongodb";
import { customAlphabet } from "nanoid";
import { z } from "zod";

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

import { verifyHostInputValidity } from "../dynamic-secret-fns";
import { DynamicSecretMongoDBSchema, TDynamicProviderFns } from "./models";

const generatePassword = (size = 48) => {
Expand All @@ -19,22 +18,8 @@ const generateUsername = () => {

export const MongoDBProvider = (): TDynamicProviderFns => {
const validateProviderInputs = async (inputs: unknown) => {
const appCfg = getConfig();
const providerInputs = await DynamicSecretMongoDBSchema.parseAsync(inputs);
if (
appCfg.isCloud &&
// localhost
// internal ips
(providerInputs.host === "host.docker.internal" ||
providerInputs.host.match(/^10\.\d+\.\d+\.\d+/) ||
providerInputs.host.match(/^192\.168\.\d+\.\d+/))
)
throw new BadRequestError({ message: "Invalid db host" });

if (providerInputs.host === "localhost" || providerInputs.host === "127.0.0.1") {
throw new BadRequestError({ message: "Invalid db host" });
}

verifyHostInputValidity(providerInputs.host);
return providerInputs;
};

Expand Down
20 changes: 2 additions & 18 deletions backend/src/ee/services/dynamic-secret/providers/rabbit-mq.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@ import https from "https";
import { customAlphabet } from "nanoid";
import { z } from "zod";

import { getConfig } from "@app/lib/config/env";
import { BadRequestError } from "@app/lib/errors";
import { removeTrailingSlash } from "@app/lib/fn";
import { logger } from "@app/lib/logger";
import { alphaNumericNanoId } from "@app/lib/nanoid";

import { verifyHostInputValidity } from "../dynamic-secret-fns";
import { DynamicSecretRabbitMqSchema, TDynamicProviderFns } from "./models";

const generatePassword = () => {
Expand Down Expand Up @@ -79,23 +78,8 @@ async function deleteRabbitMqUser({ axiosInstance, usernameToDelete }: TDeleteRa

export const RabbitMqProvider = (): TDynamicProviderFns => {
const validateProviderInputs = async (inputs: unknown) => {
const appCfg = getConfig();
const isCloud = Boolean(appCfg.LICENSE_SERVER_KEY); // quick and dirty way to check if its cloud or not

const providerInputs = await DynamicSecretRabbitMqSchema.parseAsync(inputs);
if (
isCloud &&
// localhost
// internal ips
(providerInputs.host === "host.docker.internal" ||
providerInputs.host.match(/^10\.\d+\.\d+\.\d+/) ||
providerInputs.host.match(/^192\.168\.\d+\.\d+/))
) {
throw new BadRequestError({ message: "Invalid db host" });
}
if (providerInputs.host === "localhost" || providerInputs.host === "127.0.0.1") {
throw new BadRequestError({ message: "Invalid db host" });
}
verifyHostInputValidity(providerInputs.host);

return providerInputs;
};
Expand Down
19 changes: 2 additions & 17 deletions backend/src/ee/services/dynamic-secret/providers/redis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@ import { Redis } from "ioredis";
import { customAlphabet } from "nanoid";
import { z } from "zod";

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

import { verifyHostInputValidity } from "../dynamic-secret-fns";
import { DynamicSecretRedisDBSchema, TDynamicProviderFns } from "./models";

const generatePassword = () => {
Expand Down Expand Up @@ -51,22 +50,8 @@ const executeTransactions = async (connection: Redis, commands: string[]): Promi

export const RedisDatabaseProvider = (): TDynamicProviderFns => {
const validateProviderInputs = async (inputs: unknown) => {
const appCfg = getConfig();
const isCloud = Boolean(appCfg.LICENSE_SERVER_KEY); // quick and dirty way to check if its cloud or not
const dbHost = appCfg.DB_HOST || getDbConnectionHost(appCfg.DB_CONNECTION_URI);

const providerInputs = await DynamicSecretRedisDBSchema.parseAsync(inputs);
if (
isCloud &&
// localhost
// internal ips
(providerInputs.host === "host.docker.internal" ||
providerInputs.host.match(/^10\.\d+\.\d+\.\d+/) ||
providerInputs.host.match(/^192\.168\.\d+\.\d+/))
)
throw new BadRequestError({ message: "Invalid db host" });
if (providerInputs.host === "localhost" || providerInputs.host === "127.0.0.1" || dbHost === providerInputs.host)
throw new BadRequestError({ message: "Invalid db host" });
verifyHostInputValidity(providerInputs.host);
return providerInputs;
};

Expand Down
Loading

0 comments on commit 39fdeab

Please sign in to comment.