diff --git a/common/index.ts b/common/index.ts index b33c099a7..f758c00eb 100644 --- a/common/index.ts +++ b/common/index.ts @@ -43,6 +43,8 @@ export const SAML_AUTH_LOGIN_WITH_FRAGMENT = '/auth/saml/captureUrlFragment'; export const ANONYMOUS_AUTH_LOGIN = '/auth/anonymous'; export const AUTH_TYPE_PARAM = 'auth_type'; +export const KERBEROS_AUTH_LOGIN = '/auth/kerberos/login'; + export const OPENID_AUTH_LOGOUT = '/auth/openid/logout'; export const SAML_AUTH_LOGOUT = '/auth/saml/logout'; export const ANONYMOUS_AUTH_LOGOUT = '/auth/anonymous/logout'; @@ -72,6 +74,7 @@ export enum AuthType { SAML = 'saml', PROXY = 'proxy', ANONYMOUS = 'anonymous', + KERBEROS = 'kerberos', } export enum ResourceType { diff --git a/kerberos_notes.md b/kerberos_notes.md new file mode 100644 index 000000000..fd70e5028 --- /dev/null +++ b/kerberos_notes.md @@ -0,0 +1,120 @@ +### Required environment for Kerberos authentication ### +1. Kerberos server +2. DNS server +3. Opensearch Core & Dashboards server +4. Client browser with SPNEGO ( I testing on google-chrome ) + +--- +### KEBEROS SERVER ### +Required +1. User principle to be authenticate +2. Service principle eg. HTTP/ ( When created with kadmin should get as HTTP/@) +3. Keytab for Service principle ( Make sure to give owner to opensearch core ) + +**NOTE** +1. I only test both core and dashboards on the same host. +2. Dashboards isn't the one authenticate with Kerberos but the core did so keytab doesn't need for dashboards. + +--- + +### DNS SERVER ### +Required +1. DNS Service if using **bind9** should have dns address to opensearch & kerberos like +``` +_kerberos._udp.. IN SRV 1 0 88 kdc.. +_kerberos._tcp.. IN SRV 1 0 88 kdc.. +_kerberos-adm._tcp.. IN SRV 1 0 749 kdc.. +_kpasswd._udp.. IN SRV 1 0 464 kdc.. + +kdc IN A +opensearch IN A +``` + +--- + +### Opensearch core ### +Required +1. set DNS point to your DNS server +2. Add config to opensearch.yml +``` +plugins.security.kerberos.krb5_filepath: '/etc/krb5.conf' # this is your kerberos config location +plugins.security.kerberos.acceptor_keytab_filepath: '' # keytab file for kerberos ( don't forget to give owner to opensearch ) +plugins.security.kerberos.acceptor_principal: 'HTTP/' # this is your service principle on your kerberos for opensearch +``` + +3. Configuration Opensearch-security config file ( don't forget to apply ) +``` + authc: + kerberos_auth_domain: + http_enabled: true # enable this + transport_enabled: false + order: 6 + http_authenticator: + type: kerberos + challenge: false + config: + krb_debug: true + strip_realm_from_principal: true + authentication_backend: + type: noop + + + jwt_auth_domain: + description: "Authenticate via Json Web Token" + http_enabled: true + transport_enabled: false + order: 0 + http_authenticator: + type: jwt + challenge: false + config: + signing_key: "" # edit this to your key (encoded by base64) + jwt_header: "Authorization" + jwt_url_parameter: null +# jwt_clock_skew_tolerance_seconds: 30 + roles_key: roles + subject_key: user + authentication_backend: + type: noop +``` + +--- + +### Opensearch Dashboards ### +Required +Edit config file for dashboards + +``` +# define auth type +opensearch_security.auth.type: kerberos + +# set your secret key +opensearch_security.kerberos.jwt_siging_key: '' #NOTE as plain text not encoded + +``` + +--- + +### Client Browser ### +Required ( For google chrome ) +1. Make sure to that browser have **SPNEGO** +1. Edit policy section for ```AuthServerAllowlist``` + +For google chrome debian package should locate at ```/etc/opt/chrome/policies/managed/``` +create your policy file eg. +``` +{ + "AuthServerAllowlist" : "" +} +``` + +**NOTE** +- When search on browser you must access hostname according to policies your defined. + +- Make sure that you already use **kinit** and see your tokens via **klist** + +- You can test with curl for checking kerberos by +```curl : -u ':' --negotiate -v``` +( You should see Negotiate token when request to server. If not it may problem with misconfiguration kerberos (usually principle) or DNS ) + +- **Don't forget** to add permission to user name same as kerberos principle of that user. diff --git a/package.json b/package.json index b1a3542cd..eed6b9ee2 100644 --- a/package.json +++ b/package.json @@ -43,10 +43,12 @@ "dependencies": { "@hapi/cryptiles": "5.0.0", "@hapi/wreck": "^17.1.0", + "@types/jsonwebtoken": "^9.0.7", "html-entities": "1.3.1", + "jsonwebtoken": "^9.0.2", "proxy-agent": "^6.4.0", - "zxcvbn": "^4.4.2", - "semver": "^7.5.3" + "semver": "^7.5.3", + "zxcvbn": "^4.4.2" }, "resolutions": { "selenium-webdriver": "4.10.0", @@ -58,4 +60,4 @@ "body-parser": "^1.20.3", "micromatch": "^4.0.8" } -} \ No newline at end of file +} diff --git a/server/auth/auth_handler_factory.ts b/server/auth/auth_handler_factory.ts index 2cd3c7c25..98dbf69c9 100644 --- a/server/auth/auth_handler_factory.ts +++ b/server/auth/auth_handler_factory.ts @@ -28,6 +28,7 @@ import { ProxyAuthentication, SamlAuthentication, MultipleAuthentication, + KerberosAuthentication, } from './types'; import { SecuritySessionCookie } from '../session/security_cookie'; import { IAuthenticationType, IAuthHandlerConstructor } from './types/authentication_type'; @@ -76,6 +77,9 @@ export async function getAuthenticationHandler( case AuthType.PROXY: authHandlerType = ProxyAuthentication; break; + case AuthType.KERBEROS: + authHandlerType = KerberosAuthentication; + break; default: throw new Error(`Unsupported authentication type: ${currType}`); } diff --git a/server/auth/types/index.ts b/server/auth/types/index.ts index 2635a1923..cbc857afb 100644 --- a/server/auth/types/index.ts +++ b/server/auth/types/index.ts @@ -19,3 +19,4 @@ export { OpenIdAuthentication } from './openid/openid_auth'; export { ProxyAuthentication } from './proxy/proxy_auth'; export { SamlAuthentication } from './saml/saml_auth'; export { MultipleAuthentication } from './multiple/multi_auth'; +export { KerberosAuthentication } from './kerberos/kerberos_auth'; diff --git a/server/auth/types/kerberos/kerberos_auth.ts b/server/auth/types/kerberos/kerberos_auth.ts new file mode 100644 index 000000000..fd2969e14 --- /dev/null +++ b/server/auth/types/kerberos/kerberos_auth.ts @@ -0,0 +1,117 @@ +/* + * Copyright OpenSearch Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import { + CoreSetup, + SessionStorageFactory, + IRouter, + ILegacyClusterClient, + OpenSearchDashboardsRequest, + Logger, + LifecycleResponseFactory, + AuthToolkit, +} from 'opensearch-dashboards/server'; +import { OpenSearchDashboardsResponse } from 'src/core/server/http/router'; +import { SecurityPluginConfigType } from '../../..'; +import { SecuritySessionCookie } from '../../../session/security_cookie'; +import { KerberosAuthRoutes } from './routes'; +import { AuthenticationType } from '../authentication_type'; +import { composeNextUrlQueryParam } from '../../../utils/next_url'; +import { KERBEROS_AUTH_LOGIN } from '../../../../common'; + +export class KerberosAuthentication extends AuthenticationType { + public readonly jwtCookie: string = 'jwt'; + public readonly kerberosHeader: string = 'Negotiate'; + + constructor( + config: SecurityPluginConfigType, + sessionStorageFactory: SessionStorageFactory, + router: IRouter, + esClient: ILegacyClusterClient, + coreSetup: CoreSetup, + logger: Logger + ) { + super(config, sessionStorageFactory, router, esClient, coreSetup, logger); + } + + public async init() { + const routes = new KerberosAuthRoutes( + this.router, + this.config, + this.sessionStorageFactory, + this.securityClient, + this.coreSetup + ); + routes.setupRoutes(); + } + + // Since kerberos authen always attached by SPENGO prevent authentication every request + requestIncludesAuthInfo( + request: OpenSearchDashboardsRequest + ): boolean { + return false; + } + + async getAdditionalAuthHeader( + request: OpenSearchDashboardsRequest + ): Promise { + return {}; + } + + getCookie(request: OpenSearchDashboardsRequest, authInfo: any): SecuritySessionCookie { + return {}; + } + + async isValidCookie(cookie: SecuritySessionCookie): Promise { + return ( + cookie.authType === this.jwtCookie && + cookie.expiryTime && + ((cookie.username && cookie.credentials?.authHeaderValue) || + (this.config.auth.anonymous_auth_enabled && cookie.isAnonymousAuth)) + ); + } + + handleUnauthedRequest( + request: OpenSearchDashboardsRequest, + response: LifecycleResponseFactory, + toolkit: AuthToolkit + ): OpenSearchDashboardsResponse { + if (this.isPageRequest(request)) { + const nextUrlParam = composeNextUrlQueryParam( + request, + this.coreSetup.http.basePath.serverBasePath + ); + const redirectLocation = `${this.coreSetup.http.basePath.serverBasePath}${KERBEROS_AUTH_LOGIN}?${nextUrlParam}`; + return response.redirected({ + headers: { + location: `${redirectLocation}`, + }, + }); + } else { + return response.unauthorized({ + body: `Authentication required`, + }); + } + } + + buildAuthHeaderFromCookie( + cookie: SecuritySessionCookie, + request: OpenSearchDashboardsRequest + ): any { + const headers: any = {}; + Object.assign(headers, { authorization: cookie.credentials?.authHeaderValue }); + return headers; + } +} diff --git a/server/auth/types/kerberos/routes.ts b/server/auth/types/kerberos/routes.ts new file mode 100755 index 000000000..ed516fe6c --- /dev/null +++ b/server/auth/types/kerberos/routes.ts @@ -0,0 +1,126 @@ +/* + * Copyright OpenSearch Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import { IRouter, SessionStorageFactory, CoreSetup } from 'opensearch-dashboards/server'; +import { sign } from 'jsonwebtoken'; +import { SecuritySessionCookie } from '../../../session/security_cookie'; +import { SecurityPluginConfigType } from '../../..'; +import { User } from '../../user'; +import { SecurityClient } from '../../../backend/opensearch_security_client'; +import { KERBEROS_AUTH_LOGIN, API_AUTH_LOGOUT } from '../../../../common'; +import { resolveTenant } from '../../../multitenancy/tenant_resolver'; +import { AuthType } from '../../../../common'; + +export class KerberosAuthRoutes { + constructor( + private readonly router: IRouter, + private readonly config: SecurityPluginConfigType, + private readonly sessionStorageFactory: SessionStorageFactory, + private readonly securityClient: SecurityClient, + private readonly coreSetup: CoreSetup + ) {} + + public setupRoutes() { + // login + this.router.get( + { + path: KERBEROS_AUTH_LOGIN, + validate: false, + options: { + authRequired: false, + }, + }, + async (context, request, response) => { + let user: User; + + try { + user = await this.securityClient.authinfo(request); + } catch (error) { + context.security_plugin.logger.error(`Failed authentication: ${error}`); + return response.unauthorized({ + body: `Kerberos authentication failed ${error}`, + headers: { + 'www-authenticate': 'Negotiate', + }, + }); + } + + // clear session + this.sessionStorageFactory.asScoped(request).clear(); + const payload = { + exp: Date.now() + this.config.session.ttl, + user: user.user_name, + roles: user.roles, + default_tenant: user.default_tenant, + }; + + const encodedCredentials = sign(payload, this.config.kerberos.jwt_siging_key); + + const sessionStorage: SecuritySessionCookie = { + username: user.user_name, + credentials: { + authHeaderValue: `Bearer ${encodedCredentials}`, + }, + authType: AuthType.JWT, + isAnonymousAuth: false, + expiryTime: Date.now() + this.config.session.ttl, + tenant: user.tenants, + }; + + if (user.multitenancy_enabled) { + const selectTenant = resolveTenant({ + request, + username: user.user_name, + roles: user.roles, + availableTenants: user.tenants, + config: this.config, + cookie: sessionStorage, + multitenancyEnabled: user.multitenancy_enabled, + privateTenantEnabled: user.private_tenant_enabled, + defaultTenant: user.default_tenant, + }); + // const selectTenant = user.default_tenant; + sessionStorage.tenant = selectTenant; + } + + this.sessionStorageFactory.asScoped(request).set(sessionStorage); + + // return to root + return response.redirected({ + headers: { + location: '/', + }, + }); + } + ); + + // logout + this.router.post( + { + path: API_AUTH_LOGOUT, + validate: false, + options: { + authRequired: false, + }, + }, + async (context, request, response) => { + this.sessionStorageFactory.asScoped(request).clear(); + return response.ok({ + body: {}, + }); + } + ); + } +} diff --git a/server/index.ts b/server/index.ts index 68a20f533..268eb784d 100644 --- a/server/index.ts +++ b/server/index.ts @@ -144,6 +144,12 @@ export const configSchema = schema.object({ buttonstyle: schema.string({ defaultValue: '' }), }), }), + kerberos: schema.object({ + jwt_siging_key: schema.string({ + defaultValue: 'secret share between opensearch and dashboards', + minLength: 32, + }), + }), multitenancy: schema.object({ enabled: schema.boolean({ defaultValue: false }), show_roles: schema.boolean({ defaultValue: false }), diff --git a/yarn.lock b/yarn.lock index 5cb4ec8c8..5909fb42e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -266,6 +266,13 @@ "@types/hapi__boom" "*" "@types/node" "*" +"@types/jsonwebtoken@^9.0.7": + version "9.0.7" + resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.7.tgz#e49b96c2b29356ed462e9708fc73b833014727d2" + integrity sha512-ugo316mmTYBl2g81zDFnZ7cfxlut3o+/EQdaP7J8QN2kY6lJ22hmQYCK5EHcJHbrW+dkCGSCPgbG8JtYj6qSrg== + dependencies: + "@types/node" "*" + "@types/node-forge@^1.3.0": version "1.3.11" resolved "https://registry.yarnpkg.com/@types/node-forge/-/node-forge-1.3.11.tgz#0972ea538ddb0f4d9c2fa0ec5db5724773a604da" @@ -891,6 +898,11 @@ buffer-crc32@~0.2.3: resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== +buffer-equal-constant-time@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" + integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA== + buffer-from@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" @@ -1500,6 +1512,13 @@ ecc-jsbn@~0.1.1: jsbn "~0.1.0" safer-buffer "^2.1.0" +ecdsa-sig-formatter@1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" + integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== + dependencies: + safe-buffer "^5.0.1" + ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" @@ -2785,6 +2804,22 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" +jsonwebtoken@^9.0.2: + version "9.0.2" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz#65ff91f4abef1784697d40952bb1998c504caaf3" + integrity sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ== + dependencies: + jws "^3.2.2" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" + ms "^2.1.1" + semver "^7.5.4" + jsprim@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-2.0.2.tgz#77ca23dbcd4135cd364800d22ff82c2185803d4d" @@ -2805,6 +2840,23 @@ jszip@^3.10.1: readable-stream "~2.3.6" setimmediate "^1.0.5" +jwa@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" + integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== + dependencies: + buffer-equal-constant-time "1.0.1" + ecdsa-sig-formatter "1.0.11" + safe-buffer "^5.0.1" + +jws@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" + integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== + dependencies: + jwa "^1.4.1" + safe-buffer "^5.0.1" + lazy-ass@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/lazy-ass/-/lazy-ass-1.6.0.tgz#7999655e8646c17f089fdd187d150d3324d54513" @@ -2860,6 +2912,16 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w== + +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg== + lodash.isempty@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.isempty/-/lodash.isempty-4.4.0.tgz#6f86cbedd8be4ec987be9aaf33c9684db1b31e7e" @@ -2870,17 +2932,32 @@ lodash.isfunction@^3.0.9: resolved "https://registry.yarnpkg.com/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz#06de25df4db327ac931981d1bdb067e5af68d051" integrity sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw== +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA== + +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw== + lodash.isobject@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/lodash.isobject/-/lodash.isobject-3.0.2.tgz#3c8fb8d5b5bf4bf90ae06e14f2a530a4ed935e1d" integrity sha512-3/Qptq2vr7WeJbB4KHUSKlq8Pl7ASXi3UG6CMbBm8WRtXi8+GHm7mKaU3urfpSEzWe2wCIChs6/sdocUsTKJiA== +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== + lodash.isstring@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== -lodash.once@^4.1.1: +lodash.once@^4.0.0, lodash.once@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== @@ -3159,7 +3236,7 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@2.1.3: +ms@2.1.3, ms@^2.1.1: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -3929,7 +4006,7 @@ semver@^5.6.0, semver@^5.7.2: resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== -semver@^7.5.3: +semver@^7.5.3, semver@^7.5.4: version "7.6.3" resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==