Pull request: 3503 password policy

Merge in DNS/adguard-home from 3503-password-policy to master

Closes #3503.

Squashed commit of the following:

commit 1f03cd9ef6e76a691ae383d6ba4b7f851eabddb8
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Feb 10 18:24:59 2022 +0300

    client: imp msg

commit e164ae2544284cf9a1d3333c50b68e361f78ce2a
Merge: b7efd764 f53f48cc
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Feb 10 16:52:01 2022 +0300

    Merge branch 'master' into 3503-password-policy

commit b7efd7640ec0fa3deac5290f8306ce5142428718
Author: Ildar Kamalov <ik@adguard.com>
Date:   Thu Feb 10 16:17:59 2022 +0300

    client: remove empty line

commit f19aba6cb579d2c4681675c881000c8f16257ab9
Author: Ildar Kamalov <ik@adguard.com>
Date:   Thu Feb 10 16:09:14 2022 +0300

    client: validate password length

commit a6943c94483306ecfc0d1431d576d42053823b61
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Feb 4 18:57:02 2022 +0300

    all: fix docs again

commit 9346bb6c393af0799a79b228285acdd8f8799b83
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Feb 4 18:54:15 2022 +0300

    openapi: fix docs

commit a8016443237c130f69108970ddfc77ef71126be6
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Feb 4 18:25:55 2022 +0300

    all: validate passwd runes count
This commit is contained in:
Eugene Burkov 2022-02-10 18:30:41 +03:00
parent f53f48cc33
commit 0ef8344178
10 changed files with 133 additions and 31 deletions

View File

