Skip to content

Commit

Permalink
[HOPSWORKS-2586] Add support for exporting certificates as PEM file (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
moritzmeister committed May 16, 2022
1 parent 0c1cb84 commit cd97302
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 3 deletions.
23 changes: 23 additions & 0 deletions hopsworks-IT/src/test/ruby/spec/projects_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,29 @@
expect_status(200)
end
end

context 'with authentication and project' do
before :all do
with_valid_project
end

it "should be able to download trust and keystore as well as pem format credentials" do
project_id = get_project.id
download_cert_endpoint = "#{ENV['HOPSWORKS_API']}/project/" + project_id.to_s + "/downloadCert"
data = 'password=Pass123'
headers = {"Content-Type" => 'application/x-www-form-urlencoded'}
json_result = post download_cert_endpoint, data, headers
parsed_json = JSON.parse(json_result)
expect_status_details(200)
expect(parsed_json["fileExtension"]).to eql("jks")
expect(parsed_json.key?("kStore")).to be true
expect(parsed_json.key?("tStore")).to be true
expect(parsed_json.key?("password")).to be true
expect(parsed_json.key?("caChain")).to be true
expect(parsed_json.key?("clientCert")).to be true
expect(parsed_json.key?("clientKey")).to be true
end
end
end
describe "#delete" do
context 'without authentication' do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -728,6 +728,8 @@ public Response downloadCerts(@PathParam("projectId") Integer id, @FormParam("pa
@Produces(MediaType.APPLICATION_JSON)
@AllowedProjectRoles({AllowedProjectRoles.DATA_SCIENTIST, AllowedProjectRoles.DATA_OWNER})
@ApiKeyRequired(acceptedScopes = {ApiScope.PROJECT}, allowedUserRoles = {"HOPS_ADMIN", "HOPS_USER"})
@JWTRequired(acceptedTokens = {Audience.API, Audience.JOB},
allowedUserRoles = {"HOPS_ADMIN", "HOPS_USER"})
public Response credentials(@PathParam("projectId") Integer id, @Context HttpServletRequest req,
@Context SecurityContext sc) throws ProjectException, DatasetException {
Users user = jWTHelper.getUserPrincipal(sc);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ public class AccessCredentialsDTO {
private String kStore;
private String tStore;
private String password;
private String caChain;
private String clientKey;
private String clientCert;

public AccessCredentialsDTO() {
}
Expand All @@ -32,11 +35,15 @@ public AccessCredentialsDTO(String fileExtension) {
this.fileExtension = fileExtension;
}

public AccessCredentialsDTO(String fileExtension, String kStore, String tStore, String password) {
public AccessCredentialsDTO(String fileExtension, String kStore, String tStore, String password, String caChain,
String clientCert, String clientKey) {
this.fileExtension = fileExtension;
this.kStore = kStore;
this.tStore = tStore;
this.password = password;
this.caChain = caChain;
this.clientCert = clientCert;
this.clientKey = clientKey;
}

public String getFileExtension() {
Expand Down Expand Up @@ -70,4 +77,28 @@ public String getPassword() {
public void setPassword(String password) {
this.password = password;
}

public String getCaChain() {
return caChain;
}

public void setCaChain(String caChain) {
this.caChain = caChain;
}

public String getClientKey() {
return clientKey;
}

public void setClientKey(String clientKey) {
this.clientKey = clientKey;
}

public String getClientCert() {
return clientCert;
}

public void setClientCert(String clientCert) {
this.clientCert = clientCert;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@
import io.hops.hopsworks.restutils.RESTCodes;
import io.hops.hopsworks.restutils.RESTException;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.WordUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.Path;
Expand All @@ -176,8 +177,15 @@
import javax.inject.Inject;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.sql.SQLException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
Expand All @@ -188,6 +196,7 @@
import java.util.Collections;
import java.util.Date;
import java.util.EnumSet;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
Expand Down Expand Up @@ -2672,15 +2681,46 @@ public void removeOpenSearch(Project project) throws OpenSearchException {
}

private AccessCredentialsDTO getAccessCredentials(Project project, Users user)
throws IOException, CryptoPasswordNotFoundException {
throws IOException, CryptoPasswordNotFoundException, CertificateException, NoSuchAlgorithmException,
KeyStoreException, UnrecoverableKeyException {
//Read certs from database and stream them out
certificateMaterializer.materializeCertificatesLocal(user.getUsername(), project.getName());
CertificateMaterializer.CryptoMaterial material = certificateMaterializer.getUserMaterial(user.getUsername(),
project.getName());

StringBuilder clientKey = new StringBuilder();
StringBuilder clientCert = new StringBuilder();

KeyStore jksKeyStore = KeyStore.getInstance("JKS");
jksKeyStore.load(new ByteArrayInputStream(material.getKeyStore().array()), material.getPassword());
Enumeration<String> aliases = jksKeyStore.aliases();
while (aliases.hasMoreElements()) {
String next = aliases.nextElement();

jksKeyStore.getKey(next, material.getPassword());
appendBytesToPemStr(clientKey, jksKeyStore.getKey(next, material.getPassword()).getEncoded(), "PRIVATE KEY");

for (Certificate certificate : jksKeyStore.getCertificateChain(next)) {
appendBytesToPemStr(clientCert, certificate.getEncoded(), "CERTIFICATE");
}
}

StringBuilder caChain = new StringBuilder();
KeyStore jksTrustStore = KeyStore.getInstance("JKS");
jksTrustStore.load(new ByteArrayInputStream(material.getTrustStore().array()), material.getPassword());
aliases = jksTrustStore.aliases();
while (aliases.hasMoreElements()) {
String next = aliases.nextElement();

Certificate cert = jksTrustStore.getCertificate(next);
appendBytesToPemStr(caChain, cert.getEncoded(), "CERTIFICATE");
}

String keyStore = Base64.encodeBase64String(material.getKeyStore().array());
String trustStore = Base64.encodeBase64String(material.getTrustStore().array());
String certPwd = new String(material.getPassword());
return new AccessCredentialsDTO("jks", keyStore, trustStore, certPwd);
return new AccessCredentialsDTO("jks", keyStore, trustStore, certPwd, caChain.toString(), clientCert.toString(),
clientKey.toString());
}

public AccessCredentialsDTO credentials(Integer projectId, Users user) throws ProjectException, DatasetException {
Expand All @@ -2696,6 +2736,12 @@ public AccessCredentialsDTO credentials(Integer projectId, Users user) throws Pr
}
}

private void appendBytesToPemStr(StringBuilder pemString, byte[] derBytes, String pemType) {
pemString.append("-----BEGIN ").append(pemType).append("-----\n")
.append(WordUtils.wrap(Base64.encodeBase64String(derBytes), 64, null, true)).append("\n")
.append("-----END ").append(pemType).append("-----\n");
}

/**
* Helper class to log force cleanup operations
*
Expand Down

0 comments on commit cd97302

Please sign in to comment.