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);
}
}
}
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");
}
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());
}
}
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);
}
}
}
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);
}
}
}
Aggregations