Search in sources :

Example 1 with MaterializedJWT

use of io.hops.hopsworks.persistence.entity.airflow.MaterializedJWT in project hopsworks by logicalclocks.

the class AirflowManager method prepareSecurityMaterial.

@Lock(LockType.READ)
@AccessTimeout(value = 1, unit = TimeUnit.SECONDS)
public void prepareSecurityMaterial(Users user, Project project, String[] audience) throws AirflowException {
    isInitialized();
    MaterializedJWTID materialID = new MaterializedJWTID(project.getId(), user.getUid(), MaterializedJWTID.USAGE.AIRFLOW);
    if (!materializedJWTFacade.exists(materialID)) {
        LocalDateTime expirationDate = DateUtils.getNow().plus(settings.getJWTLifetimeMs(), ChronoUnit.MILLIS);
        AirflowJWT airflowJWT = new AirflowJWT(user.getUsername(), project.getId(), project.getName(), expirationDate, user.getUid());
        try {
            String[] roles = getUserRoles(user);
            MaterializedJWT airflowMaterial = new MaterializedJWT(new MaterializedJWTID(project.getId(), user.getUid(), MaterializedJWTID.USAGE.AIRFLOW));
            materializedJWTFacade.persist(airflowMaterial);
            Map<String, Object> claims = new HashMap<>(3);
            claims.put(Constants.RENEWABLE, false);
            claims.put(Constants.EXPIRY_LEEWAY, settings.getJWTExpLeewaySec());
            claims.put(Constants.ROLES, roles);
            String token = jwtController.createToken(settings.getJWTSigningKeyName(), false, settings.getJWTIssuer(), audience, DateUtils.localDateTime2Date(expirationDate), DateUtils.localDateTime2Date(DateUtils.getNow()), user.getUsername(), claims, SignatureAlgorithm.valueOf(settings.getJWTSignatureAlg()));
            String projectAirflowDir = getProjectSecretsDirectory(user.getUsername()).toString();
            airflowJWT.tokenFile = Paths.get(projectAirflowDir, getTokenFileName(project.getName(), user.getUsername()));
            airflowJWT.token = token;
            writeTokenToFile(airflowJWT);
            certificateMaterializer.materializeCertificatesLocalCustomDir(user.getUsername(), project.getName(), projectAirflowDir);
            airflowJWTs.add(airflowJWT);
        } catch (GeneralSecurityException | JWTException ex) {
            deleteAirflowMaterial(materialID);
            throw new AirflowException(RESTCodes.AirflowErrorCode.JWT_NOT_CREATED, Level.SEVERE, "Could not generate Airflow JWT for user " + user.getUsername(), ex.getMessage(), ex);
        } catch (IOException ex) {
            LOG.log(Level.WARNING, "Could not write Airflow JWT for user " + hdfsUsersController.getHdfsUserName(project, user), ex);
            deleteAirflowMaterial(materialID);
            try {
                jwtController.invalidate(airflowJWT.token);
            } catch (InvalidationException invEx) {
                LOG.log(Level.FINE, "Could not invalidate Airflow JWT. Skipping...", ex);
            }
            throw new AirflowException(RESTCodes.AirflowErrorCode.JWT_NOT_STORED, Level.SEVERE, "Could not store Airflow JWT for user " + hdfsUsersController.getHdfsUserName(project, user), ex.getMessage(), ex);
        }
    }
}
Also used : LocalDateTime(java.time.LocalDateTime) HashMap(java.util.HashMap) JWTException(io.hops.hopsworks.jwt.exception.JWTException) GeneralSecurityException(java.security.GeneralSecurityException) IOException(java.io.IOException) MaterializedJWT(io.hops.hopsworks.persistence.entity.airflow.MaterializedJWT) MaterializedJWTID(io.hops.hopsworks.persistence.entity.airflow.MaterializedJWTID) AirflowException(io.hops.hopsworks.exceptions.AirflowException) InvalidationException(io.hops.hopsworks.jwt.exception.InvalidationException) AccessTimeout(javax.ejb.AccessTimeout) Lock(javax.ejb.Lock)

Example 2 with MaterializedJWT

