nginx-proxy-manager/test/cypress/e2e/api/AccessListHostToggle.cy.js
2026-10-09 22:35:02 +10:00

210 lines
5.7 KiB
JavaScript

/// <reference types="cypress" />
// Regression tests for #5919, #4286 and #5710: access list rules must survive
// every path that re-renders a proxy host config, not just the initial save.
describe('Access Lists across Proxy Host disable/enable', () => {
const domain = 'website4.example.com';
const creds = {
username: 'toggle-user',
password: 'toggle-pass',
};
let token;
let authListId;
let clientListId;
let hostId;
/**
* Requests a path on the proxy host directly (bypassing squid)
* and yields the HTTP status code
*
* @param {object} [auth]
* @param {string} auth.username
* @param {string} auth.password
*/
const request = (auth) => {
const authArg = auth ? `-u '${auth.username}:${auth.password}'` : '';
return cy.exec(`curl --noproxy '*' -s -o /dev/null -w '%{http_code}' ${authArg} http://${domain}/`)
.then((result) => {
expect(result.exitCode).to.eq(0);
return result.stdout.trim();
});
};
// nginx reloads are signalled and return immediately, so the old
// config can still be served for a moment after an API change.
// Retry until the expected status is seen.
const expectStatus = (auth, status) => {
let last = null;
cy.waitUntil(() => request(auth).then((res) => {
last = res;
return res === status;
}), {
timeout: 15000,
interval: 500,
errorMsg: () => `${domain} did not return ${status}, last status: ${last}`,
});
};
const createAccessList = (data) => {
return cy.task('backendApiPost', {
token: token,
path: '/api/nginx/access-lists',
data: {
satisfy_any: false,
pass_auth: false,
items: [],
clients: [],
...data,
}
}).then((res) => {
cy.validateSwaggerSchema('post', 201, '/nginx/access-lists', res);
expect(res).to.have.property('id');
expect(res.id).to.be.greaterThan(0);
return cy.wrap(res.id);
});
};
const setHostAccessList = (accessListId) => {
cy.task('backendApiPut', {
token: token,
path: `/api/nginx/proxy-hosts/${hostId}`,
data: {
access_list_id: accessListId,
}
}).then((data) => {
// No swagger validation here: with a host access list set, the expanded
// access_list in the response has no proxy_host_count, which the schema requires
expect(data).to.have.property('access_list_id', accessListId);
});
};
const toggleHost = () => {
cy.task('backendApiPost', {
token: token,
path: `/api/nginx/proxy-hosts/${hostId}/disable`,
}).then((data) => {
cy.validateSwaggerSchema('post', 200, '/nginx/proxy-hosts/{hostID}/disable', data);
expect(data).to.be.equal(true);
});
cy.task('backendApiPost', {
token: token,
path: `/api/nginx/proxy-hosts/${hostId}/enable`,
}).then((data) => {
cy.validateSwaggerSchema('post', 200, '/nginx/proxy-hosts/{hostID}/enable', data);
expect(data).to.be.equal(true);
});
};
before(() => {
cy.resetUsers();
cy.getToken().then((tok) => {
token = tok;
});
});
it('Should be able to create an auth access list and a client access list', () => {
createAccessList({
name: 'Toggle Auth',
items: [creds],
}).then((id) => {
authListId = id;
});
// The test runner is never in this range, so it must always be denied
createAccessList({
name: 'Toggle Clients',
clients: [
{
directive: 'allow',
address: '203.0.113.0/24',
}
],
}).then((id) => {
clientListId = id;
});
});
it('Should be able to create a proxy host without an access list', () => {
cy.task('backendApiPost', {
token: token,
path: '/api/nginx/proxy-hosts',
data: {
domain_names: [domain],
forward_scheme: 'http',
forward_host: 'examplesite',
forward_port: 80,
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
}
}).then((data) => {
cy.validateSwaggerSchema('post', 201, '/nginx/proxy-hosts', data);
expect(data).to.have.property('id');
expect(data.id).to.be.greaterThan(0);
hostId = data.id;
});
expectStatus(null, '200');
});
it('Should enforce an auth access list applied to an existing host', () => {
setHostAccessList(authListId);
expectStatus(null, '401');
expectStatus(creds, '200');
});
it('Should still enforce the auth access list after the host is disabled and enabled', () => {
toggleHost();
expectStatus(null, '401');
expectStatus(creds, '200');
});
it('Should enforce a client access list applied to an existing host', () => {
setHostAccessList(clientListId);
expectStatus(null, '403');
});
it('Should still enforce the client access list after the host is disabled and enabled', () => {
toggleHost();
expectStatus(null, '403');
});
it('Should be able to delete the proxy host and access lists', () => {
cy.task('backendApiDelete', {
token: token,
path: `/api/nginx/proxy-hosts/${hostId}`,
}).then((data) => {
cy.validateSwaggerSchema('delete', 200, '/nginx/proxy-hosts/{hostID}', data);
expect(data).to.be.equal(true);
});
// Each delete signals an nginx reload and returns immediately. A request
// made while the workers are being swapped can be reset by the nginx
// serving the API (502 from squid), so let each reload settle first.
[authListId, clientListId].forEach((id) => {
cy.wait(1500);
cy.task('backendApiDelete', {
token: token,
path: `/api/nginx/access-lists/${id}`,
}).then((data) => {
cy.validateSwaggerSchema('delete', 200, '/nginx/access-lists/{listID}', data);
expect(data).to.be.equal(true);
});
});
});
});