fapifta commented on code in PR #3982:
URL: https://github.com/apache/ozone/pull/3982#discussion_r1046150255
##########
hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java:
##########
@@ -1095,4 +1089,274 @@ public synchronized void close() throws IOException {
clientKeyStoresFactory.destroy();
}
}
+
+ /**
+ * Check how much time before certificate will enter expiry grace period.
+ * @return Duration, time before certificate enters the grace
+ * period defined by "hdds.x509.renew.grace.duration"
+ */
+ public Duration timeBeforeExpiryGracePeriod(String certId)
+ throws CertificateException {
+ X509Certificate cert = getCertificate(certId);
+ Duration gracePeriod = securityConfig.getRenewalGracePeriod();
+ Date expireDate = cert.getNotAfter();
+ LocalDateTime gracePeriodStart = expireDate.toInstant()
+ .atZone(ZoneId.systemDefault()).toLocalDateTime().minus(gracePeriod);
+ LocalDateTime currentTime = LocalDateTime.now();
+ if (gracePeriodStart.isBefore(currentTime)) {
+ // Cert is already in grace period time.
+ return Duration.ZERO;
+ } else {
+ return Duration.between(currentTime, gracePeriodStart);
+ }
+ }
+
+ public String renewAndStoreKeyAndCertificate(boolean force)
+ throws CertificateException {
+ if (isRenewing.compareAndSet(false, true)) {
+ try {
+ if (!force) {
+ synchronized (this) {
+ Preconditions.checkArgument(
+ timeBeforeExpiryGracePeriod(certSerialId).isZero());
+ }
+ }
+ String newKeyPath = securityConfig.getKeyLocation(component)
+ .toString() + HDDS_NEW_KEY_CERT_DIR_NAME_SUFFIX;
+ String newCertPath = securityConfig.getCertificateLocation(component)
+ .toString() + HDDS_NEW_KEY_CERT_DIR_NAME_SUFFIX;
+ File newKeyDir = new File(newKeyPath);
+ File newCertDir = new File(newCertPath);
+
+ try {
+ FileUtils.deleteDirectory(newKeyDir);
+ FileUtils.deleteDirectory(newCertDir);
+ } catch (IOException e) {
+ throw new CertificateException("Error while deleting " + newKeyPath +
+ " or " + newCertPath + " directories to cleanup certificate " +
+ " storage. ", e, RENEW_ERROR);
+ }
+
+ try {
+ Files.createDirectories(newKeyDir.toPath());
+ Files.createDirectories(newCertDir.toPath());
+ } catch (IOException e) {
+ throw new CertificateException("Error while creating " + newKeyPath +
+ " or " + newCertPath + " directories for certificate storage.",
+ e, RENEW_ERROR);
+ }
+
+ // cleanup backup directory
+ cleanBackupDir();
+
+ // Generate key
+ KeyCodec newKeyCodec = new KeyCodec(securityConfig,
newKeyDir.toPath());
+ KeyPair newKeyPair;
+ try {
+ newKeyPair = createKeyPair(newKeyCodec);
+ } catch (CertificateException e) {
+ throw new CertificateException("Error while creating new key pair.",
+ e, RENEW_ERROR);
+ }
+
+ // Get certificate signed
+ String dnCertSerialId;
+ try {
+ CertificateSignRequest.Builder csrBuilder =
getCSRBuilder(newKeyPair);
+ dnCertSerialId = signAndStoreCertificate(csrBuilder.build(),
+ Paths.get(newCertPath));
+ } catch (Exception e) {
+ throw new CertificateException("Error while signing and storing new"
+
+ " certificates.", e, RENEW_ERROR);
+ }
+
+ // switch Key and Certs directory on disk
+ File currentKeyDir = new File(
+ securityConfig.getKeyLocation(component).toString());
+ File currentCertDir = new File(
+ securityConfig.getCertificateLocation(component).toString());
+ File backupKeyDir = new File(
+ securityConfig.getKeyLocation(component).toString() +
+ HDDS_BACKUP_KEY_CERT_DIR_NAME_SUFFIX);
+ File backupCertDir = new File(
+ securityConfig.getCertificateLocation(component).toString() +
+ HDDS_BACKUP_KEY_CERT_DIR_NAME_SUFFIX);
+
+ if (!currentKeyDir.renameTo(backupKeyDir)) {
+ // Cannot rename current key dir to the backup dir
+ throw new CertificateException("Failed to rename " +
+ currentKeyDir.getAbsolutePath() +
+ " to " + backupKeyDir.getAbsolutePath() + " during " +
+ "certificate renew.", RENEW_ERROR);
+ }
+ if (!currentCertDir.renameTo(backupCertDir)) {
+ // Cannot rename current cert dir to the backup dir
+ rollbackDir(currentKeyDir, currentCertDir, newKeyDir, newCertDir,
+ backupKeyDir, backupCertDir, "step-1");
+ throw new CertificateException("Failed to rename " +
+ currentCertDir.getAbsolutePath() +
+ " to " + backupCertDir.getAbsolutePath() + " during " +
+ "certificate renew.", RENEW_ERROR);
+ }
+
+ if (!newKeyDir.renameTo(currentKeyDir)) {
+ // Cannot rename new dir as the current dir
+ String msg = "Failed to rename " + newKeyDir.getAbsolutePath() +
+ " to " + currentKeyDir.getAbsolutePath() +
+ " during certificate renew.";
+ // rollback
+ rollbackDir(currentKeyDir, currentCertDir, newKeyDir, newCertDir,
+ backupKeyDir, backupCertDir, "step-2");
+ throw new CertificateException(msg, RENEW_ERROR);
+ }
+
+ if (!newCertDir.renameTo(currentCertDir)) {
+ // Cannot rename new dir as the current dir
+ String msg = "Failed to rename " + newCertDir.getAbsolutePath() +
+ " to " + currentCertDir.getAbsolutePath() +
+ " during certificate renew.";
+ // rollback
+ rollbackDir(currentKeyDir, currentCertDir, newKeyDir, newCertDir,
+ backupKeyDir, backupCertDir, "step-2");
+ throw new CertificateException(msg, RENEW_ERROR);
+ }
+
+ // Delete backup dir on next DN startup
+ getLogger().info("Successful renew key and certificate." +
+ " New certificate {}.", dnCertSerialId);
+ return dnCertSerialId;
+ } finally {
+ isRenewing.set(false);
+ }
+ }
+
+ throw new CertificateException("A renewAndStoreKeyAndCert process" +
+ " is running already.", RENEW_ERROR);
+ }
+
+ private void rollbackDir(File currentKeyDir, File currentCertDir,
+ File newKeyDir, File newCertDir, File backupKeyDir, File backupCertDir,
+ String step) throws CertificateException {
+
+ if (step.equals("step-2")) {
+ // move current dir back as new dir
+ if (currentKeyDir.exists() && !newKeyDir.exists()) {
+ if (!currentKeyDir.renameTo(newKeyDir)) {
+ String msg = "Failed to rename " + currentKeyDir.getAbsolutePath() +
+ " back to " + newKeyDir.getAbsolutePath() +
+ " during rollback.";
+ // Need a manual recover process.
+ throw new CertificateException(msg, ROLLBACK_ERROR);
+ }
+ }
+ if (currentCertDir.exists() && !newCertDir.exists()) {
+ if (!currentCertDir.renameTo(newCertDir)) {
+ String msg = "Failed to rename " + currentCertDir.getAbsolutePath() +
+ " back to " + newCertDir.getAbsolutePath() +
+ " during rollback.";
+ // Need a manual recover process.
+ throw new CertificateException(msg, ROLLBACK_ERROR);
+ }
+ }
+ }
+
+ // move backup dir back as current dir
+ if (!currentKeyDir.exists() && backupKeyDir.exists()) {
+ if (!backupKeyDir.renameTo(currentKeyDir)) {
+ String msg = "Failed to rename " + backupKeyDir.getAbsolutePath() +
+ " back to " + currentKeyDir.getAbsolutePath() +
+ " during rollback.";
+ // Need a manual recover process.
+ throw new CertificateException(msg, ROLLBACK_ERROR);
+ }
+ }
+
+ if (!currentCertDir.exists() && backupCertDir.exists()) {
+ if (!backupCertDir.renameTo(currentCertDir)) {
+ String msg = "Failed to rename " + backupCertDir.getAbsolutePath() +
+ " back to " + currentCertDir.getAbsolutePath() +
+ " during rollback.";
+ // Need a manual recover process.
+ throw new CertificateException(msg, ROLLBACK_ERROR);
+ }
+ }
+ Preconditions.checkArgument(currentCertDir.exists());
+ Preconditions.checkArgument(currentKeyDir.exists());
+ }
+
+ /**
+ * Delete old backup key and cert directory.
+ */
+ private void cleanBackupDir() {
+ File backupKeyDir = new File(
+ securityConfig.getKeyLocation(component).toString() +
+ HDDS_BACKUP_KEY_CERT_DIR_NAME_SUFFIX);
+ File backupCertDir = new File(
+ securityConfig.getCertificateLocation(component).toString() +
+ HDDS_BACKUP_KEY_CERT_DIR_NAME_SUFFIX);
+ if (backupKeyDir.exists()) {
+ try {
+ FileUtils.deleteDirectory(backupKeyDir);
+ } catch (IOException e) {
+ getLogger().error("Error while deleting {} directories for " +
+ "certificate storage cleanup.", backupKeyDir, e);
+ }
+ }
+ if (backupCertDir.exists()) {
+ try {
+ FileUtils.deleteDirectory(backupCertDir);
+ } catch (IOException e) {
+ getLogger().error("Error while deleting {} directories for " +
+ "certificate storage cleanup.", backupCertDir, e);
+ }
+ }
+ }
+
+ public boolean isCertificateRenewed() {
+ return isRenewed.get();
+ }
+
+ public synchronized void reloadKeyAndCertificate(String newCertId) {
+ // reset current value
+ privateKey = null;
+ publicKey = null;
+ x509Certificate = null;
+ certSerialId = null;
+ caCertId = null;
+ rootCaCertId = null;
+
+ setCertificateId(newCertId);
+ // reload all new certs
+ loadAllCertificates();
+ isRenewed.set(true);
+ getLogger().info("Reset and reload key and all certificates.");
+ }
+
+ public SecurityConfig getSecurityConfig() {
+ return securityConfig;
+ }
+
+ public OzoneConfiguration getConfig() {
+ return config;
+ }
+
+ @Override
+ public String signAndStoreCertificate(PKCS10CertificationRequest request,
Review Comment:
I believe if we want to enforce implementation, we can make this abstract.
##########
hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java:
##########
@@ -1095,4 +1089,274 @@ public synchronized void close() throws IOException {
clientKeyStoresFactory.destroy();
}
}
+
+ /**
+ * Check how much time before certificate will enter expiry grace period.
+ * @return Duration, time before certificate enters the grace
+ * period defined by "hdds.x509.renew.grace.duration"
+ */
+ public Duration timeBeforeExpiryGracePeriod(String certId)
+ throws CertificateException {
+ X509Certificate cert = getCertificate(certId);
+ Duration gracePeriod = securityConfig.getRenewalGracePeriod();
+ Date expireDate = cert.getNotAfter();
+ LocalDateTime gracePeriodStart = expireDate.toInstant()
+ .atZone(ZoneId.systemDefault()).toLocalDateTime().minus(gracePeriod);
+ LocalDateTime currentTime = LocalDateTime.now();
+ if (gracePeriodStart.isBefore(currentTime)) {
+ // Cert is already in grace period time.
+ return Duration.ZERO;
+ } else {
+ return Duration.between(currentTime, gracePeriodStart);
+ }
+ }
+
+ public String renewAndStoreKeyAndCertificate(boolean force)
+ throws CertificateException {
+ if (isRenewing.compareAndSet(false, true)) {
+ try {
+ if (!force) {
+ synchronized (this) {
+ Preconditions.checkArgument(
+ timeBeforeExpiryGracePeriod(certSerialId).isZero());
+ }
+ }
+ String newKeyPath = securityConfig.getKeyLocation(component)
+ .toString() + HDDS_NEW_KEY_CERT_DIR_NAME_SUFFIX;
+ String newCertPath = securityConfig.getCertificateLocation(component)
+ .toString() + HDDS_NEW_KEY_CERT_DIR_NAME_SUFFIX;
+ File newKeyDir = new File(newKeyPath);
+ File newCertDir = new File(newCertPath);
+
+ try {
+ FileUtils.deleteDirectory(newKeyDir);
+ FileUtils.deleteDirectory(newCertDir);
+ } catch (IOException e) {
+ throw new CertificateException("Error while deleting " + newKeyPath +
+ " or " + newCertPath + " directories to cleanup certificate " +
+ " storage. ", e, RENEW_ERROR);
+ }
+
+ try {
+ Files.createDirectories(newKeyDir.toPath());
+ Files.createDirectories(newCertDir.toPath());
+ } catch (IOException e) {
+ throw new CertificateException("Error while creating " + newKeyPath +
+ " or " + newCertPath + " directories for certificate storage.",
+ e, RENEW_ERROR);
+ }
+
+ // cleanup backup directory
+ cleanBackupDir();
+
+ // Generate key
+ KeyCodec newKeyCodec = new KeyCodec(securityConfig,
newKeyDir.toPath());
+ KeyPair newKeyPair;
+ try {
+ newKeyPair = createKeyPair(newKeyCodec);
+ } catch (CertificateException e) {
+ throw new CertificateException("Error while creating new key pair.",
+ e, RENEW_ERROR);
+ }
+
+ // Get certificate signed
+ String dnCertSerialId;
+ try {
+ CertificateSignRequest.Builder csrBuilder =
getCSRBuilder(newKeyPair);
+ dnCertSerialId = signAndStoreCertificate(csrBuilder.build(),
+ Paths.get(newCertPath));
+ } catch (Exception e) {
+ throw new CertificateException("Error while signing and storing new"
+
+ " certificates.", e, RENEW_ERROR);
+ }
+
+ // switch Key and Certs directory on disk
+ File currentKeyDir = new File(
+ securityConfig.getKeyLocation(component).toString());
+ File currentCertDir = new File(
+ securityConfig.getCertificateLocation(component).toString());
+ File backupKeyDir = new File(
+ securityConfig.getKeyLocation(component).toString() +
+ HDDS_BACKUP_KEY_CERT_DIR_NAME_SUFFIX);
+ File backupCertDir = new File(
+ securityConfig.getCertificateLocation(component).toString() +
+ HDDS_BACKUP_KEY_CERT_DIR_NAME_SUFFIX);
+
+ if (!currentKeyDir.renameTo(backupKeyDir)) {
+ // Cannot rename current key dir to the backup dir
+ throw new CertificateException("Failed to rename " +
+ currentKeyDir.getAbsolutePath() +
+ " to " + backupKeyDir.getAbsolutePath() + " during " +
+ "certificate renew.", RENEW_ERROR);
+ }
+ if (!currentCertDir.renameTo(backupCertDir)) {
+ // Cannot rename current cert dir to the backup dir
+ rollbackDir(currentKeyDir, currentCertDir, newKeyDir, newCertDir,
+ backupKeyDir, backupCertDir, "step-1");
+ throw new CertificateException("Failed to rename " +
+ currentCertDir.getAbsolutePath() +
+ " to " + backupCertDir.getAbsolutePath() + " during " +
+ "certificate renew.", RENEW_ERROR);
+ }
+
+ if (!newKeyDir.renameTo(currentKeyDir)) {
+ // Cannot rename new dir as the current dir
+ String msg = "Failed to rename " + newKeyDir.getAbsolutePath() +
+ " to " + currentKeyDir.getAbsolutePath() +
+ " during certificate renew.";
+ // rollback
+ rollbackDir(currentKeyDir, currentCertDir, newKeyDir, newCertDir,
+ backupKeyDir, backupCertDir, "step-2");
+ throw new CertificateException(msg, RENEW_ERROR);
+ }
+
+ if (!newCertDir.renameTo(currentCertDir)) {
+ // Cannot rename new dir as the current dir
+ String msg = "Failed to rename " + newCertDir.getAbsolutePath() +
+ " to " + currentCertDir.getAbsolutePath() +
+ " during certificate renew.";
+ // rollback
+ rollbackDir(currentKeyDir, currentCertDir, newKeyDir, newCertDir,
+ backupKeyDir, backupCertDir, "step-2");
+ throw new CertificateException(msg, RENEW_ERROR);
+ }
+
+ // Delete backup dir on next DN startup
+ getLogger().info("Successful renew key and certificate." +
+ " New certificate {}.", dnCertSerialId);
+ return dnCertSerialId;
+ } finally {
+ isRenewing.set(false);
+ }
+ }
+
+ throw new CertificateException("A renewAndStoreKeyAndCert process" +
+ " is running already.", RENEW_ERROR);
+ }
+
+ private void rollbackDir(File currentKeyDir, File currentCertDir,
+ File newKeyDir, File newCertDir, File backupKeyDir, File backupCertDir,
+ String step) throws CertificateException {
+
+ if (step.equals("step-2")) {
+ // move current dir back as new dir
+ if (currentKeyDir.exists() && !newKeyDir.exists()) {
+ if (!currentKeyDir.renameTo(newKeyDir)) {
+ String msg = "Failed to rename " + currentKeyDir.getAbsolutePath() +
+ " back to " + newKeyDir.getAbsolutePath() +
+ " during rollback.";
+ // Need a manual recover process.
+ throw new CertificateException(msg, ROLLBACK_ERROR);
+ }
+ }
+ if (currentCertDir.exists() && !newCertDir.exists()) {
+ if (!currentCertDir.renameTo(newCertDir)) {
+ String msg = "Failed to rename " + currentCertDir.getAbsolutePath() +
+ " back to " + newCertDir.getAbsolutePath() +
+ " during rollback.";
+ // Need a manual recover process.
+ throw new CertificateException(msg, ROLLBACK_ERROR);
+ }
+ }
+ }
+
+ // move backup dir back as current dir
+ if (!currentKeyDir.exists() && backupKeyDir.exists()) {
+ if (!backupKeyDir.renameTo(currentKeyDir)) {
+ String msg = "Failed to rename " + backupKeyDir.getAbsolutePath() +
+ " back to " + currentKeyDir.getAbsolutePath() +
+ " during rollback.";
+ // Need a manual recover process.
+ throw new CertificateException(msg, ROLLBACK_ERROR);
+ }
+ }
+
+ if (!currentCertDir.exists() && backupCertDir.exists()) {
+ if (!backupCertDir.renameTo(currentCertDir)) {
+ String msg = "Failed to rename " + backupCertDir.getAbsolutePath() +
+ " back to " + currentCertDir.getAbsolutePath() +
+ " during rollback.";
+ // Need a manual recover process.
+ throw new CertificateException(msg, ROLLBACK_ERROR);
+ }
+ }
+ Preconditions.checkArgument(currentCertDir.exists());
+ Preconditions.checkArgument(currentKeyDir.exists());
+ }
+
+ /**
+ * Delete old backup key and cert directory.
+ */
+ private void cleanBackupDir() {
+ File backupKeyDir = new File(
+ securityConfig.getKeyLocation(component).toString() +
+ HDDS_BACKUP_KEY_CERT_DIR_NAME_SUFFIX);
+ File backupCertDir = new File(
+ securityConfig.getCertificateLocation(component).toString() +
+ HDDS_BACKUP_KEY_CERT_DIR_NAME_SUFFIX);
+ if (backupKeyDir.exists()) {
+ try {
+ FileUtils.deleteDirectory(backupKeyDir);
+ } catch (IOException e) {
+ getLogger().error("Error while deleting {} directories for " +
+ "certificate storage cleanup.", backupKeyDir, e);
+ }
+ }
+ if (backupCertDir.exists()) {
+ try {
+ FileUtils.deleteDirectory(backupCertDir);
+ } catch (IOException e) {
+ getLogger().error("Error while deleting {} directories for " +
+ "certificate storage cleanup.", backupCertDir, e);
+ }
+ }
+ }
+
+ public boolean isCertificateRenewed() {
+ return isRenewed.get();
+ }
+
+ public synchronized void reloadKeyAndCertificate(String newCertId) {
+ // reset current value
+ privateKey = null;
+ publicKey = null;
+ x509Certificate = null;
+ certSerialId = null;
+ caCertId = null;
+ rootCaCertId = null;
+
+ setCertificateId(newCertId);
+ // reload all new certs
+ loadAllCertificates();
+ isRenewed.set(true);
+ getLogger().info("Reset and reload key and all certificates.");
+ }
+
+ public SecurityConfig getSecurityConfig() {
+ return securityConfig;
+ }
+
+ public OzoneConfiguration getConfig() {
+ return config;
+ }
+
+ @Override
+ public String signAndStoreCertificate(PKCS10CertificationRequest request,
+ Path certPath) throws CertificateException {
+ throw new UnsupportedOperationException(
+ "Each child class should have its own implementation");
+ }
+
+ public String signAndStoreCertificate(PKCS10CertificationRequest request)
+ throws CertificateException {
+ return signAndStoreCertificate(request,
+ getSecurityConfig().getCertificateLocation(getComponentName()));
+ }
+
+ @Override
+ public CertificateSignRequest.Builder getCSRBuilder(KeyPair keyPair)
+ throws IOException {
Review Comment:
I believe if we want to enforce implementation, we can make this abstract.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]