use of io.hops.hopsworks.persistence.entity.airflow.MaterializedJWT in project hopsworks by logicalclocks.

the class JupyterJWTManager method recover.

protected void recover() {
    LOG.log(INFO, "Starting Jupyter JWT manager recovery");
    List<MaterializedJWT> failed2recover = new ArrayList<>();
    // Get state from the database
    for (MaterializedJWT materializedJWT : materializedJWTFacade.findAll4Jupyter()) {
        LOG.log(Level.FINEST, "Recovering Jupyter JWT " + materializedJWT.getIdentifier());
        // First lookup project and user in db
        Project project = projectFacade.find(materializedJWT.getIdentifier().getProjectId());
        Users user = userFacade.find(materializedJWT.getIdentifier().getUserId());
        if (project == null || user == null) {
            LOG.log(Level.WARNING, "Tried to recover " + materializedJWT.getIdentifier() + " but could not find " + "either Project or User");
            failed2recover.add(materializedJWT);
            continue;
        }
        // Get Jupyter configuration from db
        String hdfsUsername = hdfsUsersController.getHdfsUserName(project, user);
        JupyterProject jupyterProject = jupyterFacade.findByUser(hdfsUsername);
        if (jupyterProject == null) {
            LOG.log(Level.FINEST, "There is no Jupyter configuration persisted for " + materializedJWT.getIdentifier());
            failed2recover.add(materializedJWT);
            continue;
        }
        // Check if Jupyter is still running
        if (!jupyterManager.ping(jupyterProject)) {
            LOG.log(Level.FINEST, "Jupyter server is not running for " + materializedJWT.getIdentifier() + " Skip recovering...");
            failed2recover.add(materializedJWT);
            continue;
        }
        JupyterSettings jupyterSettings = jupyterSettingsFacade.findByProjectUser(project, user.getEmail());
        Path tokenFile = constructTokenFilePath(jupyterSettings);
        String token = null;
        JupyterJWT jupyterJWT = null;
        CidAndPort pidAndPort = new CidAndPort(jupyterProject.getCid(), jupyterProject.getPort());
        try {
            token = FileUtils.readFileToString(tokenFile.toFile());
            DecodedJWT decodedJWT = jwtController.verifyToken(token, settings.getJWTIssuer());
            jupyterJWT = new JupyterJWT(project, user, DateUtils.date2LocalDateTime(decodedJWT.getExpiresAt()), pidAndPort);
            jupyterJWT.token = token;
            jupyterJWT.tokenFile = tokenFile;
            LOG.log(Level.FINE, "Successfully read existing JWT from local filesystem");
        } catch (IOException | JWTException | JWTDecodeException ex) {
            LOG.log(Level.FINE, "Could not recover Jupyter JWT from local filesystem, generating new!", ex);
            // JWT does not exist or it is not valid any longer
            // We should create a new one
            String[] audience = new String[] { "api" };
            LocalDateTime expirationDate = LocalDateTime.now().plus(settings.getJWTLifetimeMs(), ChronoUnit.MILLIS);
            String[] userRoles = usersController.getUserRoles(user).toArray(new String[1]);
            try {
                Map<String, Object> claims = new HashMap<>(3);
                claims.put(Constants.RENEWABLE, false);
                claims.put(Constants.EXPIRY_LEEWAY, settings.getJWTExpLeewaySec());
                claims.put(Constants.ROLES, userRoles);
                token = jwtController.createToken(settings.getJWTSigningKeyName(), false, settings.getJWTIssuer(), audience, DateUtils.localDateTime2Date(expirationDate), DateUtils.localDateTime2Date(DateUtils.getNow()), user.getUsername(), claims, SignatureAlgorithm.valueOf(settings.getJWTSignatureAlg()));
                jupyterJWT = new JupyterJWT(project, user, expirationDate, pidAndPort);
                jupyterJWT.token = token;
                jupyterJWT.tokenFile = tokenFile;
                jwtTokenWriter.writeToken(settings, jupyterJWT);
                LOG.log(Level.FINE, "Generated new Jupyter JWT cause could not recover existing");
            } catch (IOException recIOEx) {
                LOG.log(Level.WARNING, "Failed to recover Jupyter JWT for " + materializedJWT.getIdentifier() + ", generated new valid JWT but failed to write to local filesystem. Invalidating new token!" + " Continue recovering...");
                if (token != null) {
                    try {
                        jwtController.invalidate(token);
                    } catch (InvalidationException jwtInvEx) {
                    // NO-OP
                    }
                }
                failed2recover.add(materializedJWT);
                continue;
            } catch (GeneralSecurityException | JWTException jwtEx) {
                LOG.log(Level.WARNING, "Failed to recover Jupyter JWT for " + materializedJWT.getIdentifier() + ", tried to generate new token and it failed as well. Could not recover! Continue recovering...");
                // Did our best, it's good to know when you should give up
                failed2recover.add(materializedJWT);
                continue;
            }
        }
        addToken(jupyterJWT);
    }
    // Remove from the database entries that we failed to recover
    for (MaterializedJWT failedRecovery : failed2recover) {
        materializedJWTFacade.delete(failedRecovery.getIdentifier());
    }
    LOG.log(INFO, "Finished Jupyter JWT recovery");
}
Also used : Path(java.nio.file.Path) LocalDateTime(java.time.LocalDateTime) JWTException(io.hops.hopsworks.jwt.exception.JWTException) ArrayList(java.util.ArrayList) JupyterProject(io.hops.hopsworks.persistence.entity.jupyter.JupyterProject) Users(io.hops.hopsworks.persistence.entity.user.Users) IOException(java.io.IOException) MaterializedJWT(io.hops.hopsworks.persistence.entity.airflow.MaterializedJWT) JupyterProject(io.hops.hopsworks.persistence.entity.jupyter.JupyterProject) Project(io.hops.hopsworks.persistence.entity.project.Project) JWTDecodeException(com.auth0.jwt.exceptions.JWTDecodeException) JupyterSettings(io.hops.hopsworks.persistence.entity.jupyter.JupyterSettings) InvalidationException(io.hops.hopsworks.jwt.exception.InvalidationException) DecodedJWT(com.auth0.jwt.interfaces.DecodedJWT) Map(java.util.Map) HashMap(java.util.HashMap)

