diff --git a/backend/internal/stream.js b/backend/internal/stream.js index f69a5c17..4d49bc3a 100644 --- a/backend/internal/stream.js +++ b/backend/internal/stream.js @@ -9,7 +9,7 @@ const internalHost = require('./host'); const {castJsonIfNeed} = require('../lib/helpers'); function omissions () { - return ['is_deleted']; + return ['is_deleted', 'owner.is_deleted', 'certificate.is_deleted']; } const internalStream = { diff --git a/backend/models/stream.js b/backend/models/stream.js index 40fd601c..dbec2dcb 100644 --- a/backend/models/stream.js +++ b/backend/models/stream.js @@ -8,6 +8,7 @@ const now = require('./now_helper'); Model.knex(db); const boolFields = [ + 'enabled', 'is_deleted', 'tcp_forwarding', 'udp_forwarding', diff --git a/backend/schema/components/stream-object.json b/backend/schema/components/stream-object.json index 0ab8f907..cb216f5a 100644 --- a/backend/schema/components/stream-object.json +++ b/backend/schema/components/stream-object.json @@ -20,8 +20,25 @@ "type": "integer", "minimum": 1, "maximum": 65535, - "if": {"properties": {"tcp_forwarding": {"const": true}}}, - "then": {"not": {"oneOf": [{"const": 80}, {"const": 443}]}} + "if": { + "type": "object", + "properties": { + "tcp_forwarding": { + "const": true + } + } + }, + "then": { + "not": { + "oneOf": [ + { + "const": 80 + }, { + "const": 443 + } + ] + } + } }, "forwarding_host": { "anyOf": [ @@ -60,6 +77,12 @@ }, "meta": { "type": "object" + }, + "owner": { + "$ref": "./user-object.json" + }, + "certificate": { + "$ref": "./certificate-object.json" } } } diff --git a/docker/docker-compose.ci.yml b/docker/docker-compose.ci.yml index 022f2813..6e8c1f4f 100644 --- a/docker/docker-compose.ci.yml +++ b/docker/docker-compose.ci.yml @@ -22,6 +22,10 @@ services: test: ["CMD", "/usr/bin/check-health"] interval: 10s timeout: 3s + expose: + - '80-81/tcp' + - '443/tcp' + - '1500-1503/tcp' networks: fulltest: aliases: diff --git a/test/cypress/Dockerfile b/test/cypress/Dockerfile index c0bc0ba8..eeca8e86 100644 --- a/test/cypress/Dockerfile +++ b/test/cypress/Dockerfile @@ -1,4 +1,4 @@ -FROM cypress/included:13.9.0 +FROM cypress/included:14.0.1 COPY --chown=1000 ./test /test @@ -12,7 +12,7 @@ RUN wget "https://github.com/testssl/testssl.sh/archive/refs/tags/v3.2rc4.tar.gz && mv /tmp/testssl.sh-3.2rc4 /testssl \ && rm /tmp/testssl.tgz \ && apt-get update \ - && apt-get install -y bsdmainutils \ + && apt-get install -y bsdmainutils curl dnsutils \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* \ && wget "https://github.com/FiloSottile/mkcert/releases/download/v1.4.4/mkcert-v1.4.4-linux-amd64" -O /bin/mkcert \ diff --git a/test/cypress/e2e/api/Streams.cy.js b/test/cypress/e2e/api/Streams.cy.js new file mode 100644 index 00000000..41b2be63 --- /dev/null +++ b/test/cypress/e2e/api/Streams.cy.js @@ -0,0 +1,183 @@ +/// + +describe('Streams', () => { + let token; + + before(() => { + cy.getToken().then((tok) => { + token = tok; + }); + // Create a custom cert pair + cy.exec('mkcert -cert-file=/test/cypress/fixtures/website1.pem -key-file=/test/cypress/fixtures/website1.key.pem website1.example.com').then((result) => { + expect(result.code).to.eq(0); + // Install CA + cy.exec('mkcert -install').then((result) => { + expect(result.code).to.eq(0); + }); + }); + }); + + it('Should be able to create TCP Stream', function() { + cy.task('backendApiPost', { + token: token, + path: '/api/nginx/streams', + data: { + incoming_port: 1500, + forwarding_host: '127.0.0.1', + forwarding_port: 80, + certificate_id: 0, + meta: { + dns_provider_credentials: "", + letsencrypt_agree: false, + dns_challenge: true + }, + tcp_forwarding: true, + udp_forwarding: false + } + }).then((data) => { + cy.validateSwaggerSchema('post', 201, '/nginx/streams', data); + expect(data).to.have.property('id'); + expect(data.id).to.be.greaterThan(0); + expect(data).to.have.property('enabled', true); + expect(data).to.have.property('tcp_forwarding', true); + expect(data).to.have.property('udp_forwarding', false); + + cy.exec('curl --noproxy -I http://website1.example.com:1500').then((result) => { + expect(result.code).to.eq(0); + expect(result.stderr).to.contain('HTTP/1.1 200 OK'); + }); + }); + }); + + it('Should be able to create UDP Stream', function() { + cy.task('backendApiPost', { + token: token, + path: '/api/nginx/streams', + data: { + incoming_port: 1501, + forwarding_host: '127.0.0.1', + forwarding_port: 80, + certificate_id: 0, + meta: { + dns_provider_credentials: "", + letsencrypt_agree: false, + dns_challenge: true + }, + tcp_forwarding: false, + udp_forwarding: true + } + }).then((data) => { + cy.validateSwaggerSchema('post', 201, '/nginx/streams', data); + expect(data).to.have.property('id'); + expect(data.id).to.be.greaterThan(0); + expect(data).to.have.property('enabled', true); + expect(data).to.have.property('tcp_forwarding', false); + expect(data).to.have.property('udp_forwarding', true); + }); + }); + + it('Should be able to create TCP/UDP Stream', function() { + cy.task('backendApiPost', { + token: token, + path: '/api/nginx/streams', + data: { + incoming_port: 1502, + forwarding_host: '127.0.0.1', + forwarding_port: 80, + certificate_id: 0, + meta: { + dns_provider_credentials: "", + letsencrypt_agree: false, + dns_challenge: true + }, + tcp_forwarding: true, + udp_forwarding: true + } + }).then((data) => { + cy.validateSwaggerSchema('post', 201, '/nginx/streams', data); + expect(data).to.have.property('id'); + expect(data.id).to.be.greaterThan(0); + expect(data).to.have.property('enabled', true); + expect(data).to.have.property('tcp_forwarding', true); + expect(data).to.have.property('udp_forwarding', true); + + // Check the port is working + cy.exec('curl --noproxy -I http://website1.example.com:1502').then((result) => { + expect(result.code).to.eq(0); + expect(result.stderr).to.contain('HTTP/1.1 200 OK'); + }); + }); + }); + + it('Should be able to create SSL TCP Stream', function() { + let certID = 0; + + // Create custom cert + cy.task('backendApiPost', { + token: token, + path: '/api/nginx/certificates', + data: { + provider: "other", + nice_name: "Custom Certificate for SSL Stream", + }, + }).then((data) => { + cy.validateSwaggerSchema('post', 201, '/nginx/certificates', data); + expect(data).to.have.property('id'); + certID = data.id; + + // Upload files + cy.task('backendApiPostFiles', { + token: token, + path: `/api/nginx/certificates/${certID}/upload`, + files: { + certificate: 'website1.pem', + certificate_key: 'website1.key.pem', + }, + }).then((data) => { + cy.validateSwaggerSchema('post', 200, '/nginx/certificates/{certID}/upload', data); + expect(data).to.have.property('certificate'); + expect(data).to.have.property('certificate_key'); + + // Create the stream + cy.task('backendApiPost', { + token: token, + path: '/api/nginx/streams', + data: { + incoming_port: 1503, + forwarding_host: '127.0.0.1', + forwarding_port: 80, + certificate_id: certID, + meta: { + dns_provider_credentials: "", + letsencrypt_agree: false, + dns_challenge: true + }, + tcp_forwarding: true, + udp_forwarding: false + } + }).then((data) => { + cy.validateSwaggerSchema('post', 201, '/nginx/streams', data); + expect(data).to.have.property('id'); + expect(data.id).to.be.greaterThan(0); + expect(data).to.have.property("enabled", true); + expect(data).to.have.property('tcp_forwarding', true); + expect(data).to.have.property('udp_forwarding', false); + expect(data).to.have.property('certificate_id', certID); + + // wait 5 mins + cy.wait(300000); + + // Check the ssl termination + cy.exec('/testssl/testssl.sh --quiet --add-ca="$(/bin/mkcert -CAROOT)" --jsonfile=/test/cypress/results/testssl.json website1.example.com:1503', { + timeout: 120000, // 2 minutes + failOnNonZeroExit: false, + }).then((result) => { + expect(result.code).to.eq(0); + }); + }); + }); + }); + }); + +}); + diff --git a/test/package.json b/test/package.json index b52ecfbd..a3a3cad6 100644 --- a/test/package.json +++ b/test/package.json @@ -4,18 +4,18 @@ "description": "", "main": "index.js", "dependencies": { - "@jc21/cypress-swagger-validation": "^0.3.1", - "axios": "^1.7.7", - "cypress": "^13.15.0", - "cypress-multi-reporters": "^1.6.4", + "@jc21/cypress-swagger-validation": "^0.3.2", + "axios": "^1.7.9", + "cypress": "^14.0.1", + "cypress-multi-reporters": "^2.0.5", "cypress-wait-until": "^3.0.2", - "eslint": "^9.12.0", + "eslint": "^9.19.0", "eslint-plugin-align-assignments": "^1.1.2", "eslint-plugin-chai-friendly": "^1.0.1", - "eslint-plugin-cypress": "^3.5.0", + "eslint-plugin-cypress": "^4.1.0", "form-data": "^4.0.1", "lodash": "^4.17.21", - "mocha": "^10.7.3", + "mocha": "^11.1.0", "mocha-junit-reporter": "^2.2.1" }, "scripts": {