@ -72,6 +72,7 @@ In this release, the schema version has changed from 12 to 13.
### Security ### Security
- Enforced password strength policy ([3503]).
- Weaker cipher suites that use the CBC (cipher block chaining) mode of - Weaker cipher suites that use the CBC (cipher block chaining) mode of
operation have been disabled ([#2993]). operation have been disabled ([#2993]).
@ -79,6 +80,7 @@ In this release, the schema version has changed from 12 to 13.
[#2993]: https://github.com/AdguardTeam/AdGuardHome/issues/2993 [#2993]: https://github.com/AdguardTeam/AdGuardHome/issues/2993
[#3057]: https://github.com/AdguardTeam/AdGuardHome/issues/3057 [#3057]: https://github.com/AdguardTeam/AdGuardHome/issues/3057
[#3367]: https://github.com/AdguardTeam/AdGuardHome/issues/3367 [#3367]: https://github.com/AdguardTeam/AdGuardHome/issues/3367
[#3503]: https://github.com/AdguardTeam/AdGuardHome/issues/3503
[#4216]: https://github.com/AdguardTeam/AdGuardHome/issues/4216 [#4216]: https://github.com/AdguardTeam/AdGuardHome/issues/4216
[#4221]: https://github.com/AdguardTeam/AdGuardHome/issues/4221 [#4221]: https://github.com/AdguardTeam/AdGuardHome/issues/4221
[#4238]: https://github.com/AdguardTeam/AdGuardHome/issues/4238 [#4238]: https://github.com/AdguardTeam/AdGuardHome/issues/4238

114
client/package-lock.json generated vendored
View File

@ -1700,6 +1700,12 @@
"v8-to-istanbul": "^4.1.3" "v8-to-istanbul": "^4.1.3"
}, },
"dependencies": { "dependencies": {
"ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true
},
"ansi-styles": { "ansi-styles": {
"version": "4.2.1", "version": "4.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
@ -1720,6 +1726,12 @@
"supports-color": "^7.1.0" "supports-color": "^7.1.0"
} }
}, },
"char-regex": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz",
"integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==",
"dev": true
},
"color-convert": { "color-convert": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@ -1747,6 +1759,25 @@
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true "dev": true
}, },
"string-length": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz",
"integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==",
"dev": true,
"requires": {
"char-regex": "^1.0.2",
"strip-ansi": "^6.0.0"
}
},
"strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"requires": {
"ansi-regex": "^5.0.1"
}
},
"supports-color": { "supports-color": {
"version": "7.1.0", "version": "7.1.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz",
@ -3959,10 +3990,9 @@
"integrity": "sha1-6LL+PX8at9aaMhma/5HqaTFAlRU=" "integrity": "sha1-6LL+PX8at9aaMhma/5HqaTFAlRU="
}, },
"char-regex": { "char-regex": {
"version": "1.0.2", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-2.0.0.tgz",
"integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", "integrity": "sha512-oGu2QekBMXgyQNWPDRQ001bjvDnZe4/zBTz37TMbiKz1NbNiyiH5hRkobe7npRN6GfbGbxMYFck/vQ1r9c1VMA=="
"dev": true
}, },
"character-entities": { "character-entities": {
"version": "1.2.4", "version": "1.2.4",
@ -10037,6 +10067,12 @@
"string-length": "^4.0.1" "string-length": "^4.0.1"
}, },
"dependencies": { "dependencies": {
"ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true
},
"ansi-styles": { "ansi-styles": {
"version": "4.2.1", "version": "4.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
@ -10057,6 +10093,12 @@
"supports-color": "^7.1.0" "supports-color": "^7.1.0"
} }
}, },
"char-regex": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz",
"integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==",
"dev": true
},
"color-convert": { "color-convert": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@ -10078,6 +10120,25 @@
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true "dev": true
}, },
"string-length": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz",
"integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==",
"dev": true,
"requires": {
"char-regex": "^1.0.2",
"strip-ansi": "^6.0.0"
}
},
"strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"requires": {
"ansi-regex": "^5.0.1"
}
},
"supports-color": { "supports-color": {
"version": "7.1.0", "version": "7.1.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz",
@ -14193,38 +14254,26 @@
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
"integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY=" "integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY="
}, },
"string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true,
"requires": {
"safe-buffer": "~5.1.0"
}
},
"string-length": { "string-length": {
"version": "4.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.1.tgz", "resolved": "https://registry.npmjs.org/string-length/-/string-length-5.0.1.tgz",
"integrity": "sha512-PKyXUd0LK0ePjSOnWn34V2uD6acUWev9uy0Ft05k0E8xRW+SKcA0F7eMr7h5xlzfn+4O3N+55rduYyet3Jk+jw==", "integrity": "sha512-9Ep08KAMUn0OadnVaBuRdE2l615CQ508kr0XMadjClfYpdCyvrbFp6Taebo8yyxokQ4viUd/xPPUA4FGgUa0ow==",
"dev": true,
"requires": { "requires": {
"char-regex": "^1.0.2", "char-regex": "^2.0.0",
"strip-ansi": "^6.0.0" "strip-ansi": "^7.0.1"
}, },
"dependencies": { "dependencies": {
"ansi-regex": { "ansi-regex": {
"version": "5.0.0", "version": "6.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA=="
"dev": true
}, },
"strip-ansi": { "strip-ansi": {
"version": "6.0.0", "version": "7.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz",
"integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==",
"dev": true,
"requires": { "requires": {
"ansi-regex": "^5.0.0" "ansi-regex": "^6.0.1"
} }
} }
} }
@ -14297,6 +14346,15 @@
"define-properties": "^1.1.3" "define-properties": "^1.1.3"
} }
}, },
"string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true,
"requires": {
"safe-buffer": "~5.1.0"
}
},
"stringify-entities": { "stringify-entities": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-3.0.1.tgz", "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-3.0.1.tgz",

1
client/package.json vendored
View File

@ -42,6 +42,7 @@
"redux-actions": "^2.6.5", "redux-actions": "^2.6.5",
"redux-form": "^8.3.5", "redux-form": "^8.3.5",
"redux-thunk": "^2.3.0", "redux-thunk": "^2.3.0",
"string-length": "^5.0.1",
"url-polyfill": "^1.1.9" "url-polyfill": "^1.1.9"
}, },
"devDependencies": { "devDependencies": {

View File

@ -627,5 +627,6 @@
"use_saved_key": "Use the previously saved key", "use_saved_key": "Use the previously saved key",
"parental_control": "Parental Control", "parental_control": "Parental Control",
"safe_browsing": "Safe Browsing", "safe_browsing": "Safe Browsing",
"served_from_cache": "{{value}} <i>(served from cache)</i>" "served_from_cache": "{{value}} <i>(served from cache)</i>",
"form_error_password_length": "Password must be at least {{value}} characters long"
} }

View File

