Skip to content
Open
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
1 change: 1 addition & 0 deletions backend/internal/nginx.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ const internalNginx = {
{ http2_support: host.http2_support },
{ hsts_enabled: host.hsts_enabled },
{ hsts_subdomains: host.hsts_subdomains },
{ dynamic_upstream_resolve: host.dynamic_upstream_resolve },
{ access_list: host.access_list },
{ certificate: host.certificate },
host.locations[i],
Expand Down
4 changes: 2 additions & 2 deletions backend/migrations/20260131163528_trust_forwarded_proto.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const migrateName = "trust_forwarded_proto";
* @param {Object} knex
* @returns {Promise}
*/
const up = function (knex) {
const up = (knex) => {
logger.info(`[${migrateName}] Migrating Up...`);

return knex.schema
Expand All @@ -28,7 +28,7 @@ const up = function (knex) {
* @param {Object} knex
* @returns {Promise}
*/
const down = function (knex) {
const down = (knex) => {
logger.info(`[${migrateName}] Migrating Down...`);

return knex.schema
Expand Down
43 changes: 43 additions & 0 deletions backend/migrations/20260414000000_dynamic_upstream_resolve.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { migrate as logger } from "../logger.js";

const migrateName = "dynamic_upstream_resolve";

/**
* Migrate
*
* @see http://knexjs.org/#Schema
*
* @param {Object} knex
* @returns {Promise}
*/
const up = (knex) => {
logger.info(`[${migrateName}] Migrating Up...`);

return knex.schema
.alterTable('proxy_host', (table) => {
table.tinyint('dynamic_upstream_resolve').notNullable().defaultTo(0);
})
.then(() => {
logger.info(`[${migrateName}] proxy_host Table altered`);
});
};

/**
* Undo Migrate
*
* @param {Object} knex
* @returns {Promise}
*/
const down = (knex) => {
logger.info(`[${migrateName}] Migrating Down...`);

return knex.schema
.alterTable('proxy_host', (table) => {
table.dropColumn('dynamic_upstream_resolve');
})
.then(() => {
logger.info(`[${migrateName}] proxy_host Table altered`);
});
};

export { up, down };
1 change: 1 addition & 0 deletions backend/models/proxy_host.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const boolFields = [
"enabled",
"hsts_enabled",
"hsts_subdomains",
"dynamic_upstream_resolve",
"trust_forwarded_proto",
];

Expand Down
6 changes: 6 additions & 0 deletions backend/schema/components/proxy-host-object.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"enabled",
"locations",
"hsts_enabled",
"dynamic_upstream_resolve",
"hsts_subdomains",
"trust_forwarded_proto"
],
Expand Down Expand Up @@ -147,6 +148,11 @@
"description": "Trust the forwarded headers",
"example": false
},
"dynamic_upstream_resolve": {
"type": "boolean",
"description": "Resolve upstream host dynamically using resolver directive",
"example": false
},
"certificate": {
"oneOf": [
{
Expand Down
3 changes: 3 additions & 0 deletions backend/schema/paths/nginx/proxy-hosts/hostID/put.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@
"trust_forwarded_proto": {
"$ref": "../../../../components/proxy-host-object.json#/properties/trust_forwarded_proto"
},
"dynamic_upstream_resolve": {
"$ref": "../../../../components/proxy-host-object.json#/properties/dynamic_upstream_resolve"
},
"http2_support": {
"$ref": "../../../../components/proxy-host-object.json#/properties/http2_support"
},
Expand Down
3 changes: 3 additions & 0 deletions backend/schema/paths/nginx/proxy-hosts/post.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@
"trust_forwarded_proto": {
"$ref": "../../../components/proxy-host-object.json#/properties/trust_forwarded_proto"
},
"dynamic_upstream_resolve": {
"$ref": "../../../components/proxy-host-object.json#/properties/dynamic_upstream_resolve"
},
"http2_support": {
"$ref": "../../../components/proxy-host-object.json#/properties/http2_support"
},
Expand Down
5 changes: 5 additions & 0 deletions backend/templates/_location.conf
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Real-IP $remote_addr;

{% if dynamic_upstream_resolve == 1 or dynamic_upstream_resolve == true %}
set $upstream_host "{{ forward_host }}";
proxy_pass {{ forward_scheme }}://$upstream_host:{{ forward_port }}{{ forward_path }};
{% else %}
proxy_pass {{ forward_scheme }}://{{ forward_host }}:{{ forward_port }}{{ forward_path }};
{% endif %}

{% include "_access.conf" %}
{% include "_assets.conf" %}
Expand Down
4 changes: 4 additions & 0 deletions backend/templates/proxy_host.conf
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ server {
set $server "{{ forward_host }}";
set $port {{ forward_port }};

{% if dynamic_upstream_resolve == 1 or dynamic_upstream_resolve == true %}
resolver 127.0.0.11 valid=10s;
{% endif %}

{% include "_listen.conf" %}
{% include "_certificates.conf" %}
{% include "_assets.conf" %}
Expand Down
1 change: 1 addition & 0 deletions frontend/src/api/backend/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ export interface ProxyHost {
hstsEnabled: boolean;
hstsSubdomains: boolean;
trustForwardedProto: boolean;
dynamicUpstreamResolve: boolean;
// Expansions:
owner?: User;
accessList?: AccessList;
Expand Down
1 change: 1 addition & 0 deletions frontend/src/hooks/useProxyHost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const fetchProxyHost = (id: number | "new") => {
hstsEnabled: false,
hstsSubdomains: false,
trustForwardedProto: false,
dynamicUpstreamResolve: false,
} as ProxyHost);
}
return getProxyHost(id, ["owner"]);
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/locale/src/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,9 @@
"host.flags.websockets-upgrade": {
"defaultMessage": "Websockets Support"
},
"host.flags.dynamic-upstream-resolve": {
"defaultMessage": "Dynamic Upstream Resolve"
},
"host.forward-port": {
"defaultMessage": "Forward Port"
},
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/locale/src/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,9 @@
"host.flags.websockets-upgrade": {
"defaultMessage": "Prise en charge de Websockets"
},
"host.flags.dynamic-upstream-resolve": {
"defaultMessage": "Résolution dynamique de l'Upstream"
},
"host.forward-port": {
"defaultMessage": "Port de redirection"
},
Expand Down
23 changes: 23 additions & 0 deletions frontend/src/modals/ProxyHostModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,29 @@ const ProxyHostModal = EasyModal.create(({ id, visible, remove }: Props) => {
</span>
</label>
</div>
<div>
<label className="row" htmlFor="dynamicUpstreamResolve">
<span className="col">
<T id="host.flags.dynamic-upstream-resolve" />
</span>
<span className="col-auto">
<Field name="dynamicUpstreamResolve" type="checkbox">
{({ field }: any) => (
<label className="form-check form-check-single form-switch">
<input
{...field}
id="dynamicUpstreamResolve"
className={cn("form-check-input", {
"bg-lime": field.checked,
})}
type="checkbox"
/>
</label>
)}
</Field>
</span>
</label>
</div>
</div>
</div>
</div>
Expand Down
52 changes: 43 additions & 9 deletions test/cypress/e2e/api/ProxyHosts.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,16 @@ describe('Proxy Hosts endpoints', () => {
meta: {
dns_challenge: false
},
advanced_config: '',
locations: [],
block_exploits: false,
caching_enabled: false,
allow_websocket_upgrade: false,
http2_support: false,
hsts_enabled: false,
hsts_subdomains: false,
ssl_forced: false
advanced_config: '',
locations: [],
block_exploits: false,
caching_enabled: false,
allow_websocket_upgrade: false,
http2_support: false,
hsts_enabled: false,
hsts_subdomains: false,
ssl_forced: false,
dynamic_upstream_resolve: false,
}
}).then((data) => {
cy.validateSwaggerSchema('post', 201, '/nginx/proxy-hosts', data);
Expand All @@ -45,4 +46,37 @@ describe('Proxy Hosts endpoints', () => {
});
});

it('Should be able to create a proxy host with dynamic upstream resolve enabled', () => {
cy.task('backendApiPost', {
token: token,
path: '/api/nginx/proxy-hosts',
data: {
domain_names: ['dynamic-resolve.example.com'],
forward_scheme: 'http',
forward_host: 'my.node',
forward_port: 8080,
access_list_id: '0',
certificate_id: 0,
meta: {
dns_challenge: false
},
advanced_config: '',
locations: [],
block_exploits: false,
caching_enabled: false,
allow_websocket_upgrade: false,
http2_support: false,
hsts_enabled: false,
hsts_subdomains: false,
ssl_forced: false,
dynamic_upstream_resolve: true,
}
}).then((data) => {
cy.validateSwaggerSchema('post', 201, '/nginx/proxy-hosts', data);
expect(data).to.have.property('id');
expect(data.id).to.be.greaterThan(0);
expect(data).to.have.property('dynamic_upstream_resolve', true);
});
});

});