Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions backend/src/ee/routes/v1/pam-session-router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { PolicyRulesResponseSchema } from "@app/ee/services/pam-account-policy";
import { KubernetesSessionCredentialsSchema } from "@app/ee/services/pam-resource/kubernetes/kubernetes-resource-schemas";
import { MongoDBSessionCredentialsSchema } from "@app/ee/services/pam-resource/mongodb/mongodb-resource-schemas";
import { MsSQLSessionCredentialsSchema } from "@app/ee/services/pam-resource/mssql/mssql-resource-schemas";
import { MySQLSessionCredentialsSchema } from "@app/ee/services/pam-resource/mysql/mysql-resource-schemas";
import { OracleSessionCredentialsSchema } from "@app/ee/services/pam-resource/oracle/oracle-resource-schemas";
import { PostgresSessionCredentialsSchema } from "@app/ee/services/pam-resource/postgres/postgres-resource-schemas";
Expand All @@ -26,15 +27,17 @@ import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";

// Schemas with distinguishing fields must precede simpler ones — Zod strips unrecognized keys on first match.
const SessionCredentialsSchema = z.union([
MsSQLSessionCredentialsSchema,
SSHSessionCredentialsSchema,
WindowsSessionCredentialsSchema,
KubernetesSessionCredentialsSchema,
MongoDBSessionCredentialsSchema,
PostgresSessionCredentialsSchema,
MySQLSessionCredentialsSchema,
OracleSessionCredentialsSchema,
MongoDBSessionCredentialsSchema,
KubernetesSessionCredentialsSchema,
RedisSessionCredentialsSchema,
WindowsSessionCredentialsSchema
RedisSessionCredentialsSchema
]);