@ -26,6 +26,8 @@ export const R_WIN_ABSOLUTE_PATH = /^([a-zA-Z]:)?(\\|\/)(?:[^\\/:*?"<>|\x00]+\\)
export const R_CLIENT_ID = /^[a-z0-9-]{1,63}$/; export const R_CLIENT_ID = /^[a-z0-9-]{1,63}$/;
export const MIN_PASSWORD_LENGTH = 8;
export const HTML_PAGES = { export const HTML_PAGES = {
INSTALL: '/install.html', INSTALL: '/install.html',
LOGIN: '/login.html', LOGIN: '/login.html',

View File

@ -1,4 +1,5 @@
import i18next from 'i18next'; import i18next from 'i18next';
import stringLength from 'string-length';
import { import {
MAX_PORT, MAX_PORT,
@ -13,6 +14,7 @@ import {
UNSAFE_PORTS, UNSAFE_PORTS,
R_CLIENT_ID, R_CLIENT_ID,
R_DOMAIN, R_DOMAIN,
MIN_PASSWORD_LENGTH,
} from './constants'; } from './constants';
import { ip4ToInt, isValidAbsolutePath } from './form'; import { ip4ToInt, isValidAbsolutePath } from './form';
import { isIpInCidr, parseSubnetMask } from './helpers'; import { isIpInCidr, parseSubnetMask } from './helpers';
@ -320,10 +322,20 @@ export const validatePath = (value) => {
* @param cidr {string} * @param cidr {string}
* @returns {Function} * @returns {Function}
*/ */
export const validateIpv4InCidr = (valueIp, allValues) => { export const validateIpv4InCidr = (valueIp, allValues) => {
if (!isIpInCidr(valueIp, allValues.cidr)) { if (!isIpInCidr(valueIp, allValues.cidr)) {
return i18next.t('form_error_subnet', { ip: valueIp, cidr: allValues.cidr }); return i18next.t('form_error_subnet', { ip: valueIp, cidr: allValues.cidr });
} }
return undefined; return undefined;
}; };
/**
* @param value {string}
* @returns {Function}
*/
export const validatePasswordLength = (value) => {
if (value && stringLength(value) < MIN_PASSWORD_LENGTH) {
return i18next.t('form_error_password_length', { value: MIN_PASSWORD_LENGTH });
}
return undefined;
};

View File

@ -8,6 +8,7 @@ import i18n from '../../i18n';
import Controls from './Controls'; import Controls from './Controls';
import { renderInputField } from '../../helpers/form'; import { renderInputField } from '../../helpers/form';
import { FORM_NAME } from '../../helpers/constants'; import { FORM_NAME } from '../../helpers/constants';
import { validatePasswordLength } from '../../helpers/validators';
const required = (value) => { const required = (value) => {
if (value || value === 0) { if (value || value === 0) {
@ -67,7 +68,7 @@ const Auth = (props) => {
type="password" type="password"
className="form-control" className="form-control"
placeholder={ t('install_auth_password_enter') } placeholder={ t('install_auth_password_enter') }
validate={[required]} validate={[required, validatePasswordLength]}
autoComplete="new-password" autoComplete="new-password"
/> />
</div> </div>

View File

@ -13,6 +13,7 @@ import (
"runtime" "runtime"
"strings" "strings"
"time" "time"
"unicode/utf8"
"github.com/AdguardTeam/AdGuardHome/internal/aghalg" "github.com/AdguardTeam/AdGuardHome/internal/aghalg"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp" "github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
@ -359,6 +360,9 @@ func shutdownSrv(ctx context.Context, srv *http.Server) {
} }
} }
// PasswordMinRunes is the minimum length of user's password in runes.
const PasswordMinRunes = 8
// Apply new configuration, start DNS server, restart Web server // Apply new configuration, start DNS server, restart Web server
func (web *Web) handleInstallConfigure(w http.ResponseWriter, r *http.Request) { func (web *Web) handleInstallConfigure(w http.ResponseWriter, r *http.Request) {
req, restartHTTP, err := decodeApplyConfigReq(r.Body) req, restartHTTP, err := decodeApplyConfigReq(r.Body)
@ -368,6 +372,18 @@ func (web *Web) handleInstallConfigure(w http.ResponseWriter, r *http.Request) {
return return
} }
if utf8.RuneCountInString(req.Password) < PasswordMinRunes {
aghhttp.Error(
r,
w,
http.StatusUnprocessableEntity,
"password must be at least %d symbols long",
PasswordMinRunes,
)
return
}
err = aghnet.CheckPort("udp", req.DNS.IP, req.DNS.Port) err = aghnet.CheckPort("udp", req.DNS.IP, req.DNS.Port)
if err != nil { if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err) aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)

View File

@ -4,6 +4,12 @@
## v0.107.3: API changes ## v0.107.3: API changes
### The new possible status code in `/install/configure` response.
* The new status code `422 Unprocessable Entity` in the response for
`POST /install/configure` which means that the specified password does not
meet the strength requirements.
### The new field `"version"` in `AddressesInfo` ### The new field `"version"` in `AddressesInfo`
* The new field `"version"` in `GET /install/get_addresses` is the version of * The new field `"version"` in `GET /install/get_addresses` is the version of

View File

@ -1088,6 +1088,9 @@
'description': > 'description': >
Failed to parse initial configuration or cannot listen to the Failed to parse initial configuration or cannot listen to the
specified addresses. specified addresses.
'422':
'description': >
The specified password does not meet the strength requirements.
'500': '500':
'description': 'Cannot start the DNS server' 'description': 'Cannot start the DNS server'
'/login': '/login':