Example 3 with MaterializedJWT

use of io.hops.hopsworks.persistence.entity.airflow.MaterializedJWT in project hopsworks by logicalclocks.

the class AirflowManager method recover.

/**
 * Recover security material for Airflow after restart. Read all active material from the database.
 * Check if JWT exists in the local filesystem and it is valid.
 * If not try to create a new one. Finally, materialize X.509 for project specific user.
 */
private void recover() {
    LOG.log(Level.FINE, "Starting Airflow manager recovery");
    List<MaterializedJWT> failed2recover = new ArrayList<>();
    Project project = null;
    Users user = null;
    // Get last known state from storage
    for (MaterializedJWT material : materializedJWTFacade.findAll4Airflow()) {
        LOG.log(Level.FINEST, "Recovering material: " + material.getIdentifier().getProjectId() + " - " + material.getIdentifier().getUserId());
        project = projectFacade.find(material.getIdentifier().getProjectId());
        user = userFacade.find(material.getIdentifier().getUserId());
        if (project == null || user == null) {
            LOG.log(Level.WARNING, "Error while recovering Project with ID: " + material.getIdentifier().getProjectId() + " and User ID: " + material.getIdentifier().getUserId() + ". Project or user is null");
            failed2recover.add(material);
            continue;
        }
        Path tokenFile = Paths.get(getProjectSecretsDirectory(user.getUsername()).toString(), getTokenFileName(project.getName(), user.getUsername()));
        AirflowJWT airflowJWT;
        String token = null;
        String materialIdentifier = "Project: " + project.getName() + " - User: " + user.getUsername();
        try {
            // First try to read JWT from the filesystem. We expect most of the cases this will succeed.
            token = FileUtils.readFileToString(tokenFile.toFile(), Charset.defaultCharset());
            DecodedJWT decoded = jwtController.verifyToken(token, settings.getJWTIssuer());
            airflowJWT = new AirflowJWT(user.getUsername(), project.getId(), project.getName(), DateUtils.date2LocalDateTime(decoded.getExpiresAt()), user.getUid());
            airflowJWT.tokenFile = tokenFile;
            airflowJWT.token = token;
            LOG.log(Level.FINE, "Successfully read existing JWT from local filesystem for " + materialIdentifier);
        } catch (IOException | JWTException | JWTDecodeException ex) {
            // JWT does not exist in the filesystem or we cannot read them or it is not valid any longer
            // We will create a new one
            // TODO(Antonis): Not very good that audience is hardcoded, but it is not accessible from hopsworks-common
            String[] audience = new String[] { "api" };
            LocalDateTime expirationDate = DateUtils.getNow().plus(settings.getJWTLifetimeMs(), ChronoUnit.MILLIS);
            String[] roles = getUserRoles(user);
            try {
                LOG.log(Level.FINEST, "JWT for " + materialIdentifier + " does not exist in the local FS or it is not " + "valid any longer, creating new one...");
                Map<String, Object> claims = new HashMap<>(3);
                claims.put(Constants.RENEWABLE, false);
                claims.put(Constants.EXPIRY_LEEWAY, settings.getJWTExpLeewaySec());
                claims.put(Constants.ROLES, roles);
                token = jwtController.createToken(settings.getJWTSigningKeyName(), false, settings.getJWTIssuer(), audience, DateUtils.localDateTime2Date(expirationDate), DateUtils.localDateTime2Date(DateUtils.getNow()), user.getUsername(), claims, SignatureAlgorithm.valueOf(settings.getJWTSignatureAlg()));
                airflowJWT = new AirflowJWT(user.getUsername(), project.getId(), project.getName(), expirationDate, user.getUid());
                airflowJWT.tokenFile = tokenFile;
                airflowJWT.token = token;
                writeTokenToFile(airflowJWT);
                LOG.log(Level.FINE, "Created new JWT for " + materialIdentifier + " and flushed to local FS");
            } catch (IOException ex1) {
                // Managed to create token but failed to write
                LOG.log(Level.WARNING, "Could not write to local FS new JWT for recovered material " + materialIdentifier + ". We will invalidate it and won't renew it.", ex1);
                if (token != null) {
                    try {
                        LOG.log(Level.FINE, "Failed to write JWT for " + materialIdentifier + ". Invalidating it...");
                        jwtController.invalidate(token);
                    } catch (InvalidationException ex2) {
                    // Not much we can do about it
                    }
                }
                failed2recover.add(material);
                continue;
            } catch (GeneralSecurityException | JWTException ex1) {
                LOG.log(Level.WARNING, "Tried to recover JWT for " + materialIdentifier + " but we failed. Giving up... " + "JWT will not be available for Airflow DAGs", ex1);
                // Initial token is invalid and could not create new. Give up
                failed2recover.add(material);
                continue;
            }
        }
        // If everything went fine with JWT, proceed with X.509
        try {
            LOG.log(Level.FINEST, "Materializing X.509 for " + materialIdentifier);
            certificateMaterializer.materializeCertificatesLocalCustomDir(user.getUsername(), project.getName(), getProjectSecretsDirectory(user.getUsername()).toString());
            LOG.log(Level.FINE, "Materialized X.509 for " + materialIdentifier);
            airflowJWTs.add(airflowJWT);
        } catch (IOException ex) {
            LOG.log(Level.WARNING, "Could not materialize X.509 for " + materialIdentifier + " Invalidating JWT and deleting from FS. JWT and X.509 will not be available for Airflow DAGs.", ex);
            // Could not materialize X.509
            if (token != null) {
                try {
                    LOG.log(Level.FINE, "Failed to materialize X.509 for " + materialIdentifier + " Invalidating JWT and deleting it from local FS.");
                    jwtController.invalidate(token);
                    FileUtils.deleteDirectory(getProjectSecretsDirectory(user.getUsername()).toFile());
                } catch (InvalidationException | IOException ex1) {
                // Not much we can do about it
                }
            }
            failed2recover.add(material);
        }
    }
    // Remove failed material from persistent storage
    for (MaterializedJWT failed : failed2recover) {
        materializedJWTFacade.delete(failed.getIdentifier());
    }
}
Also used : Path(java.nio.file.Path) LocalDateTime(java.time.LocalDateTime) JWTException(io.hops.hopsworks.jwt.exception.JWTException) ArrayList(java.util.ArrayList) Users(io.hops.hopsworks.persistence.entity.user.Users) IOException(java.io.IOException) MaterializedJWT(io.hops.hopsworks.persistence.entity.airflow.MaterializedJWT) Project(io.hops.hopsworks.persistence.entity.project.Project) JWTDecodeException(com.auth0.jwt.exceptions.JWTDecodeException) InvalidationException(io.hops.hopsworks.jwt.exception.InvalidationException) DecodedJWT(com.auth0.jwt.interfaces.DecodedJWT) Map(java.util.Map) HashMap(java.util.HashMap)

