/*
* Copyright (C) 2014 Intel Corporation
* All rights reserved.
*/
package com.intel.mtwilson.shiro.setup;
import com.intel.mtwilson.user.management.rest.v2.model.RolePermission;
import com.intel.mtwilson.user.management.rest.v2.model.UserLoginCertificate;
import com.intel.mtwilson.user.management.rest.v2.model.UserLoginPassword;
import com.intel.mtwilson.user.management.rest.v2.model.User;
import com.intel.mtwilson.user.management.rest.v2.model.Status;
import com.intel.mtwilson.user.management.rest.v2.model.Role;
import com.intel.dcsg.cpg.crypto.CryptographyException;
import com.intel.dcsg.cpg.crypto.RandomUtil;
import com.intel.dcsg.cpg.crypto.RsaCredentialX509;
import com.intel.dcsg.cpg.crypto.RsaUtil;
import com.intel.dcsg.cpg.crypto.Sha1Digest;
import com.intel.dcsg.cpg.crypto.Sha256Digest;
import com.intel.dcsg.cpg.crypto.SimpleKeystore;
import com.intel.dcsg.cpg.i18n.LocaleUtil;
import com.intel.dcsg.cpg.io.ByteArrayResource;
import com.intel.dcsg.cpg.io.Platform;
import com.intel.mtwilson.MyFilesystem;
import com.intel.mtwilson.crypto.password.PasswordUtil;
import com.intel.mtwilson.setup.DatabaseSetupTask;
import com.intel.mtwilson.shiro.jdbi.LoginDAO;
import com.intel.mtwilson.shiro.jdbi.MyJdbi;
import java.io.File;
import java.util.List;
import org.apache.commons.io.FileUtils;
import com.intel.dcsg.cpg.io.UUID;
import com.intel.dcsg.cpg.validation.Fault;
import com.intel.dcsg.cpg.x509.X509Builder;
import com.intel.dcsg.cpg.x509.X509Util;
import com.intel.mtwilson.My;
import com.intel.mtwilson.ms.controller.exceptions.MSDataException;
import com.intel.mtwilson.ms.controller.exceptions.NonexistentEntityException;
import com.intel.mtwilson.ms.data.MwPortalUser;
import java.io.IOException;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableEntryException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.sql.Connection;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.RandomStringUtils;
/**
* we do not store the admin username or password in configuration - the application
* must display them to the administrator
*
* You can execute this task from command line with something like this:
* mtwilson setup setup-manager create-admin-user
*
* @author jbuhacoff
*/
public class CreateAdminUser extends DatabaseSetupTask {
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CreateAdminUser.class);
public static final String ADMINISTRATOR_ROLE = "administrator";
public static final String APPROVED = "APPROVED";
private String username;
private String password;
private List<Fault> certificateBuilderFaults = null; // only populated if during execution the certificate builder fails; then if application calls validation afterwards we can copy these faults to our validation faults to inform the user; if not, the faults get logged to the error log anyway
private String getUsername() {
if (System.getenv("MC_FIRST_USERNAME") != null) {
return System.getenv("MC_FIRST_USERNAME");
}
return "admin";
}
private String getPassword() {
if (System.getenv("MC_FIRST_PASSWORD") != null) {
return System.getenv("MC_FIRST_PASSWORD");
}
return RandomStringUtils.randomAscii(16);
}
@Override
protected void configure() throws Exception {
username = getUsername();
password = getPassword();
// check that the mtwilson CA key is present
if (!My.configuration().getCaKeystoreFile().exists()) {
configuration("Mt Wilson CA is required to create the portal administrator");
}
// check for the required database tables to be present
try (Connection c = My.jdbc().connection()) {
requireTable(c, "mw_role");
requireTable(c, "mw_role_permission");
requireTable(c, "mw_user");
requireTable(c, "mw_user_login_password");
requireTable(c, "mw_user_login_password_role");
requireTable(c, "mw_user_login_certificate");
requireTable(c, "mw_user_login_certificate_role");
requireTable(c, "mw_portal_user");
}
}
Set<UUID> getRoleUuidList(List<Role> roles) {
HashSet<UUID> uuids = new HashSet<>();
for (Role role : roles) {
uuids.add(role.getId());
}
return uuids;
}
// see also LoginPassword command
private Set<String> toStrings(Set<UUID> uuids) {
HashSet<String> set = new HashSet<>();
for(UUID uuid : uuids) {
set.add(uuid.toString());
}
return set;
}
@Override
protected void validate() throws Exception {
// ensure we have an admin user created with permissions assigned - at least one assigned login method must have them
boolean isAdminPermissionAssigned = false;
try (LoginDAO loginDAO = MyJdbi.authz()) {
UserLoginPassword userLoginPassword = loginDAO.findUserLoginPasswordByUsername(username);
if (userLoginPassword == null) {
validation("User does not exist or does not have a password: %s", username);
}
else {
List<Role> passwordRoles = loginDAO.findRolesByUserLoginPasswordId(userLoginPassword.getId());
Set<UUID> passwordRoleIds = getRoleUuidList(passwordRoles);
List<RolePermission> passwordRolePermissions = loginDAO.findRolePermissionsByPasswordRoleIds(toStrings(passwordRoleIds));
if (passwordRolePermissions == null || passwordRolePermissions.isEmpty()) {
validation("User does not have password permissions assigned: %s", username);
}
else {
for (RolePermission rolePermission : passwordRolePermissions) {
if (rolePermission.getPermitDomain().equals("*") && rolePermission.getPermitAction().equals("*") && rolePermission.getPermitSelection().equals("*")) {
isAdminPermissionAssigned = true;
}
}
}
}
// now check for the admin user's x509 login
UserLoginCertificate userLoginCertificate = loginDAO.findUserLoginCertificateByUsername(username);
if (userLoginCertificate == null) {
validation("User does not exist or does not have a certificate");
}
else {
List<Role> certificateRoles = loginDAO.findRolesByUserLoginCertificateId(userLoginCertificate.getId());
Set<UUID> certificateRoleIds = getRoleUuidList(certificateRoles);
List<RolePermission> certificateRolePermissions = loginDAO.findRolePermissionsByCertificateRoleIds(toStrings(certificateRoleIds));
if (certificateRolePermissions == null || certificateRolePermissions.isEmpty()) {
validation("User does not have certificate permissions assigned: %s", username);
}
else {
for (RolePermission rolePermission : certificateRolePermissions) {
if (rolePermission.getPermitDomain().equals("*") && rolePermission.getPermitAction().equals("*") && rolePermission.getPermitSelection().equals("*")) {
isAdminPermissionAssigned = true;
}
}
}
}
if (!isAdminPermissionAssigned) {
validation("User does not have admin permissions assigned: %s", username); // eithe rpassword or certificate
}
// now check the portal user exists and that we can unlock the keystore with the new password
MwPortalUser portalUser = My.jpa().mwPortalUser().findMwPortalUserByUserName(username);
if( portalUser == null ) {
validation("Portal User was not created");
}
else {
if( !portalUser.getEnabled() ) {
validation("Portal User is not enabled");
}
if( !APPROVED.equalsIgnoreCase(portalUser.getStatus()) ) {
validation("Portal User status is not approved");
}
byte[] keystoreBytes = portalUser.getKeystore();
if( keystoreBytes == null || keystoreBytes.length == 0 ) {
validation("Portal User keystore is missing");
}
else {
try {
SimpleKeystore keystore = new SimpleKeystore(new ByteArrayResource(keystoreBytes), password);
X509Certificate certificate = keystore.getX509Certificate(username);
if( certificate == null ) {
validation("Portal User keystore does not contain user certificate");
}
}
catch(KeyManagementException | NoSuchAlgorithmException | UnrecoverableEntryException | KeyStoreException | CertificateEncodingException e) {
log.error("Cannot open user keystore: {}", e.getMessage());
validation("Portal User keystore cannot be opened");
}
}
}
}
// now if the validation is happening immediately after execution and there was a problem building the certificate, we will have some faults here to share with the user
if( certificateBuilderFaults != null ) {
for(Fault fault : certificateBuilderFaults) {
validation(fault);
}
}
}
private User createUser(LoginDAO loginDAO) {
User user = loginDAO.findUserByName(username);
if (user == null) {
user = new User();
user.setId(new UUID());
user.setComment("automatically created by setup");
// user.setEnabled(true);
// user.setStatus(Status.APPROVED);
user.setUsername(username);
loginDAO.insertUser(user.getId(), user.getUsername(), null, user.getComment()); // setting the default locale to null
}
return user;
}
private Role createAdminRole(LoginDAO loginDAO) {
Role adminRole = loginDAO.findRoleByName(ADMINISTRATOR_ROLE);
if (adminRole == null) {
adminRole = new Role();
adminRole.setId(new UUID());
adminRole.setRoleName(ADMINISTRATOR_ROLE);
adminRole.setDescription("the only role required to exist; the administrator may create and edit all other roles and permissions");
loginDAO.insertRole(adminRole.getId(), adminRole.getRoleName(), adminRole.getDescription());
}
return adminRole;
}
private UserLoginPassword createUserLoginPassword(LoginDAO loginDAO, User user) throws IOException {
UserLoginPassword userLoginPassword = loginDAO.findUserLoginPasswordByUsername(username);
if (userLoginPassword == null) {
userLoginPassword = new UserLoginPassword();
userLoginPassword.setId(new UUID());
userLoginPassword.setUserId(user.getId());
userLoginPassword.setAlgorithm("SHA256");
userLoginPassword.setIterations(1);
userLoginPassword.setSalt(RandomUtil.randomByteArray(8));
userLoginPassword.setPasswordHash(PasswordUtil.hash(password.getBytes(), userLoginPassword));
userLoginPassword.setEnabled(true);
userLoginPassword.setStatus(Status.APPROVED);
userLoginPassword.setComment("automatically created by setup");
loginDAO.insertUserLoginPassword(userLoginPassword.getId(), userLoginPassword.getUserId(), userLoginPassword.getPasswordHash(),
userLoginPassword.getSalt(), userLoginPassword.getIterations(), userLoginPassword.getAlgorithm(), userLoginPassword.getExpires(),
userLoginPassword.isEnabled(), userLoginPassword.getStatus(), userLoginPassword.getComment());
// now we have to store the password somewhere so the admin can read it and know it; but it doesn't belong in the server configuration so we save it in a file which the admin can read and delete (or the setup application can automatically delete it)
storeAdminPassword();
}
return userLoginPassword;
}
private List<RolePermission> createAdminUserPasswordPermissions(LoginDAO loginDAO, Role adminRole, UserLoginPassword userLoginPassword) {
List<Role> adminUserPasswordRoles = loginDAO.findRolesByUserLoginPasswordId(userLoginPassword.getId());
HashSet<UUID> adminUserPasswordRoleIdList = new HashSet<>(); // it's a list but it will only be populated with ONE role id -- the administrator role id
if (adminUserPasswordRoles.isEmpty()) {
loginDAO.insertUserLoginPasswordRole(userLoginPassword.getId(), adminRole.getId());
adminUserPasswordRoleIdList.add(adminRole.getId());
} else {
// admin user already has some roles, check if one of them is the admin role -- if the admin user is missing the admin role we will automatically add it
for (Role role : adminUserPasswordRoles) {
if (role.getRoleName().equalsIgnoreCase(ADMINISTRATOR_ROLE) && role.getId().equals(adminRole.getId())) {
adminUserPasswordRoleIdList.add(role.getId());
}
}
if (adminUserPasswordRoleIdList.isEmpty()) {
loginDAO.insertUserLoginPasswordRole(userLoginPassword.getId(), adminRole.getId());
adminUserPasswordRoleIdList.add(adminRole.getId());
}
}
List<RolePermission> adminPasswordRolePermissions = loginDAO.findRolePermissionsByPasswordRoleIds(toStrings(adminUserPasswordRoleIdList));
if (adminPasswordRolePermissions.isEmpty()) {
RolePermission adminPermission = new RolePermission();
adminPermission.setRoleId(adminRole.getId());
adminPermission.setPermitDomain("*");
adminPermission.setPermitAction("*");
adminPermission.setPermitSelection("*");
loginDAO.insertRolePermission(adminPermission.getRoleId(), adminPermission.getPermitDomain(), adminPermission.getPermitAction(), adminPermission.getPermitSelection());
}
return adminPasswordRolePermissions;
}
private MwPortalUser createMwPortalUser(PrivateKey privateKey, X509Certificate certificate, X509Certificate... cacerts)
throws IOException, KeyManagementException, KeyStoreException, NoSuchAlgorithmException, CertificateException, NonexistentEntityException, MSDataException {
MwPortalUser portalUser = My.jpa().mwPortalUser().findMwPortalUserByUserName(username);
if (portalUser == null) {
log.debug("Creating new mw_portal_user entry with keystore");
ByteArrayResource resource = new ByteArrayResource();
SimpleKeystore keystore = new SimpleKeystore(resource, password);
keystore.addKeyPairX509(privateKey, certificate, username, password, cacerts);
// add the CA cert itself to the keystore
for(X509Certificate cacert : cacerts) {
keystore.addTrustedCaCertificate(cacert, "mtwilson-cacert");
}
// add the mtwilson saml and tls certs to the keystore
X509Certificate saml = getSamlCertificate();
if (saml != null) {
keystore.addTrustedSamlCertificate(saml, "mtwilson-saml");
}
X509Certificate tls = getTlsCertificate();
if (tls != null) {
keystore.addTrustedSslCertificate(tls, "mtwilson-tls");
}
keystore.save();
portalUser = new MwPortalUser();
portalUser.setComment("created automatically by setup");
portalUser.setUsername(username);
portalUser.setUuid_hex(new UUID().toString());
portalUser.setStatus(APPROVED);
portalUser.setEnabled(true);
portalUser.setKeystore(resource.toByteArray());
log.debug("Created keystore size {} bytes", portalUser.getKeystore().length);
My.jpa().mwPortalUser().create(portalUser);
} else {
log.debug("Updating existing mw_portal_user entry");
SimpleKeystore keystore = new SimpleKeystore(portalUser.getKeystoreResource(), password);
try {
// ensure the "username" alias is not alraedy in the keystore because we will add the private key
// there later
keystore.delete(username);
} catch (Exception e) {
log.debug("Existing keystore did not contain private key for {}: {}", username, e.getMessage());
}
keystore.addKeyPairX509(privateKey, certificate, username, password);
keystore.save(); // saves into portalUser keystore field
portalUser.setComment("updated automatically by setup");
portalUser.setStatus(APPROVED);
portalUser.setEnabled(true);
My.jpa().mwPortalUser().edit(portalUser);
}
return portalUser;
}
private UserLoginCertificate createUserLoginCertificate(LoginDAO loginDAO, User user)
throws FileNotFoundException, IOException, CryptographyException, CertificateException, NoSuchAlgorithmException, KeyManagementException,
KeyStoreException, NonexistentEntityException, MSDataException {
UserLoginCertificate userLoginCertificate = loginDAO.findUserLoginCertificateByUsername(username);
if (userLoginCertificate == null) {
// first load the ca key
byte[] combinedPrivateKeyAndCertPemBytes;
try (FileInputStream cakeyIn = new FileInputStream(My.configuration().getCaKeystoreFile())) {
combinedPrivateKeyAndCertPemBytes = IOUtils.toByteArray(cakeyIn);
}
PrivateKey cakey = RsaUtil.decodePemPrivateKey(new String(combinedPrivateKeyAndCertPemBytes));
X509Certificate cacert = X509Util.decodePemCertificate(new String(combinedPrivateKeyAndCertPemBytes));
// now create the user certificate
KeyPair keyPair = RsaUtil.generateRsaKeyPair(RsaUtil.MINIMUM_RSA_KEY_SIZE);
/*
X509Certificate certificate = X509Builder.factory()
.selfSigned(String.format("CN=%s", username), keyPair)
.expires(365, TimeUnit.DAYS)
.build();
*/
X509Builder builder = X509Builder.factory();
X509Certificate certificate = builder
.commonName(username)
.subjectPublicKey(keyPair.getPublic())
.expires(RsaUtil.DEFAULT_RSA_KEY_EXPIRES_DAYS, TimeUnit.DAYS)
.issuerPrivateKey(cakey)
.issuerName(cacert)
.keyUsageDigitalSignature()
.keyUsageNonRepudiation()
.keyUsageKeyEncipherment()
.extKeyUsageIsCritical()
.randomSerial()
.build();
if( certificate == null ) {
// validation("Failed to create certificate");
certificateBuilderFaults = builder.getFaults();
for(Fault fault : certificateBuilderFaults) {
log.error(fault.toString());
}
throw new CertificateException("Cannot generate certificate");
}
userLoginCertificate = new UserLoginCertificate();
userLoginCertificate.setId(new UUID());
userLoginCertificate.setCertificate(certificate.getEncoded());
userLoginCertificate.setComment("automatically created by setup");
userLoginCertificate.setEnabled(true);
userLoginCertificate.setExpires(certificate.getNotAfter());
userLoginCertificate.setSha1Hash(Sha1Digest.digestOf(certificate.getEncoded()).toByteArray());
userLoginCertificate.setSha256Hash(Sha256Digest.digestOf(certificate.getEncoded()).toByteArray());
userLoginCertificate.setStatus(Status.APPROVED);
userLoginCertificate.setUserId(user.getId());
loginDAO.insertUserLoginCertificate(userLoginCertificate.getId(), userLoginCertificate.getUserId(), userLoginCertificate.getCertificate(), userLoginCertificate.getSha1Hash(), userLoginCertificate.getSha256Hash(), userLoginCertificate.getExpires(), userLoginCertificate.isEnabled(), userLoginCertificate.getStatus(), userLoginCertificate.getComment());
log.debug("Created user login certificate with sha256 {}", Sha256Digest.valueOf(userLoginCertificate.getSha256Hash()).toHexString());
// now we have to store the private key somewhere.... for now we will create a portal user keystore so the admin user can use these privileges when logged in to portal
MwPortalUser portalUser = createMwPortalUser(keyPair.getPrivate(), certificate, cacert);
log.debug("Created the portal user {} successfully", portalUser.getUsername());
}
return userLoginCertificate;
}
private List<RolePermission> createAdminUserCertificatePermissions(LoginDAO loginDAO, Role adminRole, UserLoginCertificate userLoginCertificate) {
List<Role> adminUserCertificateRoles = loginDAO.findRolesByUserLoginCertificateId(userLoginCertificate.getId());
HashSet<UUID> adminUserCertificateRoleIdList = new HashSet<>(); // it's a list but it will only be populated with ONE role id -- the administrator role id
if (adminUserCertificateRoles.isEmpty()) {
loginDAO.insertUserLoginCertificateRole(userLoginCertificate.getId(), adminRole.getId());
adminUserCertificateRoleIdList.add(adminRole.getId());
} else {
// admin user already has some roles, check if one of them is the admin role -- if the admin user is missing the admin role we will automatically add it
for (Role role : adminUserCertificateRoles) {
if (role.getRoleName().equalsIgnoreCase(ADMINISTRATOR_ROLE)) {
adminUserCertificateRoleIdList.add(role.getId());
}
}
if (adminUserCertificateRoleIdList.isEmpty()) {
loginDAO.insertUserLoginCertificateRole(userLoginCertificate.getId(), adminRole.getId());
adminUserCertificateRoleIdList.add(adminRole.getId());
}
}
// already handled above by password block... this would link to same role which would have same permissions.... so this block is a no-op
List<RolePermission> adminCertificateRolePermissions = loginDAO.findRolePermissionsByCertificateRoleIds(toStrings(adminUserCertificateRoleIdList));
if (adminCertificateRolePermissions.isEmpty()) {
RolePermission adminPermission = new RolePermission();
adminPermission.setRoleId(adminRole.getId());
adminPermission.setPermitDomain("*");
adminPermission.setPermitAction("*");
adminPermission.setPermitSelection("*");
loginDAO.insertRolePermission(adminPermission.getRoleId(), adminPermission.getPermitDomain(), adminPermission.getPermitAction(), adminPermission.getPermitSelection());
}
return adminCertificateRolePermissions;
}
@Override
protected void execute() throws Exception {
try (LoginDAO loginDAO = MyJdbi.authz()) {
User user = createUser(loginDAO);
Role adminRole = createAdminRole(loginDAO);
// password-based login
UserLoginPassword userLoginPassword = createUserLoginPassword(loginDAO, user);
List<RolePermission> adminPasswordRolePermissions = createAdminUserPasswordPermissions(loginDAO, adminRole, userLoginPassword);
log.debug("Added {} password roles for admin user.", adminPasswordRolePermissions.size());
// now prepare the x509 certificate login
UserLoginCertificate userLoginCertificate = createUserLoginCertificate(loginDAO, user);
List<RolePermission> adminCertificateRolePermissions = createAdminUserCertificatePermissions(loginDAO, adminRole, userLoginCertificate);
log.debug("Added {} certificate roles for admin user.", adminCertificateRolePermissions.size());
}
}
private void storeAdminPassword() throws IOException {
// save the password to a file so the admin user can read it ; because it shouldn't be stored in the permanent configuration
File privateDir = new File(MyFilesystem.getApplicationFilesystem().getConfigurationPath() + File.separator + "private");
if (!privateDir.exists()) {
privateDir.mkdirs();
}
if (Platform.isUnix()) {
Runtime.getRuntime().exec("chmod 700 " + privateDir.getAbsolutePath());
}
File passwordFile = privateDir.toPath().resolve("password.txt").toFile();
FileUtils.writeStringToFile(passwordFile, ""); // first create an empty file so we can set permissions before writing the password to it
if (Platform.isUnix()) {
Runtime.getRuntime().exec("chmod 600 " + passwordFile.getAbsolutePath());
}
FileUtils.writeStringToFile(passwordFile, String.format("%s\n", password));
}
private X509Certificate getSamlCertificate() throws KeyManagementException, KeyStoreException {
SimpleKeystore samlKeystore = new SimpleKeystore(My.configuration().getSamlKeystoreFile(), My.configuration().getSamlKeystorePassword());
for (String alias : samlKeystore.aliases()) {
log.debug("SAML Keystore alias: {}", alias);
// make sure it has a SAML private key and certificate inside
try {
RsaCredentialX509 credential = samlKeystore.getRsaCredentialX509(alias, My.configuration().getSamlKeystorePassword());
log.debug("SAML certificate: {}", credential.getCertificate().getSubjectX500Principal().getName());
return credential.getCertificate();
} catch (IOException | KeyStoreException | NoSuchAlgorithmException | UnrecoverableEntryException | CertificateEncodingException | CryptographyException e) {
log.debug("Cannot read SAML key from keystore", e);
// validation("Cannot read SAML key from keystore"); // we are assuming the keystore only has one private key entry ...
}
}
return null;
}
private X509Certificate getTlsCertificate() throws KeyManagementException, KeyStoreException {
SimpleKeystore tlsKeystore = new SimpleKeystore(My.configuration().getTlsKeystoreFile(), My.configuration().getTlsKeystorePassword()); //"changeit");
for (String alias : tlsKeystore.aliases()) {
log.debug("TLS Keystore alias: {}", alias);
// make sure it has a SAML private key and certificate inside
try {
if ("tomcat".equals(alias) || "s1as".equals(alias)) {
RsaCredentialX509 credential = tlsKeystore.getRsaCredentialX509(alias, My.configuration().getTlsKeystorePassword()); //"changeit");
log.debug("TLS certificate: {}", credential.getCertificate().getSubjectX500Principal().getName());
return credential.getCertificate();
} else if (!"glassfish-instance".equals(alias)) {
log.warn("Cannot find TLS certificate with correct matching alias.");
}
} catch (IOException | KeyStoreException | NoSuchAlgorithmException | UnrecoverableEntryException | CertificateEncodingException | CryptographyException e) {
log.debug("Cannot read TLS key from keystore", e);
// validation("Cannot read SAML key from keystore"); // we are assuming the keystore only has one private key entry ...
}
}
return null;
}
}