export const registerPamSessionRouter = async (server: FastifyZodProvider) => {
Expand Down
16 changes: 12 additions & 4 deletions backend/src/ee/services/pam-account/pam-account-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ import { PamDomainType } from "../pam-domain/pam-domain-enums";
import { PAM_DOMAIN_FACTORY_MAP } from "../pam-domain/pam-domain-factory";
import { TPamProjectRecordingConfigDALFactory } from "../pam-project-recording-config/pam-project-recording-config-dal";
import { TPamProjectRecordingConfigServiceFactory } from "../pam-project-recording-config/pam-project-recording-config-service";
import { MsSqlAuthMethod } from "../pam-resource/mssql/mssql-resource-enums";
import { TPamResourceDALFactory } from "../pam-resource/pam-resource-dal";
import { PamResource } from "../pam-resource/pam-resource-enums";
import { TPamResourceRotationRulesDALFactory } from "../pam-resource/pam-resource-rotation-rules-dal";
Expand Down Expand Up @@ -1499,11 +1500,18 @@ export const pamAccountServiceFactory = ({
};
}

const credentials: Record<string, unknown> = {
...decryptedResource.connectionDetails,
...decryptedAccount.credentials
};

// Old MSSQL accounts pre-date the authMethod field — default to sql-login
if (decryptedResource.resourceType === PamResource.MsSQL && !("authMethod" in credentials)) {
credentials.authMethod = MsSqlAuthMethod.SqlLogin;
}

return {
credentials: {
...decryptedResource.connectionDetails,
...decryptedAccount.credentials
},
credentials,
policyRules,
projectId: project.id,
account,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export enum MsSqlAuthMethod {
SqlLogin = "sql-login",
Ntlm = "ntlm",
Kerberos = "kerberos"
}
100 changes: 85 additions & 15 deletions backend/src/ee/services/pam-resource/mssql/mssql-resource-schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,59 @@ import {
BaseUpdateGatewayPamResourceSchema,
BaseUpdatePamAccountSchema
} from "../pam-resource-schemas";
import {
BaseSqlAccountCredentialsSchema,
BaseSqlResourceConnectionDetailsSchema
} from "../shared/sql/sql-resource-schemas";
import { BaseSqlResourceConnectionDetailsSchema } from "../shared/sql/sql-resource-schemas";
import { MsSqlAuthMethod } from "./mssql-resource-enums";

export { MsSqlAuthMethod };

// Resources
export const MsSQLResourceConnectionDetailsSchema = BaseSqlResourceConnectionDetailsSchema;
export const MsSQLAccountCredentialsSchema = BaseSqlAccountCredentialsSchema;

const MsSQLSqlLoginCredentialsSchema = z.object({
authMethod: z.literal(MsSqlAuthMethod.SqlLogin).default(MsSqlAuthMethod.SqlLogin),
username: z.string().trim().min(1).max(63),
password: z.string().trim().min(1).max(256)
});

const MsSQLNtlmCredentialsSchema = z.object({
authMethod: z.literal(MsSqlAuthMethod.Ntlm),
username: z.string().trim().min(1).max(63),
password: z.string().trim().min(1).max(256),
domain: z.string().trim().min(1, "Domain is required for NTLM authentication").max(255)
});

const MsSQLKerberosCredentialsSchema = z.object({
authMethod: z.literal(MsSqlAuthMethod.Kerberos),
username: z.string().trim().min(1).max(63),
password: z.string().trim().min(1).max(256),
realm: z
.string()
.trim()
.min(1, "Realm is required for Kerberos")
.max(255)
.regex(/^[A-Za-z0-9._-]+$/, "Realm must contain only letters, numbers, dots, hyphens")
.transform((v) => v.toUpperCase()),
kdcAddress: z
Comment thread
saifsmailbox98 marked this conversation as resolved.
Comment thread
saifsmailbox98 marked this conversation as resolved.
.string()
.trim()
.max(255)
.regex(/^[A-Za-z0-9._:-]*$/, "KDC address must be a hostname or IP with optional port")
.transform((v) => v || undefined)
.optional(),
Comment thread
saifsmailbox98 marked this conversation as resolved.
spn: z
.string()
.trim()
.min(1, "SPN is required for Kerberos")
.max(500)
.regex(/^[A-Za-z0-9._:/-]+$/, "SPN must contain only letters, numbers, dots, colons, slashes, hyphens")
});

// z.union so old accounts without authMethod fall through to the sql-login .default()
export const MsSQLAccountCredentialsSchema = z.union([
MsSQLKerberosCredentialsSchema,
MsSQLNtlmCredentialsSchema,
MsSQLSqlLoginCredentialsSchema
]);

const BaseMsSQLResourceSchema = BasePamResourceSchema.extend({ resourceType: z.literal(PamResource.MsSQL) });

Expand All @@ -26,13 +71,21 @@ export const MsSQLResourceSchema = BaseMsSQLResourceSchema.extend({
rotationAccountCredentials: MsSQLAccountCredentialsSchema.nullable().optional()
});

const SanitizedMsSQLCredentialsSchema = z.union([
z.object({
authMethod: z.literal(MsSqlAuthMethod.Kerberos),
username: z.string(),
realm: z.string(),
kdcAddress: z.string().optional(),
spn: z.string()
}),
z.object({ authMethod: z.literal(MsSqlAuthMethod.Ntlm), username: z.string(), domain: z.string() }),
z.object({ authMethod: z.literal(MsSqlAuthMethod.SqlLogin).default(MsSqlAuthMethod.SqlLogin), username: z.string() })
]);

export const SanitizedMsSQLResourceSchema = BaseMsSQLResourceSchema.extend({
connectionDetails: MsSQLResourceConnectionDetailsSchema,
rotationAccountCredentials: MsSQLAccountCredentialsSchema.pick({
username: true
})
.nullable()
.optional()
rotationAccountCredentials: SanitizedMsSQLCredentialsSchema.nullable().optional()
});

export const MsSQLResourceListItemSchema = z.object({
Expand Down Expand Up @@ -65,10 +118,27 @@ export const UpdateMsSQLAccountSchema = BaseUpdatePamAccountSchema.extend({

export const SanitizedMsSQLAccountWithResourceSchema = BasePamAccountSchemaWithResource.extend({
parentType: z.literal(PamResource.MsSQL),
credentials: MsSQLAccountCredentialsSchema.pick({
username: true
})
credentials: SanitizedMsSQLCredentialsSchema
});

// Strict variants (no .default()/.transform()) — prevents cross-resource false matches in SessionCredentialsSchema
const MsSQLStrictSqlLoginCredentialsSchema = z.object({
authMethod: z.literal(MsSqlAuthMethod.SqlLogin),
username: z.string().trim().min(1).max(63),
password: z.string().trim().min(1).max(256)
});

const MsSQLStrictKerberosCredentialsSchema = z.object({
authMethod: z.literal(MsSqlAuthMethod.Kerberos),
username: z.string().trim().min(1).max(63),
password: z.string().trim().min(1).max(256),
realm: z.string().trim().min(1).max(255),
kdcAddress: z.string().trim().max(255).optional(),
spn: z.string().trim().min(1).max(500)
});

// Sessions
export const MsSQLSessionCredentialsSchema = MsSQLResourceConnectionDetailsSchema.and(MsSQLAccountCredentialsSchema);
export const MsSQLSessionCredentialsSchema = z.union([
MsSQLResourceConnectionDetailsSchema.and(MsSQLStrictKerberosCredentialsSchema),
MsSQLResourceConnectionDetailsSchema.and(MsSQLNtlmCredentialsSchema),
MsSQLResourceConnectionDetailsSchema.and(MsSQLStrictSqlLoginCredentialsSchema)
]);
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ const makeSqlConnection = (
resourceType: PamResource;
username?: string;
password?: string;
authMethod?: string; // MSSQL-only: "ntlm" triggers Windows auth via Tedious
domain?: string; // MSSQL-only: AD domain for NTLM authentication
}
): SqlResourceConnection => {
const { connectionDetails, resourceType, username, password } = config;
Expand Down Expand Up @@ -191,22 +193,35 @@ const makeSqlConnection = (
encrypt: true,
trustServerCertificate: !sslRejectUnauthorized,
cryptoCredentialsDetails: sslCertificate ? { ca: sslCertificate } : {},
// serverName tells tedious to use this hostname for TLS SNI and certificate validation
// instead of the server/host value used for the TCP connection
serverName: host
}
: { encrypt: false };

const isNtlm = config.authMethod === "ntlm";

if (isNtlm && !config.domain) {
throw new BadRequestError({ message: "Domain is required for NTLM authentication" });
}

const client = knex({
client: "mssql",
connection: {
server: "localhost",
port: proxyPort,
user: actualUsername,
password: actualPassword,
database: connectionDetails.database,
requestTimeout: EXTERNAL_REQUEST_TIMEOUT,
// mssqlOptions is passed to tedious driver
// Knex MSSQL dialect maps these flat fields into Tedious's authentication object
...(isNtlm
? {
type: "ntlm",
userName: actualUsername,
password: actualPassword,
domain: config.domain
}
: {
user: actualUsername,
password: actualPassword
}),
// ref: https://github.com/knex/knex/blob/b6507a7129d2b9fafebf5f831494431e64c6a8a0/lib/dialects/mssql/index.js#L66
options: mssqlOptions
}
Expand Down Expand Up @@ -253,6 +268,8 @@ export const executeWithGateway = async <T>(
gatewayId: string;
username?: string;
password?: string;
authMethod?: string; // MSSQL-only: "ntlm" triggers Windows auth via Tedious
domain?: string; // MSSQL-only: AD domain for NTLM authentication
},
gatewayV2Service: Pick<TGatewayV2ServiceFactory, "getPlatformConnectionDetailsByGatewayId">,
operation: (connection: SqlResourceConnection) => Promise<T>
Expand Down Expand Up @@ -327,13 +344,22 @@ export const sqlResourceFactory: TPamResourceFactory<
throw new BadRequestError({ message: "Gateway ID is required" });
}

if ("authMethod" in credentials && credentials.authMethod === "kerberos") {
await executeWithGateway({ connectionDetails, gatewayId, resourceType }, gatewayV2Service, async (client) => {
await client.validate(true);
});
return credentials;
Comment thread
saifsmailbox98 marked this conversation as resolved.
}

await executeWithGateway(
{
connectionDetails,
gatewayId,
resourceType,
username: credentials.username,
password: credentials.password
password: credentials.password,
authMethod: "authMethod" in credentials ? credentials.authMethod : undefined,
domain: "domain" in credentials ? credentials.domain : undefined
},
gatewayV2Service,
async (client) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ sequenceDiagram

1. **Gateway**: An Infisical Gateway deployed in your network that can reach the MsSQL server. The Gateway handles secure communication between users and your MsSQL instance.

2. **Authentication**: Credentials (username/password) are stored securely in Infisical and used by the Gateway to authenticate with MsSQL on behalf of the user.
2. **Authentication**: Credentials are stored securely in Infisical and used by the Gateway to authenticate with MsSQL on behalf of the user. SQL Server Authentication and Windows Authentication (NTLM or Kerberos) are supported.

3. **Local Proxy**: The Infisical CLI starts a local proxy on your machine that intercepts MsSQL connections and routes them securely through the Gateway to your MsSQL instance.

Expand All @@ -63,13 +63,14 @@ Infisical tracks:
Before configuring MsSQL access in Infisical PAM, you need:

1. **Infisical Gateway** - A Gateway deployed in your network with access to the MsSQL server
2. **MsSQL Credentials** - Username and password for the MsSQL instance
2. **MsSQL Credentials** - SQL Server credentials (username/password), Windows domain credentials for NTLM (domain/username/password), or Kerberos credentials (realm/SPN/username/password) for the MsSQL instance
3. **Infisical CLI** - The Infisical CLI installed on user machines

<Warning>
**Gateway Required**: MsSQL access requires an Infisical Gateway to be
deployed and registered with your Infisical instance. The Gateway must have
network connectivity to your MsSQL server.
network connectivity to your MsSQL server. For Kerberos authentication, the
Gateway also needs access to the Key Distribution Center (KDC) on port 88.
</Warning>

## Create the PAM Resource
Expand Down Expand Up @@ -126,12 +127,39 @@ A PAM Account represents a specific set of credentials that users can request ac
An optional description for this account.
</ParamField>

<ParamField path="Authentication Method" type="string" required>
Choose how the account authenticates with SQL Server:
Comment thread
saifsmailbox98 marked this conversation as resolved.
- **SQL Server Authentication** — standard username and password
- **Windows Authentication (NTLM)** — authenticates using Active Directory domain credentials
- **Windows Authentication (Kerberos)** — authenticates via Kerberos tickets from a Key Distribution Center (KDC)
</ParamField>

<ParamField path="Domain" type="string">
The Active Directory domain name (e.g., `CORP`). Only required when using Windows Authentication (NTLM).
</ParamField>

<ParamField path="Realm" type="string">
The Kerberos realm, typically the AD domain name in uppercase (e.g., `CORP.EXAMPLE.COM`). Required for Kerberos authentication.
</ParamField>

<ParamField path="Service Principal Name (SPN)" type="string">
The SQL Server's SPN registered in Active Directory (e.g., `MSSQLSvc/sqlserver.corp.com:1433`). Required for Kerberos authentication.
</ParamField>

<ParamField path="KDC Address" type="string">
Hostname or IP of the Key Distribution Center (e.g., `dc.corp.example.com`). Optional — if omitted, the gateway attempts DNS-based KDC discovery.
</ParamField>

<Info>
Kerberos credentials (realm, SPN, KDC address) are verified when the first session is started, not at account creation. Ensure your KDC is reachable from the gateway on port 88.
</Info>

<ParamField path="Username" type="string" required>
The MsSQL username.
The MsSQL or domain username.
</ParamField>

<ParamField path="Password" type="string" required>
The MsSQL password.
The MsSQL or domain password.
</ParamField>

<ParamField path="Require MFA for Access" type="boolean">
Expand Down
37 changes: 35 additions & 2 deletions frontend/src/hooks/api/pam/types/mssql-resource.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,47 @@
import { PamResourceType } from "../enums";
import { TBaseSqlConnectionDetails, TBaseSqlCredentials } from "./shared/sql-resource";
import { TBaseSqlConnectionDetails } from "./shared/sql-resource";
import { TBasePamAccount } from "./base-account";
import { TBasePamResource } from "./base-resource";

export enum MsSqlAuthMethod {
SqlLogin = "sql-login",
Ntlm = "ntlm",
Kerberos = "kerberos"
}

export type TMsSQLSqlLoginCredentials = {
authMethod: MsSqlAuthMethod.SqlLogin;
username: string;
password: string;
};

export type TMsSQLNtlmCredentials = {
authMethod: MsSqlAuthMethod.Ntlm;
username: string;
password: string;
domain: string;
};

export type TMsSQLKerberosCredentials = {
authMethod: MsSqlAuthMethod.Kerberos;
username: string;
password: string;
realm: string;
kdcAddress?: string;
spn: string;
};

export type TMsSQLCredentials =
| TMsSQLSqlLoginCredentials
| TMsSQLNtlmCredentials
| TMsSQLKerberosCredentials;

// Resources
export type TMsSQLResource = TBasePamResource & { resourceType: PamResourceType.MsSQL } & {
connectionDetails: TBaseSqlConnectionDetails;
};

// Accounts
export type TMsSQLAccount = TBasePamAccount & {
credentials: TBaseSqlCredentials;
credentials: TMsSQLCredentials;
};
Loading
Loading