Example 4 with MaterializedJWT

use of io.hops.hopsworks.persistence.entity.airflow.MaterializedJWT in project hopsworks by logicalclocks.

the class AirflowManager method cleanStaleSecurityMaterial.

private void cleanStaleSecurityMaterial() {
    Iterator<AirflowJWT> airflowJWTsIt = airflowJWTs.iterator();
    while (airflowJWTsIt.hasNext()) {
        AirflowJWT nextElement = airflowJWTsIt.next();
        try {
            MaterializedJWTID materialId = new MaterializedJWTID(nextElement.projectId, nextElement.uid, MaterializedJWTID.USAGE.AIRFLOW);
            MaterializedJWT airflowMaterial = materializedJWTFacade.findById(materialId);
            boolean shouldDelete = true;
            if (airflowMaterial != null) {
                List<AirflowDag> ownedDags = airflowDagFacade.filterByOwner(nextElement.username);
                for (AirflowDag dag : ownedDags) {
                    if (!dag.getPaused()) {
                        shouldDelete = false;
                        break;
                    }
                }
            }
            if (shouldDelete) {
                certificateMaterializer.removeCertificatesLocalCustomDir(nextElement.username, nextElement.projectName, getProjectSecretsDirectory(nextElement.username).toString());
                FileUtils.deleteQuietly(nextElement.tokenFile.toFile());
                airflowJWTsIt.remove();
                if (airflowMaterial != null) {
                    deleteAirflowMaterial(materialId);
                }
                deleteDirectoryIfEmpty(nextElement.tokenFile.getParent());
            }
        } catch (Exception ex) {
            // Catch everything here. We don't want the timer thread to get killed (expunging timer)
            // Be on the safe side and renew the token
            LOG.log(Level.WARNING, "Could not determine if token " + nextElement + " is stale. It will be renewed!", ex);
        }
    }
}
Also used : MaterializedJWTID(io.hops.hopsworks.persistence.entity.airflow.MaterializedJWTID) AirflowDag(io.hops.hopsworks.common.dao.airflow.AirflowDag) MaterializedJWT(io.hops.hopsworks.persistence.entity.airflow.MaterializedJWT) GeneralSecurityException(java.security.GeneralSecurityException) JWTDecodeException(com.auth0.jwt.exceptions.JWTDecodeException) SQLException(java.sql.SQLException) AirflowException(io.hops.hopsworks.exceptions.AirflowException) IOException(java.io.IOException) JWTException(io.hops.hopsworks.jwt.exception.JWTException) InvalidationException(io.hops.hopsworks.jwt.exception.InvalidationException)

