From 81e58453a3e09d5c9bee940091dbc47c9b844744 Mon Sep 17 00:00:00 2001 From: Mirko Van Colen Date: Thu, 19 Sep 2024 12:30:11 +0200 Subject: [PATCH 1/5] ip calculations... removed CVE related code --- server/src/lib/ip.js | 397 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 397 insertions(+) create mode 100644 server/src/lib/ip.js diff --git a/server/src/lib/ip.js b/server/src/lib/ip.js new file mode 100644 index 00000000..efe48d35 --- /dev/null +++ b/server/src/lib/ip.js @@ -0,0 +1,397 @@ +const ip = exports; +const { Buffer } = require('buffer'); + +ip.toBuffer = function (ip, buff, offset) { + offset = ~~offset; + + let result; + + if (this.isV4Format(ip)) { + result = buff || Buffer.alloc(offset + 4); + ip.split(/\./g).map((byte) => { + result[offset++] = parseInt(byte, 10) & 0xff; + }); + } else if (this.isV6Format(ip)) { + const sections = ip.split(':', 8); + + let i; + for (i = 0; i < sections.length; i++) { + const isv4 = this.isV4Format(sections[i]); + let v4Buffer; + + if (isv4) { + v4Buffer = this.toBuffer(sections[i]); + sections[i] = v4Buffer.slice(0, 2).toString('hex'); + } + + if (v4Buffer && ++i < 8) { + sections.splice(i, 0, v4Buffer.slice(2, 4).toString('hex')); + } + } + + if (sections[0] === '') { + while (sections.length < 8) sections.unshift('0'); + } else if (sections[sections.length - 1] === '') { + while (sections.length < 8) sections.push('0'); + } else if (sections.length < 8) { + for (i = 0; i < sections.length && sections[i] !== ''; i++); + const argv = [i, 1]; + for (i = 9 - sections.length; i > 0; i--) { + argv.push('0'); + } + sections.splice(...argv); + } + + result = buff || Buffer.alloc(offset + 16); + for (i = 0; i < sections.length; i++) { + const word = parseInt(sections[i], 16); + result[offset++] = (word >> 8) & 0xff; + result[offset++] = word & 0xff; + } + } + + if (!result) { + throw Error(`Invalid ip address: ${ip}`); + } + + return result; +}; + +ip.toString = function (buff, offset, length) { + offset = ~~offset; + length = length || (buff.length - offset); + + let result = []; + if (length === 4) { + // IPv4 + for (let i = 0; i < length; i++) { + result.push(buff[offset + i]); + } + result = result.join('.'); + } else if (length === 16) { + // IPv6 + for (let i = 0; i < length; i += 2) { + result.push(buff.readUInt16BE(offset + i).toString(16)); + } + result = result.join(':'); + result = result.replace(/(^|:)0(:0)*:0(:|$)/, '$1::$3'); + result = result.replace(/:{3,4}/, '::'); + } + + return result; +}; + +const ipv4Regex = /^(\d{1,3}\.){3,3}\d{1,3}$/; +const ipv6Regex = /^(::)?(((\d{1,3}\.){3}(\d{1,3}){1})?([0-9a-f]){0,4}:{0,2}){1,8}(::)?$/i; + +ip.isV4Format = function (ip) { + return ipv4Regex.test(ip); +}; + +ip.isV6Format = function (ip) { + return ipv6Regex.test(ip); +}; + +function _normalizeFamily(family) { + if (family === 4) { + return 'ipv4'; + } + if (family === 6) { + return 'ipv6'; + } + return family ? family.toLowerCase() : 'ipv4'; +} + +ip.fromPrefixLen = function (prefixlen, family) { + if (prefixlen > 32) { + family = 'ipv6'; + } else { + family = _normalizeFamily(family); + } + + let len = 4; + if (family === 'ipv6') { + len = 16; + } + const buff = Buffer.alloc(len); + + for (let i = 0, n = buff.length; i < n; ++i) { + let bits = 8; + if (prefixlen < 8) { + bits = prefixlen; + } + prefixlen -= bits; + + buff[i] = ~(0xff >> bits) & 0xff; + } + + return ip.toString(buff); +}; + +ip.mask = function (addr, mask) { + addr = ip.toBuffer(addr); + mask = ip.toBuffer(mask); + + const result = Buffer.alloc(Math.max(addr.length, mask.length)); + + // Same protocol - do bitwise and + let i; + if (addr.length === mask.length) { + for (i = 0; i < addr.length; i++) { + result[i] = addr[i] & mask[i]; + } + } else if (mask.length === 4) { + // IPv6 address and IPv4 mask + // (Mask low bits) + for (i = 0; i < mask.length; i++) { + result[i] = addr[addr.length - 4 + i] & mask[i]; + } + } else { + // IPv6 mask and IPv4 addr + for (i = 0; i < result.length - 6; i++) { + result[i] = 0; + } + + // ::ffff:ipv4 + result[10] = 0xff; + result[11] = 0xff; + for (i = 0; i < addr.length; i++) { + result[i + 12] = addr[i] & mask[i + 12]; + } + i += 12; + } + for (; i < result.length; i++) { + result[i] = 0; + } + + return ip.toString(result); +}; + +ip.cidr = function (cidrString) { + const cidrParts = cidrString.split('/'); + + const addr = cidrParts[0]; + if (cidrParts.length !== 2) { + throw new Error(`invalid CIDR subnet: ${addr}`); + } + + const mask = ip.fromPrefixLen(parseInt(cidrParts[1], 10)); + + return ip.mask(addr, mask); +}; + +ip.subnet = function (addr, mask) { + const networkAddress = ip.toLong(ip.mask(addr, mask)); + + // Calculate the mask's length. + const maskBuffer = ip.toBuffer(mask); + let maskLength = 0; + + for (let i = 0; i < maskBuffer.length; i++) { + if (maskBuffer[i] === 0xff) { + maskLength += 8; + } else { + let octet = maskBuffer[i] & 0xff; + while (octet) { + octet = (octet << 1) & 0xff; + maskLength++; + } + } + } + + const numberOfAddresses = 2 ** (32 - maskLength); + + return { + networkAddress: ip.fromLong(networkAddress), + firstAddress: numberOfAddresses <= 2 + ? ip.fromLong(networkAddress) + : ip.fromLong(networkAddress + 1), + lastAddress: numberOfAddresses <= 2 + ? ip.fromLong(networkAddress + numberOfAddresses - 1) + : ip.fromLong(networkAddress + numberOfAddresses - 2), + broadcastAddress: ip.fromLong(networkAddress + numberOfAddresses - 1), + subnetMask: mask, + subnetMaskLength: maskLength, + numHosts: numberOfAddresses <= 2 + ? numberOfAddresses : numberOfAddresses - 2, + length: numberOfAddresses, + contains(other) { + return networkAddress === ip.toLong(ip.mask(other, mask)); + }, + }; +}; + +ip.cidrSubnet = function (cidrString) { + const cidrParts = cidrString.split('/'); + + const addr = cidrParts[0]; + if (cidrParts.length !== 2) { + throw new Error(`invalid CIDR subnet: ${addr}`); + } + + const mask = ip.fromPrefixLen(parseInt(cidrParts[1], 10)); + + return ip.subnet(addr, mask); +}; + +ip.not = function (addr) { + const buff = ip.toBuffer(addr); + for (let i = 0; i < buff.length; i++) { + buff[i] = 0xff ^ buff[i]; + } + return ip.toString(buff); +}; + +ip.or = function (a, b) { + a = ip.toBuffer(a); + b = ip.toBuffer(b); + + // same protocol + if (a.length === b.length) { + for (let i = 0; i < a.length; ++i) { + a[i] |= b[i]; + } + return ip.toString(a); + + // mixed protocols + } + let buff = a; + let other = b; + if (b.length > a.length) { + buff = b; + other = a; + } + + const offset = buff.length - other.length; + for (let i = offset; i < buff.length; ++i) { + buff[i] |= other[i - offset]; + } + + return ip.toString(buff); +}; + +ip.isEqual = function (a, b) { + a = ip.toBuffer(a); + b = ip.toBuffer(b); + + // Same protocol + if (a.length === b.length) { + for (let i = 0; i < a.length; i++) { + if (a[i] !== b[i]) return false; + } + return true; + } + + // Swap + if (b.length === 4) { + const t = b; + b = a; + a = t; + } + + // a - IPv4, b - IPv6 + for (let i = 0; i < 10; i++) { + if (b[i] !== 0) return false; + } + + const word = b.readUInt16BE(10); + if (word !== 0 && word !== 0xffff) return false; + + for (let i = 0; i < 4; i++) { + if (a[i] !== b[i + 12]) return false; + } + + return true; +}; + +ip.isLoopback = function (addr) { + // If addr is an IPv4 address in long integer form (no dots and no colons), convert it + if (!/\./.test(addr) && !/:/.test(addr)) { + addr = ip.fromLong(Number(addr)); + } + + return /^(::f{4}:)?127\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})/ + .test(addr) + || /^0177\./.test(addr) + || /^0x7f\./i.test(addr) + || /^fe80::1$/i.test(addr) + || /^::1$/.test(addr) + || /^::$/.test(addr); +}; + +ip.loopback = function (family) { + // + // Default to `ipv4` + // + family = _normalizeFamily(family); + + if (family !== 'ipv4' && family !== 'ipv6') { + throw new Error('family must be ipv4 or ipv6'); + } + + return family === 'ipv4' ? '127.0.0.1' : 'fe80::1'; +}; + +ip.toLong = function (ip) { + let ipl = 0; + ip.split('.').forEach((octet) => { + ipl <<= 8; + ipl += parseInt(octet); + }); + return (ipl >>> 0); +}; + +ip.fromLong = function (ipl) { + return (`${ipl >>> 24}.${ + ipl >> 16 & 255}.${ + ipl >> 8 & 255}.${ + ipl & 255}`); +}; + +ip.normalizeToLong = function (addr) { + const parts = addr.split('.').map(part => { + // Handle hexadecimal format + if (part.startsWith('0x') || part.startsWith('0X')) { + return parseInt(part, 16); + } + // Handle octal format (strictly digits 0-7 after a leading zero) + else if (part.startsWith('0') && part !== '0' && /^[0-7]+$/.test(part)) { + return parseInt(part, 8); + } + // Handle decimal format, reject invalid leading zeros + else if (/^[1-9]\d*$/.test(part) || part === '0') { + return parseInt(part, 10); + } + // Return NaN for invalid formats to indicate parsing failure + else { + return NaN; + } + }); + + if (parts.some(isNaN)) return -1; // Indicate error with -1 + + let val = 0; + const n = parts.length; + + switch (n) { + case 1: + val = parts[0]; + break; + case 2: + if (parts[0] > 0xff || parts[1] > 0xffffff) return -1; + val = (parts[0] << 24) | (parts[1] & 0xffffff); + break; + case 3: + if (parts[0] > 0xff || parts[1] > 0xff || parts[2] > 0xffff) return -1; + val = (parts[0] << 24) | (parts[1] << 16) | (parts[2] & 0xffff); + break; + case 4: + if (parts.some(part => part > 0xff)) return -1; + val = (parts[0] << 24) | (parts[1] << 16) | (parts[2] << 8) | parts[3]; + break; + default: + return -1; // Error case + } + + return val >>> 0; +}; \ No newline at end of file From e4388a5004e40305fc85152e2283ff54273d5d26 Mon Sep 17 00:00:00 2001 From: Mirko Van Colen Date: Thu, 19 Sep 2024 12:31:19 +0200 Subject: [PATCH 2/5] internal ip.js --- server/src/functions/default.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/functions/default.js b/server/src/functions/default.js index 33329005..a727365b 100644 --- a/server/src/functions/default.js +++ b/server/src/functions/default.js @@ -12,7 +12,7 @@ const {inspect} = require('node:util') const JSONbig = require('json-bigint'); const _ = require("lodash") const {firstBy,thenBy}=require("thenby") -const ip=require("ip") +const ip=require("../lib/ip") const dayjs=require("dayjs") const credentialModel = require("../models/credential.model") // import definitions as strings From 4af1aadb64c15281c9885b25eb724553b27ce119 Mon Sep 17 00:00:00 2001 From: Mirko Van Colen Date: Thu, 19 Sep 2024 12:31:43 +0200 Subject: [PATCH 3/5] bumping versions --- CHANGELOG.md | 10 ++++++++++ client/package.json | 2 +- server/package.json | 3 +-- server/src/swagger.json | 2 +- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index afd6e93b..63532d5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- Removed ip lib parts CVE related + ## [5.0.5] - 2024-09-18 +### Fixed + +- Fixed regex for ssh key +- New version highlight for CVE +- Bumped several versions + ## [5.0.4] - 2024-08-18 ### Added diff --git a/client/package.json b/client/package.json index 2628c715..7ff9245e 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "ansible_forms_vue", - "version": "5.0.5", + "version": "5.0.6", "private": true, "scripts": { "serve": "vue-cli-service serve", diff --git a/server/package.json b/server/package.json index 1890a647..044fbd9f 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "ansible_forms", - "version": "5.0.5", + "version": "5.0.6", "repository": { "type": "git", "url": "git://github.com/ansibleguy76/ansibleforms.git" @@ -33,7 +33,6 @@ "express": "~4.21.0", "cookie-session": "~2.1.0", "fs-extra": "~11.2.0", - "ip": "2.0.1", "json-bigint": "~1.0.0", "ldap-authentication": "~3.2.4", "ldapjs": "~3.0.7", diff --git a/server/src/swagger.json b/server/src/swagger.json index 74d0cc27..07883726 100644 --- a/server/src/swagger.json +++ b/server/src/swagger.json @@ -2,7 +2,7 @@ "swagger": "2.0", "info": { "description": "This is the swagger interface for AnsibleForms.\r\nUse the `/auth/login` api with basic authentication to obtain a JWT token.\r\nThen use the access token, prefixed with the word '**Bearer**' to use all other api's.\r\nNote that the access token is limited in time. You can then either login again and get a new set of tokens or use the `/token` api and the refresh token to obtain a new set (preferred).", - "version": "5.0.5", + "version": "5.0.6", "title": "AnsibleForms", "contact": { "email": "info@ansibleforms.com" From f34775b3b30f855178dc402ad335df0ac5911765 Mon Sep 17 00:00:00 2001 From: Mirko Van Colen Date: Fri, 20 Sep 2024 16:35:07 +0200 Subject: [PATCH 4/5] update to vuelidate 2 ; imported ip and trimmed --- CHANGELOG.md | 1 + client/package.json | 3 +- client/src/components/BulmaEditTable.vue | 68 +++++++++----- client/src/components/Form.vue | 109 ++++++++++++----------- client/src/views/Awx.vue | 23 ++--- client/src/views/AzureAd.vue | 17 ++-- client/src/views/Credentials.vue | 64 +++++++------ client/src/views/Groups.vue | 12 +-- client/src/views/KnownHosts.vue | 14 +-- client/src/views/Ldap.vue | 32 +++---- client/src/views/Login.vue | 17 ++-- client/src/views/MailSettings.vue | 22 ++--- client/src/views/OIDC.vue | 18 ++-- client/src/views/Profile.vue | 42 ++++----- client/src/views/Repos.vue | 22 ++--- client/src/views/Settings.vue | 14 +-- client/src/views/Sshkey.vue | 28 +++--- client/src/views/Users.vue | 72 +++++++-------- 18 files changed, 315 insertions(+), 263 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 63532d5c..3175b953 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Removed ip lib parts CVE related +- Updated to vuelidate 2+ (CVE related) ## [5.0.5] - 2024-09-18 diff --git a/client/package.json b/client/package.json index 7ff9245e..371a7cc7 100644 --- a/client/package.json +++ b/client/package.json @@ -43,7 +43,8 @@ "vue-showdown": "2.4.1", "vue-toastification": "1.7.14", "vue2-datepicker": "~3.11.1", - "vuelidate": "~0.7.7", + "@vuelidate/core": "^2.0.3", + "@vuelidate/validators": "^2.0.4", "yaml": "1.10.2" }, "devDependencies": { diff --git a/client/src/components/BulmaEditTable.vue b/client/src/components/BulmaEditTable.vue index cb3d79b3..59849930 100644 --- a/client/src/components/BulmaEditTable.vue +++ b/client/src/components/BulmaEditTable.vue @@ -6,7 +6,7 @@ - + @@ -67,14 +67,14 @@ -

This field is required

-

Must be at least {{$v.editedItem[field.name].$params.minLength.min}} characters long

-

Can not be more than {{$v.editedItem[field.name].$params.maxLength.max}} characters long

-

Value cannot be lower than {{$v.editedItem[field.name].$params.minValue.min}}

-

Value cannot be higher than {{$v.editedItem[field.name].$params.maxValue.max}}

-

{{$v.editedItem[field.name].$params.regex.description}}

-

{{$v.editedItem[field.name].$params.notIn.description}}

-

{{$v.editedItem[field.name].$params.in.description}}

+

This field is required

+

Must be at least {{v$.editedItem[field.name].minLength.$params.min}} characters long

+

Can not be more than {{v$.editedItem[field.name].maxLength.$params.max}} characters long

+

Value cannot be lower than {{v$.editedItem[field.name].minValue.$params.min}}

+

Value cannot be higher than {{v$.editedItem[field.name].maxValue.$params.max}}

+

{{v$.editedItem[field.name].regex.$params.description}}

+

{{v$.editedItem[field.name].notIn.$params.description}}

+

{{v$.editedItem[field.name].in.$params.description}}

@@ -168,15 +168,14 @@