blob: 9b917dbfac3b12b8fa572efc9d6ac931aece2fd5 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to you 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.apache.hadoop.hdds.scm.ha;
import org.apache.hadoop.hdds.conf.OzoneConfiguration;
import org.apache.hadoop.hdds.protocol.proto.HddsProtos.ScmNodeDetailsProto;
import org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos.SCMGetCertResponseProto;
import org.apache.hadoop.hdds.protocolPB.SCMSecurityProtocolClientSideTranslatorPB;
import org.apache.hadoop.hdds.scm.server.SCMCertStore;
import org.apache.hadoop.hdds.scm.server.SCMStorageConfig;
import org.apache.hadoop.hdds.security.x509.SecurityConfig;
import org.apache.hadoop.hdds.security.x509.certificate.authority.CertificateServer;
import org.apache.hadoop.hdds.security.x509.certificate.authority.DefaultCAServer;
import org.apache.hadoop.hdds.security.x509.certificate.authority.PKIProfiles.DefaultCAProfile;
import org.apache.hadoop.hdds.security.x509.certificate.client.CertificateClient;
import org.apache.hadoop.hdds.security.x509.certificate.client.CertificateClient.InitResponse;
import org.apache.hadoop.hdds.security.x509.certificate.client.SCMCertificateClient;
import org.apache.hadoop.hdds.security.x509.certificate.utils.CertificateCodec;
import org.apache.hadoop.hdds.security.x509.certificates.utils.CertificateSignRequest;
import org.apache.hadoop.hdds.utils.HddsServerUtil;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.security.KeyPair;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.concurrent.ExecutionException;
import static org.apache.hadoop.hdds.protocol.proto.HddsProtos.NodeType.SCM;
import static org.apache.hadoop.hdds.security.x509.certificate.authority.CertificateApprover.ApprovalType.KERBEROS_TRUSTED;
import static org.apache.hadoop.hdds.security.x509.certificates.utils.CertificateSignRequest.getEncodedString;
import static org.apache.hadoop.ozone.OzoneConsts.SCM_ROOT_CA_COMPONENT_NAME;
import static org.apache.hadoop.ozone.OzoneConsts.SCM_ROOT_CA_PREFIX;
import static org.apache.hadoop.ozone.OzoneConsts.SCM_SUB_CA_PREFIX;
public final class HASecurityUtils {
private HASecurityUtils() {
}
public static final Logger LOG =
LoggerFactory.getLogger(HASecurityUtils.class);
/**
* Initialize Security which generates public, private key pair and get SCM
* signed certificate and persist to local disk.
* @param scmStorageConfig
* @param fetchedScmId
* @param conf
* @param scmAddress
* @throws IOException
*/
public static void initializeSecurity(SCMStorageConfig scmStorageConfig,
String fetchedScmId, OzoneConfiguration conf,
InetSocketAddress scmAddress, boolean primaryscm)
throws IOException {
LOG.info("Initializing secure StorageContainerManager.");
CertificateClient certClient =
new SCMCertificateClient(new SecurityConfig(conf));
InitResponse response = certClient.init();
LOG.info("Init response: {}", response);
switch (response) {
case SUCCESS:
LOG.info("Initialization successful.");
break;
case GETCERT:
if (!primaryscm) {
getRootCASignedSCMCert(certClient, conf, fetchedScmId, scmStorageConfig,
scmAddress);
} else {
getPrimarySCMSelfSignedCert(certClient, conf, fetchedScmId,
scmStorageConfig, scmAddress);
}
LOG.info("Successfully stored SCM signed certificate.");
break;
case FAILURE:
LOG.error("SCM security initialization failed.");
throw new RuntimeException("OM security initialization failed.");
case RECOVER:
LOG.error("SCM security initialization failed. SCM certificate is " +
"missing.");
throw new RuntimeException("SCM security initialization failed.");
default:
LOG.error("SCM security initialization failed. Init response: {}",
response);
throw new RuntimeException("SCM security initialization failed.");
}
}
/**
* For bootstrapped SCM get sub-ca signed certificate and root CA
* certificate using scm security client and store it using certificate
* client.
*/
private static void getRootCASignedSCMCert(CertificateClient client,
OzoneConfiguration config, String fetchedSCMId,
SCMStorageConfig scmStorageConfig, InetSocketAddress scmAddress) {
try {
// Generate CSR.
PKCS10CertificationRequest csr = generateCSR(client, scmStorageConfig,
config, scmAddress, fetchedSCMId);
ScmNodeDetailsProto scmNodeDetailsProto =
ScmNodeDetailsProto.newBuilder()
.setClusterId(scmStorageConfig.getClusterID())
.setHostName(scmAddress.getHostName())
.setScmNodeId(fetchedSCMId).build();
// Create SCM security client.
SCMSecurityProtocolClientSideTranslatorPB secureScmClient =
HddsServerUtil.getScmSecurityClient(config);
// Get SCM sub CA cert.
SCMGetCertResponseProto response = secureScmClient.
getSCMCertChain(scmNodeDetailsProto, getEncodedString(csr));
String pemEncodedCert = response.getX509Certificate();
// Store SCM sub CA and root CA certificate.
if (response.hasX509CACertificate()) {
String pemEncodedRootCert = response.getX509CACertificate();
client.storeCertificate(pemEncodedRootCert, true, true);
client.storeCertificate(pemEncodedCert, true);
X509Certificate certificate =
CertificateCodec.getX509Certificate(pemEncodedCert);
persistSubCACertificate(config, client,
CertificateCodec.getCertificateHolder(certificate));
// Persist scm cert serial ID.
scmStorageConfig.setScmCertSerialId(certificate.getSerialNumber()
.toString());
} else {
throw new RuntimeException("Unable to retrieve SCM certificate chain");
}
} catch (IOException | CertificateException e) {
LOG.error("Error while fetching/storing SCM signed certificate.", e);
throw new RuntimeException(e);
}
}
/**
* For primary SCM get sub-ca signed certificate and root CA certificate by
* root CA certificate server and store it using certificate client.
*/
private static void getPrimarySCMSelfSignedCert(CertificateClient client,
OzoneConfiguration config, String fetchedSCMId,
SCMStorageConfig scmStorageConfig, InetSocketAddress scmAddress) {
try {
CertificateServer rootCAServer =
initializeRootCertificateServer(config, null, scmStorageConfig);
PKCS10CertificationRequest csr = generateCSR(client, scmStorageConfig,
config, scmAddress, fetchedSCMId);
X509CertificateHolder subSCMCertHolder = rootCAServer.
requestCertificate(csr, KERBEROS_TRUSTED, SCM).get();
X509CertificateHolder rootCACertificateHolder =
rootCAServer.getCACertificate();
String pemEncodedCert =
CertificateCodec.getPEMEncodedString(subSCMCertHolder);
String pemEncodedRootCert =
CertificateCodec.getPEMEncodedString(rootCACertificateHolder);
client.storeCertificate(pemEncodedRootCert, true, true);
client.storeCertificate(pemEncodedCert, true);
persistSubCACertificate(config, client, subSCMCertHolder);
// Persist scm cert serial ID.
scmStorageConfig.setScmCertSerialId(subSCMCertHolder.getSerialNumber()
.toString());
} catch (InterruptedException | ExecutionException| IOException |
CertificateException e) {
LOG.error("Error while fetching/storing SCM signed certificate.", e);
throw new RuntimeException(e);
}
}
/**
* This function creates/initializes a certificate server as needed.
* This function is idempotent, so calling this again and again after the
* server is initialized is not a problem.
*
* @param config
* @param scmCertStore
* @param scmStorageConfig
*/
public static CertificateServer initializeRootCertificateServer(
OzoneConfiguration config, SCMCertStore scmCertStore,
SCMStorageConfig scmStorageConfig)
throws IOException {
String subject = SCM_ROOT_CA_PREFIX +
InetAddress.getLocalHost().getHostName();
DefaultCAServer rootCAServer = new DefaultCAServer(subject,
scmStorageConfig.getClusterID(),
scmStorageConfig.getScmId(), scmCertStore, new DefaultCAProfile(),
SCM_ROOT_CA_COMPONENT_NAME);
rootCAServer.init(new SecurityConfig(config),
CertificateServer.CAType.SELF_SIGNED_CA);
return rootCAServer;
}
/**
* Generate CSR to obtain SCM sub CA certificate.
*/
private static PKCS10CertificationRequest generateCSR(
CertificateClient client, SCMStorageConfig scmStorageConfig,
OzoneConfiguration config, InetSocketAddress scmAddress,
String fetchedSCMId) throws IOException {
CertificateSignRequest.Builder builder = client.getCSRBuilder();
KeyPair keyPair = new KeyPair(client.getPublicKey(),
client.getPrivateKey());
// Get host name.
String hostname = scmAddress.getAddress().getHostName();
String subject = SCM_SUB_CA_PREFIX + hostname;
builder.setKey(keyPair)
.setConfiguration(config)
.setScmID(fetchedSCMId)
.setClusterID(scmStorageConfig.getClusterID())
.setSubject(subject);
LOG.info("Creating csr for SCM->hostName:{},scmId:{},clusterId:{}," +
"subject:{}", hostname, fetchedSCMId,
scmStorageConfig.getClusterID(), subject);
return builder.build();
}
/**
* Persists the sub SCM signed certificate to the location which can be
* read by sub CA Certificate server.
* @param config
* @param certificateClient
* @param certificateHolder
* @throws IOException
*/
private static void persistSubCACertificate(OzoneConfiguration config,
CertificateClient certificateClient,
X509CertificateHolder certificateHolder) throws IOException {
CertificateCodec certCodec =
new CertificateCodec(new SecurityConfig(config),
certificateClient.getComponentName());
certCodec.writeCertificate(certificateHolder);
}
}