Example 5 with MaterializedJWT

use of io.hops.hopsworks.persistence.entity.airflow.MaterializedJWT in project hopsworks by logicalclocks.

the class JupyterJWTManager method materializeJWT.

@Lock(LockType.WRITE)
@AccessTimeout(value = 2000)
public void materializeJWT(Users user, Project project, JupyterSettings jupyterSettings, String cid, Integer port, String[] audience) throws ServiceException {
    MaterializedJWTID materialID = new MaterializedJWTID(project.getId(), user.getUid(), MaterializedJWTID.USAGE.JUPYTER);
    if (!materializedJWTFacade.exists(materialID)) {
        LocalDateTime expirationDate = LocalDateTime.now().plus(settings.getJWTLifetimeMs(), ChronoUnit.MILLIS);
        JupyterJWT jupyterJWT = new JupyterJWT(project, user, expirationDate, new CidAndPort(cid, port));
        try {
            String[] roles = usersController.getUserRoles(user).toArray(new String[1]);
            MaterializedJWT materializedJWT = new MaterializedJWT(materialID);
            materializedJWTFacade.persist(materializedJWT);
            Map<String, Object> claims = new HashMap<>(3);
            claims.put(Constants.RENEWABLE, false);
            claims.put(Constants.EXPIRY_LEEWAY, settings.getJWTExpLeewaySec());
            claims.put(Constants.ROLES, roles);
            String token = jwtController.createToken(settings.getJWTSigningKeyName(), false, settings.getJWTIssuer(), audience, DateUtils.localDateTime2Date(expirationDate), DateUtils.localDateTime2Date(DateUtils.getNow()), user.getUsername(), claims, SignatureAlgorithm.valueOf(settings.getJWTSignatureAlg()));
            jupyterJWT.tokenFile = constructTokenFilePath(jupyterSettings);
            jupyterJWT.token = token;
            jwtTokenWriter.writeToken(settings, jupyterJWT);
            addToken(jupyterJWT);
        } catch (GeneralSecurityException | JWTException ex) {
            LOG.log(Level.SEVERE, "Error generating Jupyter JWT for " + jupyterJWT, ex);
            materializedJWTFacade.delete(materialID);
            throw new ServiceException(RESTCodes.ServiceErrorCode.JUPYTER_START_ERROR, Level.SEVERE, "Could not generate Jupyter JWT", ex.getMessage(), ex);
        } catch (IOException ex) {
            LOG.log(Level.SEVERE, "Error writing Jupyter JWT to file for " + jupyterJWT, ex);
            materializedJWTFacade.delete(materialID);
            try {
                jwtController.invalidate(jupyterJWT.token);
            } catch (InvalidationException invEx) {
                LOG.log(Level.FINE, "Could not invalidate Jupyter JWT after failure to write to file", ex);
            }
            throw new ServiceException(RESTCodes.ServiceErrorCode.JUPYTER_START_ERROR, Level.SEVERE, "Could not write Jupyter JWT to file", ex.getMessage(), ex);
        }
    }
}
Also used : LocalDateTime(java.time.LocalDateTime) HashMap(java.util.HashMap) JWTException(io.hops.hopsworks.jwt.exception.JWTException) GeneralSecurityException(java.security.GeneralSecurityException) IOException(java.io.IOException) MaterializedJWT(io.hops.hopsworks.persistence.entity.airflow.MaterializedJWT) MaterializedJWTID(io.hops.hopsworks.persistence.entity.airflow.MaterializedJWTID) ServiceException(io.hops.hopsworks.exceptions.ServiceException) InvalidationException(io.hops.hopsworks.jwt.exception.InvalidationException) AccessTimeout(javax.ejb.AccessTimeout) Lock(javax.ejb.Lock)

