make resource rotation account credentials write-only

This commit is contained in:
x032205
2025-10-17 22:54:22 -04:00
parent f9fe5e8b0e
commit ebb28844ee
7 changed files with 65 additions and 26 deletions

View File

@@ -1,7 +1,7 @@
import { PamResource } from "@app/ee/services/pam-resource/pam-resource-enums";
import {
CreatePostgresResourceSchema,
PostgresResourceSchema,
SanitizedPostgresResourceSchema,
UpdatePostgresResourceSchema
} from "@app/ee/services/pam-resource/postgres/postgres-resource-schemas";
@@ -12,7 +12,7 @@ export const PAM_RESOURCE_REGISTER_ROUTER_MAP: Record<PamResource, (server: Fast
registerPamResourceEndpoints({
server,
resourceType: PamResource.Postgres,
resourceResponseSchema: PostgresResourceSchema,
resourceResponseSchema: SanitizedPostgresResourceSchema,
createResourceSchema: CreatePostgresResourceSchema,
updateResourceSchema: UpdatePostgresResourceSchema
});

View File

@@ -3,14 +3,14 @@ import { z } from "zod";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import {
PostgresResourceListItemSchema,
PostgresResourceSchema
SanitizedPostgresResourceSchema
} from "@app/ee/services/pam-resource/postgres/postgres-resource-schemas";
import { readLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
// Use z.union([...]) when more resources are added
const ResourceSchema = PostgresResourceSchema;
const SanitizedResourceSchema = SanitizedPostgresResourceSchema;
const ResourceOptionsSchema = z.discriminatedUnion("resource", [PostgresResourceListItemSchema]);
@@ -50,7 +50,7 @@ export const registerPamResourceRouter = async (server: FastifyZodProvider) => {
}),
response: {
200: z.object({
resources: ResourceSchema.array()
resources: SanitizedResourceSchema.array()
})
}
},

View File

@@ -10,7 +10,7 @@ import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { TGatewayV2ServiceFactory } from "../gateway-v2/gateway-v2-service";
import { TLicenseServiceFactory } from "../license/license-service";
import { encryptAccountCredentials } from "../pam-account/pam-account-fns";
import { decryptAccountCredentials, encryptAccountCredentials } from "../pam-account/pam-account-fns";
import { TPamResourceDALFactory } from "./pam-resource-dal";
import { PamResource } from "./pam-resource-enums";
import { PAM_RESOURCE_FACTORY_MAP } from "./pam-resource-factory";
@@ -192,8 +192,19 @@ export const pamResourceServiceFactory = ({
gatewayV2Service
);
const validatedRotationAccountCredentials =
await factory.validateAccountCredentials(rotationAccountCredentials);
// Logic to prevent overwriting unedited censored values
const finalCredentials = { ...rotationAccountCredentials };
if (resource.encryptedRotationAccountCredentials && rotationAccountCredentials.password === "******") {
const decryptedCredentials = await decryptAccountCredentials({
encryptedCredentials: resource.encryptedRotationAccountCredentials,
projectId: resource.projectId,
kmsService
});
finalCredentials.password = decryptedCredentials.password;
}
const validatedRotationAccountCredentials = await factory.validateAccountCredentials(finalCredentials);
updateDoc.encryptedRotationAccountCredentials = await encryptAccountCredentials({
credentials: validatedRotationAccountCredentials,

View File

@@ -26,6 +26,15 @@ export const PostgresResourceSchema = BasePostgresResourceSchema.extend({
rotationAccountCredentials: PostgresAccountCredentialsSchema.nullable().optional()
});
export const SanitizedPostgresResourceSchema = BasePostgresResourceSchema.extend({
connectionDetails: PostgresResourceConnectionDetailsSchema,
rotationAccountCredentials: PostgresAccountCredentialsSchema.pick({
username: true
})
.nullable()
.optional()
});
export const PostgresResourceListItemSchema = z.object({
name: z.literal("PostgreSQL"),
resource: z.literal(PamResource.Postgres)

View File

@@ -11,8 +11,8 @@ import {
faTrash
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { twMerge } from "tailwind-merge";
import { formatDistance } from "date-fns";
import { twMerge } from "tailwind-merge";
import { createNotification } from "@app/components/notifications";
import { ProjectPermissionCan } from "@app/components/permissions";

View File

@@ -31,17 +31,25 @@ export const PostgresResourceForm = ({ resource, onSubmit }: Props) => {
const form = useForm<FormData>({
resolver: zodResolver(formSchema),
defaultValues: resource ?? {
resourceType: PamResourceType.Postgres,
connectionDetails: {
host: "",
port: 5432,
database: "default",
sslEnabled: true,
sslRejectUnauthorized: true,
sslCertificate: undefined
}
}
defaultValues: resource
? {
...resource,
rotationAccountCredentials: {
...resource.rotationAccountCredentials,
password: "******"
}
}
: {
resourceType: PamResourceType.Postgres,
connectionDetails: {
host: "",
port: 5432,
database: "default",
sslEnabled: true,
sslRejectUnauthorized: true,
sslCertificate: undefined
}
}
});
const {
@@ -62,7 +70,7 @@ export const PostgresResourceForm = ({ resource, onSubmit }: Props) => {
selectedTabIndex={selectedTabIndex}
setSelectedTabIndex={setSelectedTabIndex}
/>
<SqlRotateAccountFields />
<SqlRotateAccountFields isUpdate={isUpdate} />
<div className="mt-6 flex items-center">
<Button
className="mr-4"

View File

@@ -6,11 +6,10 @@ import {
AccordionItem,
AccordionTrigger,
FormControl,
Input,
SecretInput
Input
} from "@app/components/v2";
export const SqlRotateAccountFields = () => {
export const SqlRotateAccountFields = ({ isUpdate }: { isUpdate: boolean }) => {
const { control } = useFormContext();
return (
@@ -49,9 +48,21 @@ export const SqlRotateAccountFields = () => {
isError={Boolean(error?.message)}
label="Password"
>
<SecretInput
containerClassName="text-gray-400 group-focus-within:border-primary-400/50! border border-mineshaft-500 bg-mineshaft-900 px-2.5 py-1.5"
<Input
{...field}
type="password"
onFocus={(e) => {
if (isUpdate && field.value === "******") {
field.onChange("");
}
e.target.type = "text";
}}
onBlur={(e) => {
if (isUpdate && field.value === "") {
field.onChange("******");
}
e.target.type = "password";
}}
/>
</FormControl>
)}