mirror of
				https://github.com/NginxProxyManager/nginx-proxy-manager.git
				synced 2025-10-29 23:03:34 +00:00 
			
		
		
		
	Merge pull request #4344 from NginxProxyManager/stream-ssl
SSL for Streams - 2025
This commit is contained in:
		| @@ -4,10 +4,12 @@ const utils            = require('../lib/utils'); | |||||||
| const streamModel         = require('../models/stream'); | const streamModel         = require('../models/stream'); | ||||||
| const internalNginx       = require('./nginx'); | const internalNginx       = require('./nginx'); | ||||||
| const internalAuditLog    = require('./audit-log'); | const internalAuditLog    = require('./audit-log'); | ||||||
|  | const internalCertificate = require('./certificate'); | ||||||
|  | const internalHost        = require('./host'); | ||||||
| const {castJsonIfNeed}    = require('../lib/helpers'); | const {castJsonIfNeed}    = require('../lib/helpers'); | ||||||
|  |  | ||||||
| function omissions () { | function omissions () { | ||||||
| 	return ['is_deleted']; | 	return ['is_deleted', 'owner.is_deleted', 'certificate.is_deleted']; | ||||||
| } | } | ||||||
|  |  | ||||||
| const internalStream = { | const internalStream = { | ||||||
| @@ -18,6 +20,12 @@ const internalStream = { | |||||||
| 	 * @returns {Promise} | 	 * @returns {Promise} | ||||||
| 	 */ | 	 */ | ||||||
| 	create: (access, data) => { | 	create: (access, data) => { | ||||||
|  | 		const create_certificate = data.certificate_id === 'new'; | ||||||
|  |  | ||||||
|  | 		if (create_certificate) { | ||||||
|  | 			delete data.certificate_id; | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		return access.can('streams:create', data) | 		return access.can('streams:create', data) | ||||||
| 			.then((/*access_data*/) => { | 			.then((/*access_data*/) => { | ||||||
| 				// TODO: At this point the existing ports should have been checked | 				// TODO: At this point the existing ports should have been checked | ||||||
| @@ -27,16 +35,44 @@ const internalStream = { | |||||||
| 					data.meta = {}; | 					data.meta = {}; | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
|  | 				// streams aren't routed by domain name so don't store domain names in the DB | ||||||
|  | 				let data_no_domains = structuredClone(data); | ||||||
|  | 				delete data_no_domains.domain_names; | ||||||
|  |  | ||||||
| 				return streamModel | 				return streamModel | ||||||
| 					.query() | 					.query() | ||||||
| 					.insertAndFetch(data) | 					.insertAndFetch(data_no_domains) | ||||||
| 					.then(utils.omitRow(omissions())); | 					.then(utils.omitRow(omissions())); | ||||||
| 			}) | 			}) | ||||||
|  | 			.then((row) => { | ||||||
|  | 				if (create_certificate) { | ||||||
|  | 					return internalCertificate.createQuickCertificate(access, data) | ||||||
|  | 						.then((cert) => { | ||||||
|  | 							// update host with cert id | ||||||
|  | 							return internalStream.update(access, { | ||||||
|  | 								id:             row.id, | ||||||
|  | 								certificate_id: cert.id | ||||||
|  | 							}); | ||||||
|  | 						}) | ||||||
|  | 						.then(() => { | ||||||
|  | 							return row; | ||||||
|  | 						}); | ||||||
|  | 				} else { | ||||||
|  | 					return row; | ||||||
|  | 				} | ||||||
|  | 			}) | ||||||
|  | 			.then((row) => { | ||||||
|  | 				// re-fetch with cert | ||||||
|  | 				return internalStream.get(access, { | ||||||
|  | 					id:     row.id, | ||||||
|  | 					expand: ['certificate', 'owner'] | ||||||
|  | 				}); | ||||||
|  | 			}) | ||||||
| 			.then((row) => { | 			.then((row) => { | ||||||
| 				// Configure nginx | 				// Configure nginx | ||||||
| 				return internalNginx.configure(streamModel, 'stream', row) | 				return internalNginx.configure(streamModel, 'stream', row) | ||||||
| 					.then(() => { | 					.then(() => { | ||||||
| 						return internalStream.get(access, {id: row.id, expand: ['owner']}); | 						return row; | ||||||
| 					}); | 					}); | ||||||
| 			}) | 			}) | ||||||
| 			.then((row) => { | 			.then((row) => { | ||||||
| @@ -60,6 +96,12 @@ const internalStream = { | |||||||
| 	 * @return {Promise} | 	 * @return {Promise} | ||||||
| 	 */ | 	 */ | ||||||
| 	update: (access, data) => { | 	update: (access, data) => { | ||||||
|  | 		const create_certificate = data.certificate_id === 'new'; | ||||||
|  |  | ||||||
|  | 		if (create_certificate) { | ||||||
|  | 			delete data.certificate_id; | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		return access.can('streams:update', data.id) | 		return access.can('streams:update', data.id) | ||||||
| 			.then((/*access_data*/) => { | 			.then((/*access_data*/) => { | ||||||
| 				// TODO: at this point the existing streams should have been checked | 				// TODO: at this point the existing streams should have been checked | ||||||
| @@ -71,16 +113,32 @@ const internalStream = { | |||||||
| 					throw new error.InternalValidationError('Stream could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id); | 					throw new error.InternalValidationError('Stream could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
|  | 				if (create_certificate) { | ||||||
|  | 					return internalCertificate.createQuickCertificate(access, { | ||||||
|  | 						domain_names: data.domain_names || row.domain_names, | ||||||
|  | 						meta:         _.assign({}, row.meta, data.meta) | ||||||
|  | 					}) | ||||||
|  | 						.then((cert) => { | ||||||
|  | 							// update host with cert id | ||||||
|  | 							data.certificate_id = cert.id; | ||||||
|  | 						}) | ||||||
|  | 						.then(() => { | ||||||
|  | 							return row; | ||||||
|  | 						}); | ||||||
|  | 				} else { | ||||||
|  | 					return row; | ||||||
|  | 				} | ||||||
|  | 			}) | ||||||
|  | 			.then((row) => { | ||||||
|  | 				// Add domain_names to the data in case it isn't there, so that the audit log renders correctly. The order is important here. | ||||||
|  | 				data = _.assign({}, { | ||||||
|  | 					domain_names: row.domain_names | ||||||
|  | 				}, data); | ||||||
|  |  | ||||||
| 				return streamModel | 				return streamModel | ||||||
| 					.query() | 					.query() | ||||||
| 					.patchAndFetchById(row.id, data) | 					.patchAndFetchById(row.id, data) | ||||||
| 					.then(utils.omitRow(omissions())) | 					.then(utils.omitRow(omissions())) | ||||||
| 					.then((saved_row) => { |  | ||||||
| 						return internalNginx.configure(streamModel, 'stream', saved_row) |  | ||||||
| 							.then(() => { |  | ||||||
| 								return internalStream.get(access, {id: row.id, expand: ['owner']}); |  | ||||||
| 							}); |  | ||||||
| 					}) |  | ||||||
| 					.then((saved_row) => { | 					.then((saved_row) => { | ||||||
| 						// Add to audit log | 						// Add to audit log | ||||||
| 						return internalAuditLog.add(access, { | 						return internalAuditLog.add(access, { | ||||||
| @@ -93,6 +151,17 @@ const internalStream = { | |||||||
| 								return saved_row; | 								return saved_row; | ||||||
| 							}); | 							}); | ||||||
| 					}); | 					}); | ||||||
|  | 			}) | ||||||
|  | 			.then(() => { | ||||||
|  | 				return internalStream.get(access, {id: data.id, expand: ['owner', 'certificate']}) | ||||||
|  | 					.then((row) => { | ||||||
|  | 						return internalNginx.configure(streamModel, 'stream', row) | ||||||
|  | 							.then((new_meta) => { | ||||||
|  | 								row.meta = new_meta; | ||||||
|  | 								row      = internalHost.cleanRowCertificateMeta(row); | ||||||
|  | 								return _.omit(row, omissions()); | ||||||
|  | 							}); | ||||||
|  | 					}); | ||||||
| 			}); | 			}); | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| @@ -115,7 +184,7 @@ const internalStream = { | |||||||
| 					.query() | 					.query() | ||||||
| 					.where('is_deleted', 0) | 					.where('is_deleted', 0) | ||||||
| 					.andWhere('id', data.id) | 					.andWhere('id', data.id) | ||||||
| 					.allowGraph('[owner]') | 					.allowGraph('[owner,certificate]') | ||||||
| 					.first(); | 					.first(); | ||||||
|  |  | ||||||
| 				if (access_data.permission_visibility !== 'all') { | 				if (access_data.permission_visibility !== 'all') { | ||||||
| @@ -132,6 +201,7 @@ const internalStream = { | |||||||
| 				if (!row || !row.id) { | 				if (!row || !row.id) { | ||||||
| 					throw new error.ItemNotFoundError(data.id); | 					throw new error.ItemNotFoundError(data.id); | ||||||
| 				} | 				} | ||||||
|  | 				row = internalHost.cleanRowCertificateMeta(row); | ||||||
| 				// Custom omissions | 				// Custom omissions | ||||||
| 				if (typeof data.omit !== 'undefined' && data.omit !== null) { | 				if (typeof data.omit !== 'undefined' && data.omit !== null) { | ||||||
| 					row = _.omit(row, data.omit); | 					row = _.omit(row, data.omit); | ||||||
| @@ -197,14 +267,14 @@ const internalStream = { | |||||||
| 			.then(() => { | 			.then(() => { | ||||||
| 				return internalStream.get(access, { | 				return internalStream.get(access, { | ||||||
| 					id:     data.id, | 					id:     data.id, | ||||||
| 					expand: ['owner'] | 					expand: ['certificate', 'owner'] | ||||||
| 				}); | 				}); | ||||||
| 			}) | 			}) | ||||||
| 			.then((row) => { | 			.then((row) => { | ||||||
| 				if (!row || !row.id) { | 				if (!row || !row.id) { | ||||||
| 					throw new error.ItemNotFoundError(data.id); | 					throw new error.ItemNotFoundError(data.id); | ||||||
| 				} else if (row.enabled) { | 				} else if (row.enabled) { | ||||||
| 					throw new error.ValidationError('Host is already enabled'); | 					throw new error.ValidationError('Stream is already enabled'); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				row.enabled = 1; | 				row.enabled = 1; | ||||||
| @@ -250,7 +320,7 @@ const internalStream = { | |||||||
| 				if (!row || !row.id) { | 				if (!row || !row.id) { | ||||||
| 					throw new error.ItemNotFoundError(data.id); | 					throw new error.ItemNotFoundError(data.id); | ||||||
| 				} else if (!row.enabled) { | 				} else if (!row.enabled) { | ||||||
| 					throw new error.ValidationError('Host is already disabled'); | 					throw new error.ValidationError('Stream is already disabled'); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				row.enabled = 0; | 				row.enabled = 0; | ||||||
| @@ -298,7 +368,7 @@ const internalStream = { | |||||||
| 					.query() | 					.query() | ||||||
| 					.where('is_deleted', 0) | 					.where('is_deleted', 0) | ||||||
| 					.groupBy('id') | 					.groupBy('id') | ||||||
| 					.allowGraph('[owner]') | 					.allowGraph('[owner,certificate]') | ||||||
| 					.orderByRaw('CAST(incoming_port AS INTEGER) ASC'); | 					.orderByRaw('CAST(incoming_port AS INTEGER) ASC'); | ||||||
|  |  | ||||||
| 				if (access_data.permission_visibility !== 'all') { | 				if (access_data.permission_visibility !== 'all') { | ||||||
| @@ -317,6 +387,13 @@ const internalStream = { | |||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				return query.then(utils.omitRows(omissions())); | 				return query.then(utils.omitRows(omissions())); | ||||||
|  | 			}) | ||||||
|  | 			.then((rows) => { | ||||||
|  | 				if (typeof expand !== 'undefined' && expand !== null && expand.indexOf('certificate') !== -1) { | ||||||
|  | 					return internalHost.cleanAllRowsCertificateMeta(rows); | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				return rows; | ||||||
| 			}); | 			}); | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										38
									
								
								backend/migrations/20240427161436_stream_ssl.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								backend/migrations/20240427161436_stream_ssl.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | |||||||
|  | const migrate_name = 'stream_ssl'; | ||||||
|  | const logger       = require('../logger').migrate; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Migrate | ||||||
|  |  * | ||||||
|  |  * @see http://knexjs.org/#Schema | ||||||
|  |  * | ||||||
|  |  * @param   {Object} knex | ||||||
|  |  * @returns {Promise} | ||||||
|  |  */ | ||||||
|  | exports.up = function (knex) { | ||||||
|  | 	logger.info('[' + migrate_name + '] Migrating Up...'); | ||||||
|  |  | ||||||
|  | 	return knex.schema.table('stream', (table) => { | ||||||
|  | 		table.integer('certificate_id').notNull().unsigned().defaultTo(0); | ||||||
|  | 	}) | ||||||
|  | 		.then(function () { | ||||||
|  | 			logger.info('[' + migrate_name + '] stream Table altered'); | ||||||
|  | 		}); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Undo Migrate | ||||||
|  |  * | ||||||
|  |  * @param   {Object} knex | ||||||
|  |  * @returns {Promise} | ||||||
|  |  */ | ||||||
|  | exports.down = function (knex) { | ||||||
|  | 	logger.info('[' + migrate_name + '] Migrating Down...'); | ||||||
|  |  | ||||||
|  | 	return knex.schema.table('stream', (table) => { | ||||||
|  | 		table.dropColumn('certificate_id'); | ||||||
|  | 	}) | ||||||
|  | 		.then(function () { | ||||||
|  | 			logger.info('[' + migrate_name + '] stream Table altered'); | ||||||
|  | 		}); | ||||||
|  | }; | ||||||
| @@ -1,15 +1,14 @@ | |||||||
| // Objection Docs: | const Model       = require('objection').Model; | ||||||
| // http://vincit.github.io/objection.js/ |  | ||||||
|  |  | ||||||
| const db          = require('../db'); | const db          = require('../db'); | ||||||
| const helpers     = require('../lib/helpers'); | const helpers     = require('../lib/helpers'); | ||||||
| const Model   = require('objection').Model; |  | ||||||
| const User        = require('./user'); | const User        = require('./user'); | ||||||
|  | const Certificate = require('./certificate'); | ||||||
| const now         = require('./now_helper'); | const now         = require('./now_helper'); | ||||||
|  |  | ||||||
| Model.knex(db); | Model.knex(db); | ||||||
|  |  | ||||||
| const boolFields = [ | const boolFields = [ | ||||||
|  | 	'enabled', | ||||||
| 	'is_deleted', | 	'is_deleted', | ||||||
| 	'tcp_forwarding', | 	'tcp_forwarding', | ||||||
| 	'udp_forwarding', | 	'udp_forwarding', | ||||||
| @@ -64,6 +63,17 @@ class Stream extends Model { | |||||||
| 				modify: function (qb) { | 				modify: function (qb) { | ||||||
| 					qb.where('user.is_deleted', 0); | 					qb.where('user.is_deleted', 0); | ||||||
| 				} | 				} | ||||||
|  | 			}, | ||||||
|  | 			certificate: { | ||||||
|  | 				relation:   Model.HasOneRelation, | ||||||
|  | 				modelClass: Certificate, | ||||||
|  | 				join:       { | ||||||
|  | 					from: 'stream.certificate_id', | ||||||
|  | 					to:   'certificate.id' | ||||||
|  | 				}, | ||||||
|  | 				modify: function (qb) { | ||||||
|  | 					qb.where('certificate.is_deleted', 0); | ||||||
|  | 				} | ||||||
| 			} | 			} | ||||||
| 		}; | 		}; | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -19,9 +19,7 @@ | |||||||
| 		"incoming_port": { | 		"incoming_port": { | ||||||
| 			"type": "integer", | 			"type": "integer", | ||||||
| 			"minimum": 1, | 			"minimum": 1, | ||||||
| 			"maximum": 65535, | 			"maximum": 65535 | ||||||
| 			"if": {"properties": {"tcp_forwarding": {"const": true}}}, |  | ||||||
| 			"then": {"not": {"oneOf": [{"const": 80}, {"const": 443}]}} |  | ||||||
| 		}, | 		}, | ||||||
| 		"forwarding_host": { | 		"forwarding_host": { | ||||||
| 			"anyOf": [ | 			"anyOf": [ | ||||||
| @@ -55,8 +53,24 @@ | |||||||
| 		"enabled": { | 		"enabled": { | ||||||
| 			"$ref": "../common.json#/properties/enabled" | 			"$ref": "../common.json#/properties/enabled" | ||||||
| 		}, | 		}, | ||||||
|  | 		"certificate_id": { | ||||||
|  | 			"$ref": "../common.json#/properties/certificate_id" | ||||||
|  | 		}, | ||||||
| 		"meta": { | 		"meta": { | ||||||
| 			"type": "object" | 			"type": "object" | ||||||
|  | 		}, | ||||||
|  | 		"owner": { | ||||||
|  | 			"$ref": "./user-object.json" | ||||||
|  | 		}, | ||||||
|  | 		"certificate": { | ||||||
|  | 			"oneOf": [ | ||||||
|  | 				{ | ||||||
|  | 					"type": "null" | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					"$ref": "./certificate-object.json" | ||||||
|  | 				} | ||||||
|  | 			] | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -14,7 +14,7 @@ | |||||||
| 			"description": "Expansions", | 			"description": "Expansions", | ||||||
| 			"schema": { | 			"schema": { | ||||||
| 				"type": "string", | 				"type": "string", | ||||||
| 				"enum": ["access_list", "owner", "certificate"] | 				"enum": ["owner", "certificate"] | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	], | 	], | ||||||
| @@ -40,7 +40,8 @@ | |||||||
| 										"nginx_online": true, | 										"nginx_online": true, | ||||||
| 										"nginx_err": null | 										"nginx_err": null | ||||||
| 									}, | 									}, | ||||||
| 									"enabled": true | 									"enabled": true, | ||||||
|  | 									"certificate_id": 0 | ||||||
| 								} | 								} | ||||||
| 							] | 							] | ||||||
| 						} | 						} | ||||||
|   | |||||||
| @@ -32,6 +32,9 @@ | |||||||
| 						"udp_forwarding": { | 						"udp_forwarding": { | ||||||
| 							"$ref": "../../../components/stream-object.json#/properties/udp_forwarding" | 							"$ref": "../../../components/stream-object.json#/properties/udp_forwarding" | ||||||
| 						}, | 						}, | ||||||
|  | 						"certificate_id": { | ||||||
|  | 							"$ref": "../../../components/stream-object.json#/properties/certificate_id" | ||||||
|  | 						}, | ||||||
| 						"meta": { | 						"meta": { | ||||||
| 							"$ref": "../../../components/stream-object.json#/properties/meta" | 							"$ref": "../../../components/stream-object.json#/properties/meta" | ||||||
| 						} | 						} | ||||||
| @@ -73,7 +76,8 @@ | |||||||
| 									"nickname": "Admin", | 									"nickname": "Admin", | ||||||
| 									"avatar": "", | 									"avatar": "", | ||||||
| 									"roles": ["admin"] | 									"roles": ["admin"] | ||||||
| 								} | 								}, | ||||||
|  | 								"certificate_id": 0 | ||||||
| 							} | 							} | ||||||
| 						} | 						} | ||||||
| 					}, | 					}, | ||||||
|   | |||||||
| @@ -40,7 +40,8 @@ | |||||||
| 									"nginx_online": true, | 									"nginx_online": true, | ||||||
| 									"nginx_err": null | 									"nginx_err": null | ||||||
| 								}, | 								}, | ||||||
| 								"enabled": true | 								"enabled": true, | ||||||
|  | 								"certificate_id": 0 | ||||||
| 							} | 							} | ||||||
| 						} | 						} | ||||||
| 					}, | 					}, | ||||||
|   | |||||||
| @@ -29,56 +29,26 @@ | |||||||
| 					"additionalProperties": false, | 					"additionalProperties": false, | ||||||
| 					"minProperties": 1, | 					"minProperties": 1, | ||||||
| 					"properties": { | 					"properties": { | ||||||
| 						"domain_names": { | 						"incoming_port": { | ||||||
| 							"$ref": "../../../../components/proxy-host-object.json#/properties/domain_names" | 							"$ref": "../../../../components/stream-object.json#/properties/incoming_port" | ||||||
| 						}, | 						}, | ||||||
| 						"forward_scheme": { | 						"forwarding_host": { | ||||||
| 							"$ref": "../../../../components/proxy-host-object.json#/properties/forward_scheme" | 							"$ref": "../../../../components/stream-object.json#/properties/forwarding_host" | ||||||
| 						}, | 						}, | ||||||
| 						"forward_host": { | 						"forwarding_port": { | ||||||
| 							"$ref": "../../../../components/proxy-host-object.json#/properties/forward_host" | 							"$ref": "../../../../components/stream-object.json#/properties/forwarding_port" | ||||||
| 						}, | 						}, | ||||||
| 						"forward_port": { | 						"tcp_forwarding": { | ||||||
| 							"$ref": "../../../../components/proxy-host-object.json#/properties/forward_port" | 							"$ref": "../../../../components/stream-object.json#/properties/tcp_forwarding" | ||||||
|  | 						}, | ||||||
|  | 						"udp_forwarding": { | ||||||
|  | 							"$ref": "../../../../components/stream-object.json#/properties/udp_forwarding" | ||||||
| 						}, | 						}, | ||||||
| 						"certificate_id": { | 						"certificate_id": { | ||||||
| 							"$ref": "../../../../components/proxy-host-object.json#/properties/certificate_id" | 							"$ref": "../../../../components/stream-object.json#/properties/certificate_id" | ||||||
| 						}, |  | ||||||
| 						"ssl_forced": { |  | ||||||
| 							"$ref": "../../../../components/proxy-host-object.json#/properties/ssl_forced" |  | ||||||
| 						}, |  | ||||||
| 						"hsts_enabled": { |  | ||||||
| 							"$ref": "../../../../components/proxy-host-object.json#/properties/hsts_enabled" |  | ||||||
| 						}, |  | ||||||
| 						"hsts_subdomains": { |  | ||||||
| 							"$ref": "../../../../components/proxy-host-object.json#/properties/hsts_subdomains" |  | ||||||
| 						}, |  | ||||||
| 						"http2_support": { |  | ||||||
| 							"$ref": "../../../../components/proxy-host-object.json#/properties/http2_support" |  | ||||||
| 						}, |  | ||||||
| 						"block_exploits": { |  | ||||||
| 							"$ref": "../../../../components/proxy-host-object.json#/properties/block_exploits" |  | ||||||
| 						}, |  | ||||||
| 						"caching_enabled": { |  | ||||||
| 							"$ref": "../../../../components/proxy-host-object.json#/properties/caching_enabled" |  | ||||||
| 						}, |  | ||||||
| 						"allow_websocket_upgrade": { |  | ||||||
| 							"$ref": "../../../../components/proxy-host-object.json#/properties/allow_websocket_upgrade" |  | ||||||
| 						}, |  | ||||||
| 						"access_list_id": { |  | ||||||
| 							"$ref": "../../../../components/proxy-host-object.json#/properties/access_list_id" |  | ||||||
| 						}, |  | ||||||
| 						"advanced_config": { |  | ||||||
| 							"$ref": "../../../../components/proxy-host-object.json#/properties/advanced_config" |  | ||||||
| 						}, |  | ||||||
| 						"enabled": { |  | ||||||
| 							"$ref": "../../../../components/proxy-host-object.json#/properties/enabled" |  | ||||||
| 						}, | 						}, | ||||||
| 						"meta": { | 						"meta": { | ||||||
| 							"$ref": "../../../../components/proxy-host-object.json#/properties/meta" | 							"$ref": "../../../../components/stream-object.json#/properties/meta" | ||||||
| 						}, |  | ||||||
| 						"locations": { |  | ||||||
| 							"$ref": "../../../../components/proxy-host-object.json#/properties/locations" |  | ||||||
| 						} | 						} | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| @@ -94,42 +64,32 @@ | |||||||
| 						"default": { | 						"default": { | ||||||
| 							"value": { | 							"value": { | ||||||
| 								"id": 1, | 								"id": 1, | ||||||
| 								"created_on": "2024-10-08T23:23:03.000Z", | 								"created_on": "2024-10-09T02:33:45.000Z", | ||||||
| 								"modified_on": "2024-10-08T23:26:37.000Z", | 								"modified_on": "2024-10-09T02:33:45.000Z", | ||||||
| 								"owner_user_id": 1, | 								"owner_user_id": 1, | ||||||
| 								"domain_names": ["test.example.com"], | 								"incoming_port": 9090, | ||||||
| 								"forward_host": "192.168.0.10", | 								"forwarding_host": "router.internal", | ||||||
| 								"forward_port": 8989, | 								"forwarding_port": 80, | ||||||
| 								"access_list_id": 0, | 								"tcp_forwarding": true, | ||||||
| 								"certificate_id": 0, | 								"udp_forwarding": false, | ||||||
| 								"ssl_forced": false, |  | ||||||
| 								"caching_enabled": false, |  | ||||||
| 								"block_exploits": false, |  | ||||||
| 								"advanced_config": "", |  | ||||||
| 								"meta": { | 								"meta": { | ||||||
| 									"nginx_online": true, | 									"nginx_online": true, | ||||||
| 									"nginx_err": null | 									"nginx_err": null | ||||||
| 								}, | 								}, | ||||||
| 								"allow_websocket_upgrade": false, |  | ||||||
| 								"http2_support": false, |  | ||||||
| 								"forward_scheme": "http", |  | ||||||
| 								"enabled": true, | 								"enabled": true, | ||||||
| 								"hsts_enabled": false, |  | ||||||
| 								"hsts_subdomains": false, |  | ||||||
| 								"owner": { | 								"owner": { | ||||||
| 									"id": 1, | 									"id": 1, | ||||||
| 									"created_on": "2024-10-07T22:43:55.000Z", | 									"created_on": "2024-10-09T02:33:16.000Z", | ||||||
| 									"modified_on": "2024-10-08T12:52:54.000Z", | 									"modified_on": "2024-10-09T02:33:16.000Z", | ||||||
| 									"is_deleted": false, | 									"is_deleted": false, | ||||||
| 									"is_disabled": false, | 									"is_disabled": false, | ||||||
| 									"email": "admin@example.com", | 									"email": "admin@example.com", | ||||||
| 									"name": "Administrator", | 									"name": "Administrator", | ||||||
| 									"nickname": "some guy", | 									"nickname": "Admin", | ||||||
| 									"avatar": "//www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?default=mm", | 									"avatar": "", | ||||||
| 									"roles": ["admin"] | 									"roles": ["admin"] | ||||||
| 								}, | 								}, | ||||||
| 								"certificate": null, | 								"certificate_id": 0 | ||||||
| 								"access_list": null |  | ||||||
| 							} | 							} | ||||||
| 						} | 						} | ||||||
| 					}, | 					}, | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ | |||||||
| {% if certificate.provider == "letsencrypt" %} | {% if certificate.provider == "letsencrypt" %} | ||||||
|   # Let's Encrypt SSL |   # Let's Encrypt SSL | ||||||
|   include conf.d/include/letsencrypt-acme-challenge.conf; |   include conf.d/include/letsencrypt-acme-challenge.conf; | ||||||
|  |   include conf.d/include/ssl-cache.conf; | ||||||
|   include conf.d/include/ssl-ciphers.conf; |   include conf.d/include/ssl-ciphers.conf; | ||||||
|   ssl_certificate /etc/letsencrypt/live/npm-{{ certificate_id }}/fullchain.pem; |   ssl_certificate /etc/letsencrypt/live/npm-{{ certificate_id }}/fullchain.pem; | ||||||
|   ssl_certificate_key /etc/letsencrypt/live/npm-{{ certificate_id }}/privkey.pem; |   ssl_certificate_key /etc/letsencrypt/live/npm-{{ certificate_id }}/privkey.pem; | ||||||
|   | |||||||
							
								
								
									
										13
									
								
								backend/templates/_certificates_stream.conf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								backend/templates/_certificates_stream.conf
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | {% if certificate and certificate_id > 0 %} | ||||||
|  | {% if certificate.provider == "letsencrypt" %} | ||||||
|  |   # Let's Encrypt SSL | ||||||
|  |   include conf.d/include/ssl-cache-stream.conf; | ||||||
|  |   include conf.d/include/ssl-ciphers.conf; | ||||||
|  |   ssl_certificate /etc/letsencrypt/live/npm-{{ certificate_id }}/fullchain.pem; | ||||||
|  |   ssl_certificate_key /etc/letsencrypt/live/npm-{{ certificate_id }}/privkey.pem; | ||||||
|  | {%- else %} | ||||||
|  |   # Custom SSL | ||||||
|  |   ssl_certificate /data/custom_ssl/npm-{{ certificate_id }}/fullchain.pem; | ||||||
|  |   ssl_certificate_key /data/custom_ssl/npm-{{ certificate_id }}/privkey.pem; | ||||||
|  | {%- endif -%} | ||||||
|  | {%- endif -%} | ||||||
| @@ -5,12 +5,10 @@ | |||||||
| {% if enabled %} | {% if enabled %} | ||||||
| {% if tcp_forwarding == 1 or tcp_forwarding == true -%} | {% if tcp_forwarding == 1 or tcp_forwarding == true -%} | ||||||
| server { | server { | ||||||
|   listen {{ incoming_port }}; |   listen {{ incoming_port }} {%- if certificate %} ssl {%- endif %}; | ||||||
| {% if ipv6 -%} |   {% unless ipv6 -%} # {%- endunless -%} listen [::]:{{ incoming_port }} {%- if certificate %} ssl {%- endif %}; | ||||||
|   listen [::]:{{ incoming_port }}; |  | ||||||
| {% else -%} |   {%- include "_certificates_stream.conf" %} | ||||||
|   #listen [::]:{{ incoming_port }}; |  | ||||||
| {% endif %} |  | ||||||
|  |  | ||||||
|   proxy_pass {{ forwarding_host }}:{{ forwarding_port }}; |   proxy_pass {{ forwarding_host }}:{{ forwarding_port }}; | ||||||
|  |  | ||||||
| @@ -19,14 +17,12 @@ server { | |||||||
|   include /data/nginx/custom/server_stream_tcp[.]conf; |   include /data/nginx/custom/server_stream_tcp[.]conf; | ||||||
| } | } | ||||||
| {% endif %} | {% endif %} | ||||||
| {% if udp_forwarding == 1 or udp_forwarding == true %} |  | ||||||
|  | {% if udp_forwarding == 1 or udp_forwarding == true -%} | ||||||
| server { | server { | ||||||
|   listen {{ incoming_port }} udp; |   listen {{ incoming_port }} udp; | ||||||
| {% if ipv6 -%} |   {% unless ipv6 -%} # {%- endunless -%} listen [::]:{{ incoming_port }} udp; | ||||||
|   listen [::]:{{ incoming_port }} udp; |  | ||||||
| {% else -%} |  | ||||||
|   #listen [::]:{{ incoming_port }} udp; |  | ||||||
| {% endif %} |  | ||||||
|   proxy_pass {{ forwarding_host }}:{{ forwarding_port }}; |   proxy_pass {{ forwarding_host }}:{{ forwarding_port }}; | ||||||
|  |  | ||||||
|   # Custom |   # Custom | ||||||
|   | |||||||
| @@ -22,6 +22,10 @@ services: | |||||||
|       test: ["CMD", "/usr/bin/check-health"] |       test: ["CMD", "/usr/bin/check-health"] | ||||||
|       interval: 10s |       interval: 10s | ||||||
|       timeout: 3s |       timeout: 3s | ||||||
|  |     expose: | ||||||
|  |       - '80-81/tcp' | ||||||
|  |       - '443/tcp' | ||||||
|  |       - '1500-1503/tcp' | ||||||
|     networks: |     networks: | ||||||
|       fulltest: |       fulltest: | ||||||
|         aliases: |         aliases: | ||||||
| @@ -97,7 +101,7 @@ services: | |||||||
|       HTTP_PROXY: 'squid:3128' |       HTTP_PROXY: 'squid:3128' | ||||||
|       HTTPS_PROXY: 'squid:3128' |       HTTPS_PROXY: 'squid:3128' | ||||||
|     volumes: |     volumes: | ||||||
|       - 'cypress_logs:/results' |       - 'cypress_logs:/test/results' | ||||||
|       - './dev/resolv.conf:/etc/resolv.conf:ro' |       - './dev/resolv.conf:/etc/resolv.conf:ro' | ||||||
|       - '/etc/localtime:/etc/localtime:ro' |       - '/etc/localtime:/etc/localtime:ro' | ||||||
|     command: cypress run --browser chrome --config-file=cypress/config/ci.js |     command: cypress run --browser chrome --config-file=cypress/config/ci.js | ||||||
|   | |||||||
| @@ -0,0 +1,2 @@ | |||||||
|  | ssl_session_timeout 5m; | ||||||
|  | ssl_session_cache shared:SSL_stream:50m; | ||||||
							
								
								
									
										2
									
								
								docker/rootfs/etc/nginx/conf.d/include/ssl-cache.conf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								docker/rootfs/etc/nginx/conf.d/include/ssl-cache.conf
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | |||||||
|  | ssl_session_timeout 5m; | ||||||
|  | ssl_session_cache shared:SSL:50m; | ||||||
| @@ -1,6 +1,3 @@ | |||||||
| ssl_session_timeout 5m; |  | ||||||
| ssl_session_cache shared:SSL:50m; |  | ||||||
|  |  | ||||||
| # intermediate configuration. tweak to your needs. | # intermediate configuration. tweak to your needs. | ||||||
| ssl_protocols TLSv1.2 TLSv1.3; | ssl_protocols TLSv1.2 TLSv1.3; | ||||||
| ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384'; | ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384'; | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ BLUE='\E[1;34m' | |||||||
| GREEN='\E[1;32m' | GREEN='\E[1;32m' | ||||||
| RESET='\E[0m' | RESET='\E[0m' | ||||||
|  |  | ||||||
| S6_OVERLAY_VERSION=3.1.5.0 | S6_OVERLAY_VERSION=3.2.0.2 | ||||||
| TARGETPLATFORM=${1:-linux/amd64} | TARGETPLATFORM=${1:-linux/amd64} | ||||||
|  |  | ||||||
| # Determine the correct binary file for the architecture given | # Determine the correct binary file for the architecture given | ||||||
|   | |||||||
| @@ -3,8 +3,16 @@ | |||||||
|         <h5 class="modal-title"><%- i18n('streams', 'form-title', {id: id}) %></h5> |         <h5 class="modal-title"><%- i18n('streams', 'form-title', {id: id}) %></h5> | ||||||
|         <button type="button" class="close cancel" aria-label="Close" data-dismiss="modal"> </button> |         <button type="button" class="close cancel" aria-label="Close" data-dismiss="modal"> </button> | ||||||
|     </div> |     </div> | ||||||
|     <div class="modal-body"> |     <div class="modal-body has-tabs"> | ||||||
|  |         <div class="alert alert-danger mb-0 rounded-0" id="le-error-info" role="alert"></div> | ||||||
|         <form> |         <form> | ||||||
|  |             <ul class="nav nav-tabs" role="tablist"> | ||||||
|  |                 <li role="presentation" class="nav-item"><a href="#details" aria-controls="tab1" role="tab" data-toggle="tab" class="nav-link active"><i class="fe fe-zap"></i> <%- i18n('all-hosts', 'details') %></a></li> | ||||||
|  |                 <li role="presentation" class="nav-item"><a href="#ssl-options" aria-controls="tab2" role="tab" data-toggle="tab" class="nav-link"><i class="fe fe-shield"></i> <%- i18n('str', 'ssl') %></a></li> | ||||||
|  |             </ul> | ||||||
|  |             <div class="tab-content"> | ||||||
|  |                 <!-- Details --> | ||||||
|  |                 <div role="tabpanel" class="tab-pane active" id="details"> | ||||||
|                     <div class="row"> |                     <div class="row"> | ||||||
|                         <div class="col-sm-12 col-md-12"> |                         <div class="col-sm-12 col-md-12"> | ||||||
|                             <div class="form-group"> |                             <div class="form-group"> | ||||||
| @@ -46,6 +54,137 @@ | |||||||
|                             <div class="forward-type-error invalid-feedback"><%- i18n('streams', 'forward-type-error') %></div> |                             <div class="forward-type-error invalid-feedback"><%- i18n('streams', 'forward-type-error') %></div> | ||||||
|                         </div> |                         </div> | ||||||
|                     </div> |                     </div> | ||||||
|  |                 </div> | ||||||
|  |  | ||||||
|  |                 <!-- SSL --> | ||||||
|  |                 <div role="tabpanel" class="tab-pane" id="ssl-options"> | ||||||
|  |                     <div class="row"> | ||||||
|  |                         <div class="col-sm-12 col-md-12"> | ||||||
|  |                             <div class="form-group"> | ||||||
|  |                                 <label class="form-label"><%- i18n('streams', 'ssl-certificate') %></label> | ||||||
|  |                                 <select name="certificate_id" class="form-control custom-select" placeholder="<%- i18n('all-hosts', 'none') %>"> | ||||||
|  |                                     <option selected value="0" data-data="{"id":0}" <%- certificate_id ? '' : 'selected' %>><%- i18n('all-hosts', 'none') %></option> | ||||||
|  |                                     <option selected value="new" data-data="{"id":"new"}"><%- i18n('all-hosts', 'new-cert') %></option> | ||||||
|  |                                 </select> | ||||||
|  |                             </div> | ||||||
|  |                         </div> | ||||||
|  |  | ||||||
|  |                         <!-- DNS challenge --> | ||||||
|  |                         <div class="col-sm-12 col-md-12 letsencrypt"> | ||||||
|  |                             <div class="form-group"> | ||||||
|  |                                 <label class="form-label"><%- i18n('all-hosts', 'domain-names') %> <span class="form-required">*</span></label> | ||||||
|  |                                 <input type="text" name="domain_names" class="form-control" id="input-domains" value="<%- domain_names.join(',') %>"> | ||||||
|  |                             </div> | ||||||
|  |                             <div class="form-group"> | ||||||
|  |                                 <label class="custom-switch"> | ||||||
|  |                                     <input | ||||||
|  |                                             type="checkbox" | ||||||
|  |                                             class="custom-switch-input" | ||||||
|  |                                             name="meta[dns_challenge]" | ||||||
|  |                                             value="1" | ||||||
|  |                                             checked | ||||||
|  |                                             disabled | ||||||
|  |                                     > | ||||||
|  |                                     <span class="custom-switch-indicator"></span> | ||||||
|  |                                     <span class="custom-switch-description"><%= i18n('ssl', 'dns-challenge') %></span> | ||||||
|  |                                 </label> | ||||||
|  |                             </div> | ||||||
|  |                         </div> | ||||||
|  |                         <div class="col-sm-12 col-md-12 letsencrypt"> | ||||||
|  |                             <fieldset class="form-fieldset dns-challenge"> | ||||||
|  |                                 <div class="text-red mb-4"><i class="fe fe-alert-triangle"></i> <%= i18n('ssl', 'certbot-warning') %></div> | ||||||
|  |  | ||||||
|  |                                 <!-- Certbot DNS plugin selection --> | ||||||
|  |                                 <div class="row"> | ||||||
|  |                                     <div class="col-sm-12 col-md-12"> | ||||||
|  |                                         <div class="form-group"> | ||||||
|  |                                             <label class="form-label"><%- i18n('ssl', 'dns-provider') %> <span class="form-required">*</span></label> | ||||||
|  |                                             <select | ||||||
|  |                                                     name="meta[dns_provider]" | ||||||
|  |                                                     id="dns_provider" | ||||||
|  |                                                     class="form-control custom-select" | ||||||
|  |                                             > | ||||||
|  |                                                 <option | ||||||
|  |                                                         value="" | ||||||
|  |                                                         disabled | ||||||
|  |                                                         hidden | ||||||
|  |                                                         <%- getDnsProvider() === null ? 'selected' : '' %> | ||||||
|  |                                                 >Please Choose...</option> | ||||||
|  |                                                 <% _.each(dns_plugins, function(plugin_info, plugin_name){ %> | ||||||
|  |                                                     <option | ||||||
|  |                                                             value="<%- plugin_name %>" | ||||||
|  |                                                             <%- getDnsProvider() === plugin_name ? 'selected' : '' %> | ||||||
|  |                                                     ><%- plugin_info.name %></option> | ||||||
|  |                                                 <% }); %> | ||||||
|  |                                             </select> | ||||||
|  |                                         </div> | ||||||
|  |                                     </div> | ||||||
|  |                                 </div> | ||||||
|  |  | ||||||
|  |                                 <!-- Certbot credentials file content --> | ||||||
|  |                                 <div class="row credentials-file-content"> | ||||||
|  |                                     <div class="col-sm-12 col-md-12"> | ||||||
|  |                                         <div class="form-group"> | ||||||
|  |                                             <label class="form-label"><%- i18n('ssl', 'credentials-file-content') %> <span class="form-required">*</span></label> | ||||||
|  |                                             <textarea | ||||||
|  |                                                     name="meta[dns_provider_credentials]" | ||||||
|  |                                                     class="form-control text-monospace" | ||||||
|  |                                                     id="dns_provider_credentials" | ||||||
|  |                                             ><%- getDnsProviderCredentials() %></textarea> | ||||||
|  |                                             <div class="text-secondary small"> | ||||||
|  |                                                 <i class="fe fe-info"></i> | ||||||
|  |                                                 <%= i18n('ssl', 'credentials-file-content-info') %> | ||||||
|  |                                             </div> | ||||||
|  |                                             <div class="text-red small"> | ||||||
|  |                                                 <i class="fe fe-alert-triangle"></i> | ||||||
|  |                                                 <%= i18n('ssl', 'stored-as-plaintext-info') %> | ||||||
|  |                                             </div> | ||||||
|  |                                         </div> | ||||||
|  |                                     </div> | ||||||
|  |                                 </div> | ||||||
|  |  | ||||||
|  |                                 <!-- DNS propagation delay --> | ||||||
|  |                                 <div class="row"> | ||||||
|  |                                     <div class="col-sm-12 col-md-12"> | ||||||
|  |                                         <div class="form-group mb-0"> | ||||||
|  |                                             <label class="form-label"><%- i18n('ssl', 'propagation-seconds') %></label> | ||||||
|  |                                             <input | ||||||
|  |                                                     type="number" | ||||||
|  |                                                     min="0" | ||||||
|  |                                                     name="meta[propagation_seconds]" | ||||||
|  |                                                     class="form-control" | ||||||
|  |                                                     id="propagation_seconds" | ||||||
|  |                                                     value="<%- getPropagationSeconds() %>" | ||||||
|  |                                             > | ||||||
|  |                                             <div class="text-secondary small"> | ||||||
|  |                                                 <i class="fe fe-info"></i> | ||||||
|  |                                                 <%= i18n('ssl', 'propagation-seconds-info') %> | ||||||
|  |                                             </div> | ||||||
|  |                                         </div> | ||||||
|  |                                     </div> | ||||||
|  |                                 </div> | ||||||
|  |                             </fieldset> | ||||||
|  |                         </div> | ||||||
|  |  | ||||||
|  |                         <!-- Lets encrypt --> | ||||||
|  |                         <div class="col-sm-12 col-md-12 letsencrypt"> | ||||||
|  |                             <div class="form-group"> | ||||||
|  |                                 <label class="form-label"><%- i18n('ssl', 'letsencrypt-email') %> <span class="form-required">*</span></label> | ||||||
|  |                                 <input name="meta[letsencrypt_email]" type="email" class="form-control" placeholder="" value="<%- getLetsencryptEmail() %>" required disabled> | ||||||
|  |                             </div> | ||||||
|  |                         </div> | ||||||
|  |                         <div class="col-sm-12 col-md-12 letsencrypt"> | ||||||
|  |                             <div class="form-group"> | ||||||
|  |                                 <label class="custom-switch"> | ||||||
|  |                                     <input type="checkbox" class="custom-switch-input" name="meta[letsencrypt_agree]" value="1" required disabled> | ||||||
|  |                                     <span class="custom-switch-indicator"></span> | ||||||
|  |                                     <span class="custom-switch-description"><%= i18n('ssl', 'letsencrypt-agree', {url: 'https://letsencrypt.org/repository/'}) %> <span class="form-required">*</span></span> | ||||||
|  |                                 </label> | ||||||
|  |                             </div> | ||||||
|  |                         </div> | ||||||
|  |                     </div> | ||||||
|  |                 </div> | ||||||
|  |             </div> | ||||||
|         </form> |         </form> | ||||||
|     </div> |     </div> | ||||||
|     <div class="modal-footer"> |     <div class="modal-footer"> | ||||||
|   | |||||||
| @@ -2,10 +2,14 @@ const Mn          = require('backbone.marionette'); | |||||||
| const App           = require('../../main'); | const App           = require('../../main'); | ||||||
| const StreamModel   = require('../../../models/stream'); | const StreamModel   = require('../../../models/stream'); | ||||||
| const template      = require('./form.ejs'); | const template      = require('./form.ejs'); | ||||||
|  | const dns_providers = require('../../../../../global/certbot-dns-plugins'); | ||||||
|  |  | ||||||
| require('jquery-serializejson'); | require('jquery-serializejson'); | ||||||
| require('jquery-mask-plugin'); | require('jquery-mask-plugin'); | ||||||
| require('selectize'); | require('selectize'); | ||||||
|  | const Helpers = require("../../../lib/helpers"); | ||||||
|  | const certListItemTemplate = require("../certificates-list-item.ejs"); | ||||||
|  | const i18n = require("../../i18n"); | ||||||
|  |  | ||||||
| module.exports = Mn.View.extend({ | module.exports = Mn.View.extend({ | ||||||
|     template:  template, |     template:  template, | ||||||
| @@ -18,7 +22,17 @@ module.exports = Mn.View.extend({ | |||||||
|         buttons:                  '.modal-footer button', |         buttons:                  '.modal-footer button', | ||||||
|         switches:                 '.custom-switch-input', |         switches:                 '.custom-switch-input', | ||||||
|         cancel:                   'button.cancel', |         cancel:                   'button.cancel', | ||||||
|         save:       'button.save' |         save:                     'button.save', | ||||||
|  |         le_error_info:            '#le-error-info', | ||||||
|  |         certificate_select:       'select[name="certificate_id"]', | ||||||
|  |         domain_names:             'input[name="domain_names"]', | ||||||
|  |         dns_challenge_switch:     'input[name="meta[dns_challenge]"]', | ||||||
|  |         dns_challenge_content:    '.dns-challenge', | ||||||
|  |         dns_provider:             'select[name="meta[dns_provider]"]', | ||||||
|  |         credentials_file_content: '.credentials-file-content', | ||||||
|  |         dns_provider_credentials: 'textarea[name="meta[dns_provider_credentials]"]', | ||||||
|  |         propagation_seconds:      'input[name="meta[propagation_seconds]"]', | ||||||
|  |         letsencrypt:              '.letsencrypt' | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     events: { |     events: { | ||||||
| @@ -48,6 +62,35 @@ module.exports = Mn.View.extend({ | |||||||
|             data.tcp_forwarding  = !!data.tcp_forwarding; |             data.tcp_forwarding  = !!data.tcp_forwarding; | ||||||
|             data.udp_forwarding  = !!data.udp_forwarding; |             data.udp_forwarding  = !!data.udp_forwarding; | ||||||
|  |  | ||||||
|  |             if (typeof data.meta === 'undefined') data.meta = {}; | ||||||
|  |             data.meta.letsencrypt_agree = data.meta.letsencrypt_agree == 1; | ||||||
|  |             data.meta.dns_challenge = true; | ||||||
|  |  | ||||||
|  |             if (data.meta.propagation_seconds === '') data.meta.propagation_seconds = undefined; | ||||||
|  |  | ||||||
|  |             if (typeof data.domain_names === 'string' && data.domain_names) { | ||||||
|  |                 data.domain_names = data.domain_names.split(','); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // Check for any domain names containing wildcards, which are not allowed with letsencrypt | ||||||
|  |             if (data.certificate_id === 'new') { | ||||||
|  |                 let domain_err = false; | ||||||
|  |                 if (!data.meta.dns_challenge) { | ||||||
|  |                     data.domain_names.map(function (name) { | ||||||
|  |                         if (name.match(/\*/im)) { | ||||||
|  |                             domain_err = true; | ||||||
|  |                         } | ||||||
|  |                     }); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 if (domain_err) { | ||||||
|  |                     alert(i18n('ssl', 'no-wildcard-without-dns')); | ||||||
|  |                     return; | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 data.certificate_id = parseInt(data.certificate_id, 10); | ||||||
|  |             } | ||||||
|  |  | ||||||
|             let method = App.Api.Nginx.Streams.create; |             let method = App.Api.Nginx.Streams.create; | ||||||
|             let is_new = true; |             let is_new = true; | ||||||
|  |  | ||||||
| @@ -70,10 +113,108 @@ module.exports = Mn.View.extend({ | |||||||
|                     }); |                     }); | ||||||
|                 }) |                 }) | ||||||
|                 .catch(err => { |                 .catch(err => { | ||||||
|                     alert(err.message); |                     let more_info = ''; | ||||||
|                     this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); |                     if (err.code === 500 && err.debug) { | ||||||
|                 }); |                         try { | ||||||
|  |                             more_info = JSON.parse(err.debug).debug.stack.join("\n"); | ||||||
|  |                         } catch (e) { | ||||||
|                         } |                         } | ||||||
|  |                     } | ||||||
|  |                     this.ui.le_error_info[0].innerHTML = `${err.message}${more_info !== '' ? `<pre class="mt-3">${more_info}</pre>` : ''}`; | ||||||
|  |                     this.ui.le_error_info.show(); | ||||||
|  |                     this.ui.le_error_info[0].scrollIntoView(); | ||||||
|  |                     this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); | ||||||
|  |                     this.ui.save.removeClass('btn-loading'); | ||||||
|  |                 }); | ||||||
|  |         }, | ||||||
|  |  | ||||||
|  |         'change @ui.certificate_select': function () { | ||||||
|  |             let id = this.ui.certificate_select.val(); | ||||||
|  |             if (id === 'new') { | ||||||
|  |                 this.ui.letsencrypt.show().find('input').prop('disabled', false); | ||||||
|  |                 this.ui.domain_names.prop('required', 'required'); | ||||||
|  |  | ||||||
|  |                 this.ui.dns_challenge_switch | ||||||
|  |                     .prop('disabled', true) | ||||||
|  |                     .parents('.form-group') | ||||||
|  |                     .css('opacity', 0.5); | ||||||
|  |  | ||||||
|  |                 this.ui.dns_provider.prop('required', 'required'); | ||||||
|  |                 const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value; | ||||||
|  |                 if (selected_provider != '' && dns_providers[selected_provider].credentials !== false) { | ||||||
|  |                     this.ui.dns_provider_credentials.prop('required', 'required'); | ||||||
|  |                 } | ||||||
|  |                 this.ui.dns_challenge_content.show(); | ||||||
|  |             } else { | ||||||
|  |                 this.ui.letsencrypt.hide().find('input').prop('disabled', true); | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |  | ||||||
|  |         'change @ui.dns_provider': function () { | ||||||
|  |             const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value; | ||||||
|  |             if (selected_provider != '' && dns_providers[selected_provider].credentials !== false) { | ||||||
|  |                 this.ui.dns_provider_credentials.prop('required', 'required'); | ||||||
|  |                 this.ui.dns_provider_credentials[0].value = dns_providers[selected_provider].credentials; | ||||||
|  |                 this.ui.credentials_file_content.show(); | ||||||
|  |             } else { | ||||||
|  |                 this.ui.dns_provider_credentials.prop('required', false); | ||||||
|  |                 this.ui.credentials_file_content.hide(); | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     templateContext: { | ||||||
|  |         getLetsencryptEmail: function () { | ||||||
|  |             return App.Cache.User.get('email'); | ||||||
|  |         }, | ||||||
|  |         getDnsProvider: function () { | ||||||
|  |             return typeof this.meta.dns_provider !== 'undefined' && this.meta.dns_provider != '' ? this.meta.dns_provider : null; | ||||||
|  |         }, | ||||||
|  |         getDnsProviderCredentials: function () { | ||||||
|  |             return typeof this.meta.dns_provider_credentials !== 'undefined' ? this.meta.dns_provider_credentials : ''; | ||||||
|  |         }, | ||||||
|  |         getPropagationSeconds: function () { | ||||||
|  |             return typeof this.meta.propagation_seconds !== 'undefined' ? this.meta.propagation_seconds : ''; | ||||||
|  |         }, | ||||||
|  |         dns_plugins: dns_providers, | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     onRender: function () { | ||||||
|  |         let view = this; | ||||||
|  |  | ||||||
|  |         // Certificates | ||||||
|  |         this.ui.le_error_info.hide(); | ||||||
|  |         this.ui.dns_challenge_content.hide(); | ||||||
|  |         this.ui.credentials_file_content.hide(); | ||||||
|  |         this.ui.letsencrypt.hide(); | ||||||
|  |         this.ui.certificate_select.selectize({ | ||||||
|  |             valueField:       'id', | ||||||
|  |             labelField:       'nice_name', | ||||||
|  |             searchField:      ['nice_name', 'domain_names'], | ||||||
|  |             create:           false, | ||||||
|  |             preload:          true, | ||||||
|  |             allowEmptyOption: true, | ||||||
|  |             render:           { | ||||||
|  |                 option: function (item) { | ||||||
|  |                     item.i18n         = App.i18n; | ||||||
|  |                     item.formatDbDate = Helpers.formatDbDate; | ||||||
|  |                     return certListItemTemplate(item); | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|  |             load:             function (query, callback) { | ||||||
|  |                 App.Api.Nginx.Certificates.getAll() | ||||||
|  |                     .then(rows => { | ||||||
|  |                         callback(rows); | ||||||
|  |                     }) | ||||||
|  |                     .catch(err => { | ||||||
|  |                         console.error(err); | ||||||
|  |                         callback(); | ||||||
|  |                     }); | ||||||
|  |             }, | ||||||
|  |             onLoad:           function () { | ||||||
|  |                 view.ui.certificate_select[0].selectize.setValue(view.model.get('certificate_id')); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     initialize: function (options) { |     initialize: function (options) { | ||||||
|   | |||||||
| @@ -16,7 +16,10 @@ | |||||||
| </td> | </td> | ||||||
| <td> | <td> | ||||||
|     <div> |     <div> | ||||||
|         <% if (tcp_forwarding) { %> |         <% if (certificate) { %> | ||||||
|  |             <span class="tag"><%- i18n('streams', 'tcp+ssl') %></span> | ||||||
|  |         <% } | ||||||
|  |         else if (tcp_forwarding) { %> | ||||||
|             <span class="tag"><%- i18n('streams', 'tcp') %></span> |             <span class="tag"><%- i18n('streams', 'tcp') %></span> | ||||||
|         <% } |         <% } | ||||||
|         if (udp_forwarding) { %> |         if (udp_forwarding) { %> | ||||||
| @@ -24,6 +27,9 @@ | |||||||
|         <% } %> |         <% } %> | ||||||
|     </div> |     </div> | ||||||
| </td> | </td> | ||||||
|  | <td> | ||||||
|  |     <div><%- certificate && certificate_id ? i18n('ssl', certificate.provider) : i18n('all-hosts', 'none') %></div> | ||||||
|  | </td> | ||||||
| <td> | <td> | ||||||
|     <% |     <% | ||||||
|     var o = isOnline(); |     var o = isOnline(); | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ | |||||||
|     <th><%- i18n('streams', 'incoming-port') %></th> |     <th><%- i18n('streams', 'incoming-port') %></th> | ||||||
|     <th><%- i18n('str', 'destination') %></th> |     <th><%- i18n('str', 'destination') %></th> | ||||||
|     <th><%- i18n('streams', 'protocol') %></th> |     <th><%- i18n('streams', 'protocol') %></th> | ||||||
|  |     <th><%- i18n('str', 'ssl') %></th> | ||||||
|     <th><%- i18n('str', 'status') %></th> |     <th><%- i18n('str', 'status') %></th> | ||||||
|     <% if (canManage) { %> |     <% if (canManage) { %> | ||||||
|     <th> </th> |     <th> </th> | ||||||
|   | |||||||
| @@ -88,7 +88,7 @@ module.exports = Mn.View.extend({ | |||||||
|     onRender: function () { |     onRender: function () { | ||||||
|         let view = this; |         let view = this; | ||||||
|  |  | ||||||
|         view.fetch(['owner']) |         view.fetch(['owner', 'certificate']) | ||||||
|             .then(response => { |             .then(response => { | ||||||
|                 if (!view.isDestroyed()) { |                 if (!view.isDestroyed()) { | ||||||
|                     if (response && response.length) { |                     if (response && response.length) { | ||||||
|   | |||||||
| @@ -179,7 +179,9 @@ | |||||||
|       "delete-confirm": "Are you sure you want to delete this Stream?", |       "delete-confirm": "Are you sure you want to delete this Stream?", | ||||||
|       "help-title": "What is a Stream?", |       "help-title": "What is a Stream?", | ||||||
|       "help-content": "A relatively new feature for Nginx, a Stream will serve to forward TCP/UDP traffic directly to another computer on the network.\nIf you're running game servers, FTP or SSH servers this can come in handy.", |       "help-content": "A relatively new feature for Nginx, a Stream will serve to forward TCP/UDP traffic directly to another computer on the network.\nIf you're running game servers, FTP or SSH servers this can come in handy.", | ||||||
|       "search": "Search Incoming Port…" |       "search": "Search Incoming Port…", | ||||||
|  |       "ssl-certificate": "SSL Certificate for TCP Forwarding", | ||||||
|  |       "tcp+ssl": "TCP+SSL" | ||||||
|     }, |     }, | ||||||
|     "certificates": { |     "certificates": { | ||||||
|       "title": "SSL Certificates", |       "title": "SSL Certificates", | ||||||
|   | |||||||
| @@ -15,8 +15,11 @@ const model = Backbone.Model.extend({ | |||||||
|             udp_forwarding:  false, |             udp_forwarding:  false, | ||||||
|             enabled:         true, |             enabled:         true, | ||||||
|             meta:            {}, |             meta:            {}, | ||||||
|  |             certificate_id:  0, | ||||||
|  |             domain_names:    [], | ||||||
|             // The following are expansions: |             // The following are expansions: | ||||||
|             owner:           null |             owner:           null, | ||||||
|  |             certificate:     null | ||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -1,11 +1,22 @@ | |||||||
| FROM cypress/included:13.9.0 | FROM cypress/included:14.0.1 | ||||||
|  |  | ||||||
| COPY --chown=1000 ./test /test |  | ||||||
|  |  | ||||||
| # Disable Cypress CLI colors | # Disable Cypress CLI colors | ||||||
| ENV FORCE_COLOR=0 | ENV FORCE_COLOR=0 | ||||||
| ENV NO_COLOR=1 | ENV NO_COLOR=1 | ||||||
|  |  | ||||||
|  | # testssl.sh and mkcert | ||||||
|  | RUN wget "https://github.com/testssl/testssl.sh/archive/refs/tags/v3.2rc4.tar.gz" -O /tmp/testssl.tgz -q \ | ||||||
|  | 	&& tar -xzf /tmp/testssl.tgz -C /tmp \ | ||||||
|  | 	&& mv /tmp/testssl.sh-3.2rc4 /testssl \ | ||||||
|  | 	&& rm /tmp/testssl.tgz \ | ||||||
|  | 	&& apt-get update \ | ||||||
|  | 	&& 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 \ | ||||||
|  | 	&& chmod +x /bin/mkcert | ||||||
|  |  | ||||||
|  | COPY --chown=1000 ./test /test | ||||||
| WORKDIR /test | WORKDIR /test | ||||||
| RUN yarn install && yarn cache clean | RUN yarn install && yarn cache clean | ||||||
| ENTRYPOINT [] | ENTRYPOINT [] | ||||||
|   | |||||||
							
								
								
									
										213
									
								
								test/cypress/e2e/api/Streams.cy.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										213
									
								
								test/cypress/e2e/api/Streams.cy.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,213 @@ | |||||||
|  | /// <reference types="cypress" /> | ||||||
|  |  | ||||||
|  | describe('Streams', () => { | ||||||
|  | 	let token; | ||||||
|  |  | ||||||
|  | 	before(() => { | ||||||
|  | 		cy.getToken().then((tok) => { | ||||||
|  | 			token = tok; | ||||||
|  | 			// Set default site content | ||||||
|  | 			cy.task('backendApiPut', { | ||||||
|  | 				token: token, | ||||||
|  | 				path:  '/api/settings/default-site', | ||||||
|  | 				data: { | ||||||
|  | 					value: 'html', | ||||||
|  | 					meta: { | ||||||
|  | 						html: '<p>yay it works</p>' | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}).then((data) => { | ||||||
|  | 				cy.validateSwaggerSchema('put', 200, '/settings/{settingID}', data); | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		// 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); | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		cy.exec('rm -f /test/results/testssl.json'); | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	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 -- http://website1.example.com:1500').then((result) => { | ||||||
|  | 				expect(result.code).to.eq(0); | ||||||
|  | 				expect(result.stdout).to.contain('yay it works'); | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	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); | ||||||
|  |  | ||||||
|  | 			cy.exec('curl --noproxy -- http://website1.example.com:1502').then((result) => { | ||||||
|  | 				expect(result.code).to.eq(0); | ||||||
|  | 				expect(result.stdout).to.contain('yay it works'); | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	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); | ||||||
|  |  | ||||||
|  | 					// Check the ssl termination | ||||||
|  | 					cy.task('log', '[testssl.sh] Running ...'); | ||||||
|  | 					cy.exec('/testssl/testssl.sh --quiet --add-ca="$(/bin/mkcert -CAROOT)/rootCA.pem" --jsonfile=/test/results/testssl.json website1.example.com:1503', { | ||||||
|  | 						timeout: 120000, // 2 minutes | ||||||
|  | 					}).then((result) => { | ||||||
|  | 						cy.task('log', '[testssl.sh] ' + result.stdout); | ||||||
|  |  | ||||||
|  | 						const allowedSeverities = ["INFO", "OK", "LOW", "MEDIUM"]; | ||||||
|  | 						const ignoredIDs = [ | ||||||
|  | 							'cert_chain_of_trust', | ||||||
|  | 							'cert_extlifeSpan', | ||||||
|  | 							'cert_revocation', | ||||||
|  | 							'overall_grade', | ||||||
|  | 						]; | ||||||
|  |  | ||||||
|  | 						cy.readFile('/test/results/testssl.json').then((data) => { | ||||||
|  | 							// Parse each array item | ||||||
|  | 							for (let i = 0; i < data.length; i++) { | ||||||
|  | 								const item = data[i]; | ||||||
|  | 								if (ignoredIDs.includes(item.id)) { | ||||||
|  | 									continue; | ||||||
|  | 								} | ||||||
|  | 								expect(item.severity).to.be.oneOf(allowedSeverities); | ||||||
|  | 							} | ||||||
|  | 						}); | ||||||
|  | 					}); | ||||||
|  | 				}); | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | }); | ||||||
| @@ -4,18 +4,18 @@ | |||||||
| 	"description": "", | 	"description": "", | ||||||
| 	"main": "index.js", | 	"main": "index.js", | ||||||
| 	"dependencies": { | 	"dependencies": { | ||||||
| 		"@jc21/cypress-swagger-validation": "^0.3.1", | 		"@jc21/cypress-swagger-validation": "^0.3.2", | ||||||
| 		"axios": "^1.7.7", | 		"axios": "^1.7.9", | ||||||
| 		"cypress": "^13.15.0", | 		"cypress": "^14.0.1", | ||||||
| 		"cypress-multi-reporters": "^1.6.4", | 		"cypress-multi-reporters": "^2.0.5", | ||||||
| 		"cypress-wait-until": "^3.0.2", | 		"cypress-wait-until": "^3.0.2", | ||||||
| 		"eslint": "^9.12.0", | 		"eslint": "^9.19.0", | ||||||
| 		"eslint-plugin-align-assignments": "^1.1.2", | 		"eslint-plugin-align-assignments": "^1.1.2", | ||||||
| 		"eslint-plugin-chai-friendly": "^1.0.1", | 		"eslint-plugin-chai-friendly": "^1.0.1", | ||||||
| 		"eslint-plugin-cypress": "^3.5.0", | 		"eslint-plugin-cypress": "^4.1.0", | ||||||
| 		"form-data": "^4.0.1", | 		"form-data": "^4.0.1", | ||||||
| 		"lodash": "^4.17.21", | 		"lodash": "^4.17.21", | ||||||
| 		"mocha": "^10.7.3", | 		"mocha": "^11.1.0", | ||||||
| 		"mocha-junit-reporter": "^2.2.1" | 		"mocha-junit-reporter": "^2.2.1" | ||||||
| 	}, | 	}, | ||||||
| 	"scripts": { | 	"scripts": { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user