Aggregations

InvalidationException (io.hops.hopsworks.jwt.exception.InvalidationException)6 JWTException (io.hops.hopsworks.jwt.exception.JWTException)6 MaterializedJWT (io.hops.hopsworks.persistence.entity.airflow.MaterializedJWT)6 IOException (java.io.IOException)6 JWTDecodeException (com.auth0.jwt.exceptions.JWTDecodeException)4 MaterializedJWTID (io.hops.hopsworks.persistence.entity.airflow.MaterializedJWTID)4 GeneralSecurityException (java.security.GeneralSecurityException)4 LocalDateTime (java.time.LocalDateTime)4 HashMap (java.util.HashMap)4 Lock (javax.ejb.Lock)3 DecodedJWT (com.auth0.jwt.interfaces.DecodedJWT)2 AirflowException (io.hops.hopsworks.exceptions.AirflowException)2 ServiceException (io.hops.hopsworks.exceptions.ServiceException)2 Project (io.hops.hopsworks.persistence.entity.project.Project)2 Users (io.hops.hopsworks.persistence.entity.user.Users)2 Path (java.nio.file.Path)2 ArrayList (java.util.ArrayList)2 Map (java.util.Map)2 AccessTimeout (javax.ejb.AccessTimeout)2 AirflowDag (io.hops.hopsworks.common.dao.airflow.AirflowDag)1