mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-10-15 05:11:26 +00:00
fix(dns): enforce the configured OVH zone on record operations
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -600,3 +600,43 @@ describe("ovhClient.testConnection", () => {
|
||||
expect(calls[0]?.[0]).toBe(`${cfg.baseUrl}/domain/zone/example.com`);
|
||||
});
|
||||
});
|
||||
|
||||
describe("ovhClient with a configured zone", () => {
|
||||
const record = {
|
||||
type: "A" as const,
|
||||
name: "app.other.org",
|
||||
content: "1.2.3.4",
|
||||
};
|
||||
|
||||
it("refuses record operations on any other zone without calling OVH", async () => {
|
||||
const cfg = { ...freshConfig(), zone: "example.com" };
|
||||
mockApi();
|
||||
|
||||
const message = /restricted to the zone "example.com"/;
|
||||
await expect(ovhClient.listRecords(cfg, "other.org")).rejects.toThrow(
|
||||
message,
|
||||
);
|
||||
await expect(
|
||||
ovhClient.upsertRecord(cfg, { ...record, zoneId: "other.org" }),
|
||||
).rejects.toThrow(message);
|
||||
await expect(
|
||||
ovhClient.updateRecord(cfg, "other.org", "1", record),
|
||||
).rejects.toThrow(message);
|
||||
await expect(ovhClient.deleteRecord(cfg, "other.org", "1")).rejects.toThrow(
|
||||
message,
|
||||
);
|
||||
expect(apiCalls()).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("still allows operations on the configured zone", async () => {
|
||||
const cfg = { ...freshConfig(), zone: "example.com" };
|
||||
mockApi(ovhSuccess(null), ovhSuccess(null));
|
||||
|
||||
await expect(
|
||||
ovhClient.deleteRecord(cfg, "example.com", "1"),
|
||||
).resolves.toBeUndefined();
|
||||
expect(apiCalls()[0]?.[0]).toBe(
|
||||
`${cfg.baseUrl}/domain/zone/example.com/record/1`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -559,9 +559,12 @@ export const HandleDnsProvider = ({ dnsProviderId }: Props) => {
|
||||
<Input placeholder="example.com" {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Restrict this provider to a single DNS zone. Leave empty
|
||||
to use every zone the credentials can access. When set,
|
||||
the <code>GET /domain/zone</code> right is not needed.
|
||||
Restrict this provider to a single DNS zone: Dokploy
|
||||
will only list and change records in it. Leave empty to
|
||||
use every zone the credentials can access. When set, the{" "}
|
||||
<code>GET /domain/zone</code> right is not needed. This
|
||||
is enforced by Dokploy only, so also limit the OVH token
|
||||
to that zone if you need a hard guarantee.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
|
||||
@@ -252,6 +252,14 @@ const listZoneNames = async (config: OvhConfig) => {
|
||||
}
|
||||
};
|
||||
|
||||
const assertZoneAllowed = (config: OvhConfig, zoneId: string) => {
|
||||
if (config.zone && zoneId !== config.zone) {
|
||||
throw new Error(
|
||||
`OVH: this provider is restricted to the zone "${config.zone}", not "${zoneId}"`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const ovhClient: DnsClient<OvhConfig> = {
|
||||
async listZones(config) {
|
||||
if (config.zone) {
|
||||
@@ -262,6 +270,7 @@ export const ovhClient: DnsClient<OvhConfig> = {
|
||||
},
|
||||
|
||||
async listRecords(config, zoneId) {
|
||||
assertZoneAllowed(config, zoneId);
|
||||
const zone = encodeURIComponent(zoneId);
|
||||
// The listing endpoint only returns ids, so each record is fetched on its own.
|
||||
const ids = await ovhFetch<number[]>(config, `/domain/zone/${zone}/record`);
|
||||
@@ -279,6 +288,7 @@ export const ovhClient: DnsClient<OvhConfig> = {
|
||||
},
|
||||
|
||||
async upsertRecord(config, record) {
|
||||
assertZoneAllowed(config, record.zoneId);
|
||||
const zone = encodeURIComponent(record.zoneId);
|
||||
const subDomain = toSubDomain(record.name, record.zoneId);
|
||||
const existing = await ovhFetch<number[]>(
|
||||
@@ -322,6 +332,7 @@ export const ovhClient: DnsClient<OvhConfig> = {
|
||||
},
|
||||
|
||||
async updateRecord(config, zoneId, recordId, record) {
|
||||
assertZoneAllowed(config, zoneId);
|
||||
const zone = encodeURIComponent(zoneId);
|
||||
const existing = await ovhFetch<OvhRecord>(
|
||||
config,
|
||||
@@ -366,6 +377,7 @@ export const ovhClient: DnsClient<OvhConfig> = {
|
||||
},
|
||||
|
||||
async deleteRecord(config, zoneId, recordId) {
|
||||
assertZoneAllowed(config, zoneId);
|
||||
await ovhFetch(
|
||||
config,
|
||||
`/domain/zone/${encodeURIComponent(zoneId)}/record/${recordId}`,
|
||||
|
||||
Reference in New Issue
Block a user