From ff30598b6737f473e36eab3635b64e6eba2e760d Mon Sep 17 00:00:00 2001 From: Ioakeim Samaras Date: Wed, 25 Sep 2019 15:35:35 +0300 Subject: [PATCH] [UI] Encrypt passwords in db The passwords stored in database are encrypted. In this way, this data cannot be used by individuals that shouldn't have access to it. JIRA: VAL-55 Signed-off-by: Ioakeim Samaras Change-Id: I3eb3b6a23398d37faa59938f84368711e7666978 --- docker/README.rst | 6 +- docker/mariadb/deploy.sh | 12 ++- docker/ui/deploy.sh | 10 +- ui/CHANGELOG.md | 8 ++ ui/README.rst | 10 +- ui/db-scripts/EcompSdkDDLMySql_2_4_Common.sql | 4 +- ui/pom.xml | 2 +- .../validation/ui/login/LoginStrategyImpl.java | 118 ++++++++++++++------- .../akraino/validation/ui/service/DbAdapter.java | 16 +++ 9 files changed, 136 insertions(+), 50 deletions(-) diff --git a/docker/README.rst b/docker/README.rst index 80451d1..a86db5d 100644 --- a/docker/README.rst +++ b/docker/README.rst @@ -108,6 +108,7 @@ NAME, name of the mariadb image, default value is validation TAG_PRE, first part of the image version, default value is mariadb TAG_VER, last part of the image version, default value is latest MARIADB_HOST_PORT, port on which mariadb is exposed on host, default value is 3307 +ENCRYPTION_KEY, the key that should be used by the AES algorithm for encrypting passwords stored in database, this variable is required In order to deploy the container, this script can be executed with the appropriate parameters. @@ -116,7 +117,7 @@ Example (assuming the default variables have been utilized for building the imag .. code-block:: console cd validation/docker/mariadb - ./deploy.sh MARIADB_ROOT_PASSWORD=root_password MARIADB_AKRAINO_PASSWORD=akraino_password UI_ADMIN_PASSWORD=admin UI_AKRAINO_PASSWORD=akraino + ./deploy.sh MARIADB_ROOT_PASSWORD=root_password MARIADB_AKRAINO_PASSWORD=akraino_password UI_ADMIN_PASSWORD=admin UI_AKRAINO_PASSWORD=akraino ENCRYPTION_KEY=key Also, in order to re-deploy the database (it is assumed that the corresponding mariadb container has been stopped and deleted) while the persistent storage already exists (currently, the directory /var/lib/mariadb of the host is used), a different approach should be used after the image build process. @@ -176,6 +177,7 @@ JENKINS_JOB_NAME, the name of Jenkins job capable of executing the blueprint val NEXUS_PROXY, the needed proxy in order for the Nexus server to be reachable, default value is none JENKINS_PROXY, the needed proxy in order for the Jenkins server to be reachable, default value is none CERTDIR, the directory where the SSL certificates can be found, default value is the working directory where self signed certificates exist only for demo purposes +ENCRYPTION_KEY, the key that should be used by the AES algorithm for encrypting passwords stored in database, this variable is required Note that, for a functional UI, the following prerequisites are needed: @@ -192,7 +194,7 @@ Example (assuming the default variables have been utilized for building the imag .. code-block:: console cd validation/docker/ui - ./deploy.sh DB_IP_PORT=172.17.0.3:3306 MARIADB_AKRAINO_PASSWORD=akraino_password + ./deploy.sh DB_IP_PORT=172.17.0.3:3306 MARIADB_AKRAINO_PASSWORD=akraino_password ENCRYPTION_KEY=key The kube-conformance container ============================== diff --git a/docker/mariadb/deploy.sh b/docker/mariadb/deploy.sh index ea48b54..b3fea76 100755 --- a/docker/mariadb/deploy.sh +++ b/docker/mariadb/deploy.sh @@ -26,6 +26,7 @@ MARIADB_ROOT_PASSWORD="" MARIADB_AKRAINO_PASSWORD="" UI_ADMIN_PASSWORD="" UI_AKRAINO_PASSWORD="" +ENCRYPTION_KEY="" # Image data REGISTRY=akraino NAME=validation @@ -48,6 +49,7 @@ do MARIADB_HOST_PORT) MARIADB_HOST_PORT=${VALUE} ;; UI_ADMIN_PASSWORD) UI_ADMIN_PASSWORD=${VALUE} ;; UI_AKRAINO_PASSWORD) UI_AKRAINO_PASSWORD=${VALUE} ;; + ENCRYPTION_KEY) ENCRYPTION_KEY=${VALUE} ;; *) esac done @@ -76,8 +78,14 @@ if [ -z "$UI_AKRAINO_PASSWORD" ] exit 1 fi +if [ -z "$ENCRYPTION_KEY" ] + then + echo "ERROR: You must specify the encryption key" + exit 1 +fi + IMAGE="$REGISTRY"/"$NAME":"$TAG_PRE"-"$TAG_VER" chmod 0444 "/$(pwd)/mariadb.conf" -docker run --detach --name $CONTAINER_NAME --publish $MARIADB_HOST_PORT:3306 -v $DOCKER_VOLUME_NAME:/var/lib/mysql -v "/$(pwd)/mariadb.conf:/etc/mysql/conf.d/my.cnf" -e MYSQL_ROOT_PASSWORD="$MARIADB_ROOT_PASSWORD" -e MYSQL_DATABASE="akraino" -e MYSQL_USER="akraino" -e MYSQL_PASSWORD="$MARIADB_AKRAINO_PASSWORD" -e UI_ADMIN_PASSWORD="$UI_ADMIN_PASSWORD" -e UI_AKRAINO_PASSWORD="$UI_AKRAINO_PASSWORD" $IMAGE -docker exec $CONTAINER_NAME /bin/bash -c 'sed -i 's/admin_password/'"$UI_ADMIN_PASSWORD"'/g' /docker-entrypoint-initdb.d/EcompSdkDMLMySql_2_4_OS.sql ; sed -i 's/akraino_password/'"$UI_AKRAINO_PASSWORD"'/g' /docker-entrypoint-initdb.d/EcompSdkDMLMySql_2_4_OS.sql; continue=`ps aux | grep mysql` ; while [ -z "$continue" ]; do continue=`ps aux | grep mysql`; sleep 5; done ; sleep 10 ;' +docker run --detach --name $CONTAINER_NAME --publish $MARIADB_HOST_PORT:3306 -v $DOCKER_VOLUME_NAME:/var/lib/mysql -v "/$(pwd)/mariadb.conf:/etc/mysql/conf.d/my.cnf" -e MYSQL_ROOT_PASSWORD="$MARIADB_ROOT_PASSWORD" -e MYSQL_DATABASE="akraino" -e MYSQL_USER="akraino" -e MYSQL_PASSWORD="$MARIADB_AKRAINO_PASSWORD" -e UI_ADMIN_PASSWORD="$UI_ADMIN_PASSWORD" -e UI_AKRAINO_PASSWORD="$UI_AKRAINO_PASSWORD" -e ENCRYPTION_KEY="$ENCRYPTION_KEY" $IMAGE +docker exec $CONTAINER_NAME /bin/bash -c 'sed -i 's/admin_password/'"$UI_ADMIN_PASSWORD"'/g' /docker-entrypoint-initdb.d/EcompSdkDMLMySql_2_4_OS.sql ; sed -i 's/akraino_password/'"$UI_AKRAINO_PASSWORD"'/g' /docker-entrypoint-initdb.d/EcompSdkDMLMySql_2_4_OS.sql; echo "UPDATE fn_user SET LOGIN_PWD = HEX(AES_ENCRYPT(LOGIN_PWD, \"$ENCRYPTION_KEY\"))" >> /docker-entrypoint-initdb.d/EcompSdkDMLMySql_2_4_OS.sql ;continue=`ps aux | grep mysql` ; while [ -z "$continue" ]; do continue=`ps aux | grep mysql`; sleep 5; done ; sleep 10 ;' sleep 10 diff --git a/docker/ui/deploy.sh b/docker/ui/deploy.sh index 388cb9c..43bd5bc 100755 --- a/docker/ui/deploy.sh +++ b/docker/ui/deploy.sh @@ -33,6 +33,7 @@ DB_IP_PORT="" NEXUS_PROXY="" JENKINS_PROXY="" CERTDIR=$(pwd) +ENCRYPTION_KEY="" for ARGUMENT in "$@" do @@ -53,6 +54,7 @@ do NEXUS_PROXY) NEXUS_PROXY=${VALUE} ;; JENKINS_PROXY) JENKINS_PROXY=${VALUE} ;; CERTDIR) CERTDIR=${VALUE} ;; + ENCRYPTION_KEY) ENCRYPTION_KEY=${VALUE} ;; *) esac done @@ -69,6 +71,12 @@ if [ -z "$MARIADB_AKRAINO_PASSWORD" ] exit 1 fi +if [ -z "$ENCRYPTION_KEY" ] + then + echo "ERROR: You must specify the encryption key" + exit 1 +fi + IMAGE="$REGISTRY"/"$NAME":"$TAG_PRE"-"$TAG_VER" -docker run --detach --name $CONTAINER_NAME --network="host" -v "$(pwd)/server.xml:/usr/local/tomcat/conf/server.xml" -v "$CERTDIR/bluval.key:/usr/local/tomcat/bluval.key" -v "$CERTDIR/bluval.crt:/usr/local/tomcat/bluval.crt" -v "$(pwd)/root_index.jsp:/usr/local/tomcat/webapps/ROOT/index.jsp" -e DB_IP_PORT="$DB_IP_PORT" -e MARIADB_AKRAINO_PASSWORD="$MARIADB_AKRAINO_PASSWORD" -e JENKINS_URL="$JENKINS_URL" -e JENKINS_USERNAME="$JENKINS_USERNAME" -e JENKINS_USER_PASSWORD="$JENKINS_USER_PASSWORD" -e JENKINS_JOB_NAME="$JENKINS_JOB_NAME" -e NEXUS_PROXY="$NEXUS_PROXY" -e JENKINS_PROXY="$JENKINS_PROXY" $IMAGE +docker run --detach --name $CONTAINER_NAME --network="host" -v "$(pwd)/server.xml:/usr/local/tomcat/conf/server.xml" -v "$CERTDIR/bluval.key:/usr/local/tomcat/bluval.key" -v "$CERTDIR/bluval.crt:/usr/local/tomcat/bluval.crt" -v "$(pwd)/root_index.jsp:/usr/local/tomcat/webapps/ROOT/index.jsp" -e DB_IP_PORT="$DB_IP_PORT" -e MARIADB_AKRAINO_PASSWORD="$MARIADB_AKRAINO_PASSWORD" -e JENKINS_URL="$JENKINS_URL" -e JENKINS_USERNAME="$JENKINS_USERNAME" -e JENKINS_USER_PASSWORD="$JENKINS_USER_PASSWORD" -e JENKINS_JOB_NAME="$JENKINS_JOB_NAME" -e NEXUS_PROXY="$NEXUS_PROXY" -e JENKINS_PROXY="$JENKINS_PROXY" -e ENCRYPTION_KEY="$ENCRYPTION_KEY" $IMAGE sleep 10 diff --git a/ui/CHANGELOG.md b/ui/CHANGELOG.md index c1343fe..50d234a 100644 --- a/ui/CHANGELOG.md +++ b/ui/CHANGELOG.md @@ -182,3 +182,11 @@ All notable changes to this project will be documented in this file. ### Removed +## [0.3.3-SNAPSHOT] - 25 September 2019 +### Added +- Encryption of passwords stored in database. + +### Changed +- Password of users that try to login is taken into account + +### Removed diff --git a/ui/README.rst b/ui/README.rst index 41b9a16..33ecb23 100644 --- a/ui/README.rst +++ b/ui/README.rst @@ -177,6 +177,7 @@ NAME, name of the mariadb image, default value is validation TAG_PRE, first part of the image version, default value is mariadb TAG_VER, last part of the image version, default value is latest MARIADB_HOST_PORT, port on which mariadb is exposed on host, default value is 3307 +ENCRYPTION_KEY, the key that should be used by the AES algorithm for encrypting passwords stored in database, this variable is required Currently, two users are supported by the UI, namely admin (full privileges) and akraino (limited privileges). Their passwords must be defined in the database. @@ -189,7 +190,7 @@ The mariadb root password, mariadb akraino user password (currently the UI conne cd validation/ui mvn docker:build -Ddocker.filter=akraino/validation:dev-mariadb-latest cd ../docker/mariadb - ./deploy.sh TAG_PRE=dev-mariadb MARIADB_ROOT_PASSWORD= MARIADB_AKRAINO_PASSWORD= UI_ADMIN_PASSWORD= UI_AKRAINO_PASSWORD= + ./deploy.sh TAG_PRE=dev-mariadb MARIADB_ROOT_PASSWORD= MARIADB_AKRAINO_PASSWORD= UI_ADMIN_PASSWORD= UI_AKRAINO_PASSWORD= ENCRYPTION_KEY= mysql -p -uakraino -h < ../../ui/db-scripts/examples/initialize_db_example.sql In order to retrieve the IP of the mariadb container, the following command should be executed: @@ -409,6 +410,7 @@ JENKINS_JOB_NAME, the name of Jenkins job capable of executing the blueprint val NEXUS_PROXY, the needed proxy in order for the Nexus server to be reachable, default value is none JENKINS_PROXY, the needed proxy in order for the Jenkins server to be reachable, default value is none CERTDIR, the directory where the SSL certificates can be found, default value is the working directory where self signed certificates exist only for demo purposes +ENCRYPTION_KEY, the key that should be used by the AES algorithm for encrypting passwords stored in database, this variable is required So, for a functional UI, the following prerequisites are needed: @@ -420,9 +422,9 @@ Then, the following commands can be executed in order to deploy the UI container .. code-block:: console cd ../docker/ui - ./deploy.sh TAG_PRE=dev-ui DB_IP_PORT= MARIADB_AKRAINO_PASSWORD= + ./deploy.sh TAG_PRE=dev-ui DB_IP_PORT= MARIADB_AKRAINO_PASSWORD= ENCRYPTION_KEY= -The content of the DB_IP_PORT can be for example '172.17.0.3:3306'. +The content of the DB_IP_PORT can be for example '172.17.0.3:3306'. Also, the value of the encryption key should be the same as the value of the encryption key used in database deployment. Furthermore, the TAG_PRE variable should be defined as the default value is 'ui' (note that the 'dev-ui' is used for development purposes - look at pom.xml file). @@ -430,7 +432,7 @@ If no proxy exists, the proxy ip and port variables should not be defined. The UI should be available in the following url: - https://localhost:8443/bluvalui/ + https://:8443/bluvalui/ Note that the deployment uses the network host mode, so the ports 8080 and 8443 must be available on the host. diff --git a/ui/db-scripts/EcompSdkDDLMySql_2_4_Common.sql b/ui/db-scripts/EcompSdkDDLMySql_2_4_Common.sql index bdfb647..ebc838f 100644 --- a/ui/db-scripts/EcompSdkDDLMySql_2_4_Common.sql +++ b/ui/db-scripts/EcompSdkDDLMySql_2_4_Common.sql @@ -311,8 +311,8 @@ create table fn_user ( HRID CHARACTER VARYING(20), ORG_USER_ID CHARACTER VARYING(20), ORG_CODE CHARACTER VARYING(30), - LOGIN_ID CHARACTER VARYING(25), - LOGIN_PWD CHARACTER VARYING(25), + LOGIN_ID CHARACTER VARYING(255), + LOGIN_PWD CHARACTER VARYING(255), LAST_LOGIN_DATE TIMESTAMP, ACTIVE_YN CHARACTER VARYING(1) DEFAULT 'Y' NOT NULL, CREATED_ID INT(11), diff --git a/ui/pom.xml b/ui/pom.xml index 6cdbc38..614d2ff 100644 --- a/ui/pom.xml +++ b/ui/pom.xml @@ -14,7 +14,7 @@ org.akraino.validation ui - 0.3.2-SNAPSHOT + 0.3.3-SNAPSHOT Bluval UI Maven Webapp war diff --git a/ui/src/main/java/org/akraino/validation/ui/login/LoginStrategyImpl.java b/ui/src/main/java/org/akraino/validation/ui/login/LoginStrategyImpl.java index 55960ac..b6a78fc 100644 --- a/ui/src/main/java/org/akraino/validation/ui/login/LoginStrategyImpl.java +++ b/ui/src/main/java/org/akraino/validation/ui/login/LoginStrategyImpl.java @@ -1,51 +1,41 @@ -/*- - * ============LICENSE_START========================================== - * ONAP Portal - * =================================================================== - * Copyright © 2017 AT&T Intellectual Property. All rights reserved. - * =================================================================== +/* + * Copyright (c) 2019 AT&T Intellectual Property. All rights reserved. * - * Unless otherwise specified, all software contained herein is licensed - * under the Apache License, Version 2.0 (the "License"); - * you may not use this software except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License 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. - * - * Unless otherwise specified, all documentation contained herein is licensed - * under the Creative Commons License, Attribution 4.0 Intl. (the "License"); - * you may not use this documentation except in compliance with the License. - * You may obtain a copy of the License at - * - * https://creativecommons.org/licenses/by/4.0/ - * - * Unless required by applicable law or agreed to in writing, documentation - * distributed under the License 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. - * - * ============LICENSE_END============================================ - * - * + * 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. */ package org.akraino.validation.ui.login; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.apache.commons.codec.binary.Hex; import org.onap.portalsdk.core.auth.LoginStrategy; import org.onap.portalsdk.core.command.LoginBean; import org.onap.portalsdk.core.domain.RoleFunction; @@ -62,9 +52,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.servlet.ModelAndView; /** - * Implements basic single-signon login strategy for open-source - * applications when users start at Portal. Extracts an encrypted user ID - * sent by Portal. + * Implements basic single-signon login strategy for open-source applications + * when users start at Portal. Extracts an encrypted user ID sent by Portal. */ public class LoginStrategyImpl extends LoginStrategy { @@ -87,9 +76,11 @@ public class LoginStrategyImpl extends LoginStrategy { LoginBean commandBean = new LoginBean(); String loginId = request.getParameter("loginId"); String password = request.getParameter("password"); + String key = System.getenv("ENCRYPTION_KEY"); + password = aesEncrypt(password, key); commandBean.setLoginId(loginId); commandBean.setLoginPwd(password); - commandBean.setUserid(loginId); + // commandBean.setUserid(loginId); commandBean = loginService.findUser(commandBean, (String) request.getAttribute(MenuProperties.MENU_PROPERTIES_FILENAME_KEY), new HashMap()); List roleFunctionList = roleService.getRoleFunctions(loginId); @@ -111,6 +102,40 @@ public class LoginStrategyImpl extends LoginStrategy { } } + @Override + public ModelAndView doExternalLogin(HttpServletRequest request, HttpServletResponse response) throws IOException { + + invalidateExistingSession(request); + + LoginBean commandBean = new LoginBean(); + String loginId = request.getParameter("loginId"); + String password = request.getParameter("password"); + String key = System.getenv("ENCRYPTION_KEY"); + password = aesEncrypt(password, key); + commandBean.setLoginId(loginId); + commandBean.setLoginPwd(password); + // commandBean.setUserid(loginId); + commandBean = loginService.findUser(commandBean, + (String) request.getAttribute(MenuProperties.MENU_PROPERTIES_FILENAME_KEY), new HashMap()); + List roleFunctionList = roleService.getRoleFunctions(loginId); + + if (commandBean.getUser() == null) { + String loginErrorMessage = (commandBean.getLoginErrorMessage() != null) ? commandBean.getLoginErrorMessage() + : "login.error.external.invalid"; + Map model = new HashMap<>(); + model.put("error", loginErrorMessage); + return new ModelAndView("login_external", "model", model); + } else { + // store the currently logged in user's information in the session + UserUtils.setUserSession(request, commandBean.getUser(), commandBean.getMenu(), + commandBean.getBusinessDirectMenu(), + SystemProperties.getProperty(SystemProperties.LOGIN_METHOD_BACKDOOR), roleFunctionList); + initateSessionMgtHandler(request); + // user has been authenticated, now take them to the welcome page + return new ModelAndView("redirect:welcome"); + } + } + @Override public String getUserId(HttpServletRequest request) throws PortalAPIException { // Check ECOMP Portal cookie @@ -130,8 +155,8 @@ public class LoginStrategyImpl extends LoginStrategy { } /** - * Searches the request for the user-ID cookie and decrypts the value - * using a key configured in properties + * Searches the request for the user-ID cookie and decrypts the value using a + * key configured in properties * * @param request HttpServletRequest * @return User ID @@ -154,7 +179,7 @@ public class LoginStrategyImpl extends LoginStrategy { /** * Searches the request for the named cookie. * - * @param request HttpServletRequest + * @param request HttpServletRequest * @param cookieName Name of desired cookie * @return Cookie if found; otherwise null. */ @@ -167,4 +192,21 @@ public class LoginStrategyImpl extends LoginStrategy { return null; } + private String aesEncrypt(String password, String strKey) { + try { + byte[] keyBytes = Arrays.copyOf(strKey.getBytes("ASCII"), 16); + SecretKey key = new SecretKeySpec(keyBytes, "AES"); + Cipher cipher = Cipher.getInstance("AES"); + cipher.init(Cipher.ENCRYPT_MODE, key); + byte[] cleartext = password.getBytes("UTF-8"); + byte[] ciphertextBytes = cipher.doFinal(cleartext); + return new String(Hex.encodeHex(ciphertextBytes)); + } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | UnsupportedEncodingException + | IllegalBlockSizeException | BadPaddingException e) { + LOGGER.error(EELFLoggerDelegate.errorLogger, + "Error when encrypting password key" + UserUtils.getStackTrace(e)); + return null; + } + } + } diff --git a/ui/src/main/java/org/akraino/validation/ui/service/DbAdapter.java b/ui/src/main/java/org/akraino/validation/ui/service/DbAdapter.java index 56eada6..756c03a 100644 --- a/ui/src/main/java/org/akraino/validation/ui/service/DbAdapter.java +++ b/ui/src/main/java/org/akraino/validation/ui/service/DbAdapter.java @@ -1,3 +1,19 @@ +/* + * Copyright (c) 2019 AT&T Intellectual Property. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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. + */ + package org.akraino.validation.ui.service; import java.io.IOException; -- 2.16.6