Skip to content
This repository was archived by the owner on Sep 16, 2024. It is now read-only.

Commit 417a08b

Browse files
committed
#282 Can now insert host certificates into a certificate template
1 parent cdef81d commit 417a08b

File tree

14 files changed

+344
-9
lines changed

14 files changed

+344
-9
lines changed

src/main/java/com/marklogic/appdeployer/command/CommandMapBuilder.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import com.marklogic.appdeployer.command.security.DeployAmpsCommand;
3535
import com.marklogic.appdeployer.command.security.DeployCertificateAuthoritiesCommand;
3636
import com.marklogic.appdeployer.command.security.DeployCertificateTemplatesCommand;
37+
import com.marklogic.appdeployer.command.security.InsertCertificateHostsTemplateCommand;
3738
import com.marklogic.appdeployer.command.security.DeployExternalSecurityCommand;
3839
import com.marklogic.appdeployer.command.security.DeployPrivilegesCommand;
3940
import com.marklogic.appdeployer.command.security.DeployProtectedCollectionsCommand;
@@ -69,6 +70,7 @@ public Map<String, List<Command>> buildCommandMap() {
6970
securityCommands.add(new DeployAmpsCommand());
7071
securityCommands.add(new DeployCertificateTemplatesCommand());
7172
securityCommands.add(new DeployCertificateAuthoritiesCommand());
73+
securityCommands.add(new InsertCertificateHostsTemplateCommand());
7274
securityCommands.add(new DeployExternalSecurityCommand());
7375
securityCommands.add(new DeployPrivilegesCommand());
7476
securityCommands.add(new DeployProtectedCollectionsCommand());

src/main/java/com/marklogic/appdeployer/command/SortOrderConstants.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ public abstract class SortOrderConstants {
55
public static Integer DEPLOY_PRIVILEGES = 5;
66
public static Integer DEPLOY_ROLES = 10;
77
public static Integer DEPLOY_USERS = 15;
8-
public static Integer DEPLOY_CERTIFICATE_TEMPLATES = 20;
9-
public static Integer GENERATE_TEMPORARY_CERTIFICATE = 25;
10-
public static Integer DEPLOY_CERTIFICATE_AUTHORITIES = 30;
8+
public static Integer DEPLOY_CERTIFICATE_AUTHORITIES = 20;
9+
public static Integer DEPLOY_CERTIFICATE_TEMPLATES = 24;
10+
public static Integer GENERATE_TEMPORARY_CERTIFICATE = 25;
11+
public static Integer INSERT_HOST_CERTIFICATES = 28;
12+
1113
public static Integer DEPLOY_EXTERNAL_SECURITY = 35;
1214
public static Integer DEPLOY_PROTECTED_COLLECTIONS = 40;
1315
public static Integer DEPLOY_MIMETYPES = 45;

src/main/java/com/marklogic/appdeployer/command/databases/DeployDatabaseCommand.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ public void undo(CommandContext context) {
141141

142142
/**
143143
* Creates and attaches sub-databases to a the specified database, making it a super-database.
144-
* Note: Sub-databases are expected to have a configuration files in databases/subdatabases/<super-database-name>
144+
* Note: Sub-databases are expected to have a configuration files in databases/subdatabases/super-database-name
145145
* @param dbMgr
146146
* @param context
147147
* @param superDatabaseName Name of the database the sub-databases are to be associated with
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package com.marklogic.appdeployer.command.security;
2+
3+
import java.io.File;
4+
import java.util.List;
5+
6+
import com.marklogic.appdeployer.ConfigDir;
7+
import com.marklogic.appdeployer.command.AbstractCommand;
8+
import com.marklogic.appdeployer.command.CommandContext;
9+
import com.marklogic.appdeployer.command.SortOrderConstants;
10+
import com.marklogic.mgmt.resource.security.CertificateTemplateManager;
11+
12+
/**
13+
* Inserts host certificates for each certificate template returned by the Manage API. Host certificates are inserted
14+
* via the endpoint at http://docs.marklogic.com/REST/POST/manage/v2/certificate-templates/[id-or-name]#insertHC .
15+
*
16+
* To allow for host certificates to be associated with a certificate template, this command expects to find host
17+
* certificates in a directory named "(configuration directory)/security/certificate-templates/host-certificates/(name of template)/".
18+
*
19+
* The public certificate file must be a PEM-formatted file with a file extension of ".crt". And the private key file must
20+
* be a PEM-formatted file with a file extension of ".key".
21+
*/
22+
public class InsertCertificateHostsTemplateCommand extends AbstractCommand {
23+
24+
private String publicCertificateFileExtension = ".crt";
25+
private String privateKeyFileExtension = ".key";
26+
27+
public InsertCertificateHostsTemplateCommand() {
28+
setExecuteSortOrder(SortOrderConstants.INSERT_HOST_CERTIFICATES);
29+
}
30+
31+
@Override
32+
public void execute(CommandContext context) {
33+
List<String> templateNames = new CertificateTemplateManager(context.getManageClient()).getAsXml().getListItemNameRefs();
34+
if (templateNames != null && !templateNames.isEmpty()) {
35+
if (logger.isInfoEnabled()) {
36+
logger.info("Looking for host certificates to insert for certificate templates: " + templateNames);
37+
}
38+
for (String templateName : templateNames) {
39+
insertHostCertificatesForTemplate(context, templateName);
40+
}
41+
}
42+
}
43+
44+
/**
45+
* Looks for host certificates for the given template name.
46+
*
47+
* @param context
48+
* @param templateName Name of the template the host certificates should be inserted into
49+
*/
50+
protected void insertHostCertificatesForTemplate(CommandContext context, String templateName) {
51+
for (ConfigDir configDir : context.getAppConfig().getConfigDirs()) {
52+
File hostCertDir = new File(configDir.getCertificateTemplatesDir() + File.separator + "host-certificates" + File.separator + templateName);
53+
logger.info(format("Looking for host certificate files ending in '%s' for template '%s' in: %s", publicCertificateFileExtension, templateName, hostCertDir.getAbsolutePath()));
54+
if (hostCertDir.exists()){
55+
for (File f : hostCertDir.listFiles()) {
56+
if (f.getName().endsWith(publicCertificateFileExtension)) {
57+
File privateKeyFile = determinePrivateKeyFile(f);
58+
if (privateKeyFile.exists()) {
59+
logger.info("Found public certificate file at: " + f.getAbsolutePath() + ", and found corresponding private key file at: " + privateKeyFile.getAbsolutePath());
60+
this.insertHostCertificate(context, templateName, f, privateKeyFile);
61+
} else {
62+
logger.warn("Did not find expected private key file at: " + privateKeyFile.getAbsolutePath() + "; will ignore " +
63+
"public certificate file found at: " + f.getAbsolutePath());
64+
}
65+
}
66+
}
67+
}
68+
}
69+
}
70+
71+
protected File determinePrivateKeyFile(File publicCertificateFile) {
72+
String path = publicCertificateFile.getAbsolutePath();
73+
return new File(path.substring(0, path.length() - publicCertificateFileExtension.length()) + privateKeyFileExtension);
74+
}
75+
76+
/**
77+
* @param context
78+
* @param templateName The name of the certificate template that the host certificate will be inserted into
79+
* @param publicCertFile
80+
* @param privateKeyFile
81+
*/
82+
protected void insertHostCertificate(CommandContext context, String templateName, File publicCertFile, File privateKeyFile) {
83+
CertificateTemplateManager mgr = new CertificateTemplateManager(context.getManageClient());
84+
if (!mgr.certificateExists(templateName)) {
85+
logger.info(format("Inserting host certificate for certificate template '%s'", templateName));
86+
String pubCertString = copyFileToString(publicCertFile);
87+
String privateKeyString = copyFileToString(privateKeyFile);
88+
mgr.insertHostCertificate(templateName, pubCertString, privateKeyString);
89+
logger.info(format("Inserted host certificate for certificate template '%s'", templateName));
90+
} else {
91+
logger.info(format("Host certificate already exists for certificate template '%s', so not inserting host certificate found at: %s",
92+
templateName, publicCertFile.getAbsolutePath()));
93+
}
94+
}
95+
96+
public void setPublicCertificateFileExtension(String publicCertificateFileExtension) {
97+
this.publicCertificateFileExtension = publicCertificateFileExtension;
98+
}
99+
100+
public void setPrivateKeyFileExtension(String privateKeyFileExtension) {
101+
this.privateKeyFileExtension = privateKeyFileExtension;
102+
}
103+
}

src/main/java/com/marklogic/appdeployer/export/security/UserExporter.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ protected String[] getExportMessages() {
4545
* allows the user to be immediately deployed, and the developer can then change the password to a real one at a
4646
* later date.
4747
*
48-
* @param resourceName
4948
* @param payload
5049
* @return
5150
*/

src/main/java/com/marklogic/mgmt/resource/security/CertificateTemplateManager.java

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package com.marklogic.mgmt.resource.security;
22

3+
import com.fasterxml.jackson.databind.node.ArrayNode;
34
import com.fasterxml.jackson.databind.node.ObjectNode;
45
import com.marklogic.mgmt.ManageClient;
56
import com.marklogic.mgmt.resource.AbstractResourceManager;
67
import com.marklogic.mgmt.util.ObjectMapperFactory;
78
import com.marklogic.rest.util.Fragment;
9+
810
import org.springframework.http.ResponseEntity;
911

1012
/**
@@ -72,10 +74,60 @@ public ResponseEntity<String> generateTemporaryCertificate(String templateIdOrNa
7274
json));
7375
}
7476
return postPayload(getManageClient(), getResourcePath(templateIdOrName), json);
75-
}
77+
}
78+
79+
/**
80+
* Inserts a host certificate for a given template.
81+
*
82+
* Even though service allows multiple inserts in one call, this only inserts one host cert at a time.
83+
*
84+
* Note that the docs as of ML 9.0-5 are not correct for this operation - this method submits the correct JSON
85+
* structure.
86+
*
87+
* @param templateIdOrName The template name to insert the host certificates
88+
* @param pubCert the Public PEM formatted certificate
89+
* @param privateKey the Private PEM formatted certificate
90+
*/
91+
public ResponseEntity<String> insertHostCertificate(String templateIdOrName, String pubCert, String privateKey) {
92+
ObjectNode command = ObjectMapperFactory.getObjectMapper().createObjectNode();
93+
command.put("operation", "insert-host-certificates");
94+
ArrayNode certs = ObjectMapperFactory.getObjectMapper().createArrayNode();
95+
96+
ObjectNode certificate = ObjectMapperFactory.getObjectMapper().createObjectNode();
97+
ObjectNode cert = ObjectMapperFactory.getObjectMapper().createObjectNode();
98+
99+
certificate.put("cert", pubCert);
100+
certificate.put("pkey", privateKey);
101+
cert.set("certificate", certificate);
102+
certs.add(cert);
103+
104+
command.set("certificates", certs);
105+
106+
String json = command.toString();
107+
if (logger.isInfoEnabled()) {
108+
// NOTE - should NOT print out private key - EVER
109+
logger.info(format("Inserting host certificate for template %s", templateIdOrName));
110+
}
111+
return postPayload(getManageClient(), getResourcePath(templateIdOrName), json);
112+
}
113+
114+
/**
115+
* Utility service to determine if certificates exist for a template.
116+
*
117+
* Used because ML9.0-5 (and prior) has bug for "needs-certificate" call
118+
*/
119+
public boolean certificateExists(String templateIdOrName) {
120+
Fragment response = getCertificatesForTemplate(templateIdOrName);
121+
if (logger.isDebugEnabled()) {
122+
logger.debug(format("Checking if %s template has certificates --> for template: %s", templateIdOrName, response.getPrettyXml()));
123+
}
124+
125+
return response.elementExists("/msec:certificate-list/msec:certificate");
126+
}
76127

77128
public Fragment getCertificatesForTemplate(String templateIdOrName) {
78129
ObjectNode node = ObjectMapperFactory.getObjectMapper().createObjectNode();
130+
// Note the docs in ML 9.0-5 have a typo - it's "certificates", not "certificate"
79131
node.put("operation", "get-certificates-for-template");
80132

81133
String json = node.toString();

src/main/java/com/marklogic/rest/util/Fragment.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,19 +29,21 @@ public Fragment(String xml, Namespace... namespaces) {
2929
try {
3030
internalDoc = new SAXBuilder().build(new StringReader(xml));
3131
List<Namespace> list = new ArrayList<Namespace>();
32+
list.add(Namespace.getNamespace("arp", "http://marklogic.com/manage/alert-rule/properties"));
3233
list.add(Namespace.getNamespace("c", "http://marklogic.com/manage/clusters"));
34+
list.add(Namespace.getNamespace("cert", "http://marklogic.com/xdmp/x509"));
3335
list.add(Namespace.getNamespace("db", "http://marklogic.com/manage/databases"));
3436
list.add(Namespace.getNamespace("es", "http://marklogic.com/entity-services"));
3537
list.add(Namespace.getNamespace("f", "http://marklogic.com/manage/forests"));
3638
list.add(Namespace.getNamespace("g", "http://marklogic.com/manage/groups"));
3739
list.add(Namespace.getNamespace("h", "http://marklogic.com/manage/hosts"));
3840
list.add(Namespace.getNamespace("m", "http://marklogic.com/manage"));
39-
list.add(Namespace.getNamespace("s", "http://marklogic.com/manage/servers"));
4041
list.add(Namespace.getNamespace("msec", "http://marklogic.com/manage/security"));
42+
list.add(Namespace.getNamespace("pki", "http://marklogic.com/xdmp/pki"));
43+
list.add(Namespace.getNamespace("req", "http://marklogic.com/manage/requests"));
44+
list.add(Namespace.getNamespace("s", "http://marklogic.com/manage/servers"));
4145
list.add(Namespace.getNamespace("sec", "http://marklogic.com/xdmp/security"));
42-
list.add(Namespace.getNamespace("arp", "http://marklogic.com/manage/alert-rule/properties"));
4346
list.add(Namespace.getNamespace("ts", "http://marklogic.com/manage/task-server"));
44-
list.add(Namespace.getNamespace("req", "http://marklogic.com/manage/requests"));
4547
list.add(Namespace.getNamespace("t", "http://marklogic.com/manage/tasks"));
4648
for (Namespace n : namespaces) {
4749
list.add(n);
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package com.marklogic.appdeployer.command.security;
2+
3+
import com.marklogic.appdeployer.AbstractAppDeployerTest;
4+
import com.marklogic.appdeployer.ConfigDir;
5+
import com.marklogic.mgmt.resource.security.CertificateTemplateManager;
6+
import com.marklogic.rest.util.Fragment;
7+
import org.junit.Test;
8+
9+
import java.io.File;
10+
import java.util.List;
11+
12+
13+
public class InsertHostCertificateTest extends AbstractAppDeployerTest {
14+
15+
private final static String TEMPLATE_NAME = "sample-app-certificate-template";
16+
17+
@Test
18+
public void test() {
19+
appConfig.setConfigDir(new ConfigDir(new File("src/test/resources/sample-app/host-certificates")));
20+
21+
initializeAppDeployer(
22+
new DeployCertificateAuthoritiesCommand(),
23+
new DeployCertificateTemplatesCommand(),
24+
new InsertCertificateHostsTemplateCommand());
25+
26+
CertificateTemplateManager mgr = new CertificateTemplateManager(manageClient);
27+
28+
try {
29+
deploySampleApp();
30+
verifyHostCertificateWasInserted(mgr);
31+
32+
// Make sure nothing breaks by deploying it again
33+
deploySampleApp();
34+
verifyHostCertificateWasInserted(mgr);
35+
} finally {
36+
/**
37+
* TODO Deleting certificate authorities in ML 9.0-5 via the Manage API doesn't appear to be working, so
38+
* the certificate authority that's created by this class is left over.
39+
*/
40+
undeploySampleApp();
41+
42+
List<String> templateNames = mgr.getAsXml().getListItemNameRefs();
43+
assertFalse(templateNames.contains(TEMPLATE_NAME));
44+
}
45+
}
46+
47+
private void verifyHostCertificateWasInserted(CertificateTemplateManager mgr) {
48+
List<String> templateNames = mgr.getAsXml().getListItemNameRefs();
49+
assertTrue(templateNames.contains(TEMPLATE_NAME));
50+
51+
Fragment xml = mgr.getCertificatesForTemplate(TEMPLATE_NAME);
52+
assertEquals("host1.marklogic.com", xml.getElementValue("/msec:certificate-list/msec:certificate/msec:host-name"));
53+
assertEquals("MarkLogicBogusCA", xml.getElementValue("/msec:certificate-list/msec:certificate/cert:cert/cert:issuer/cert:commonName"));
54+
}
55+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIETjCCAzagAwIBAgIJAOLo8uCav9wyMA0GCSqGSIb3DQEBBAUAMHUxEzARBgoJ
3+
kiaJk/IsZAEZFgNjb20xGTAXBgoJkiaJk/IsZAEZFgltYXJrbG9naWMxEjAQBgNV
4+
BAoTCU1hcmtMb2dpYzEUMBIGA1UECxMLYXBwZGVwbG95ZXIxGTAXBgNVBAMTEE1h
5+
cmtMb2dpY0JvZ3VzQ0EwHhcNMTgwNjE1MDAyNDUyWhcNMjEwNjE0MDAyNDUyWjB1
6+
MRMwEQYKCZImiZPyLGQBGRYDY29tMRkwFwYKCZImiZPyLGQBGRYJbWFya2xvZ2lj
7+
MRIwEAYDVQQKEwlNYXJrTG9naWMxFDASBgNVBAsTC2FwcGRlcGxveWVyMRkwFwYD
8+
VQQDExBNYXJrTG9naWNCb2d1c0NBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
9+
CgKCAQEAr7PJ0JNIomzSsfpJAJNNGzu5c+mFPWQCleDdcXfTc9gKJ36F/pxi6Gf4
10+
KNmFq3hiXXG1mZUw/TqWMHNOlAd/PcNGd8B3kLYBMQc3zRmgPpUz+KB2IQuvbf3f
11+
y98V7Ti/68vmlpVId6a9SPIIIpyE8g3m76BgfYchAv7HDRtSLa1GuZcLoWyFfHcU
12+
Ec/De4z5E6Icl095nsNBPCtb1EwgmToMeiHK7HJFDtwVLoZSNkkYCLC91CJv2pbM
13+
5xh+9AZDpNyO721VFoICAQ8Z4wUQ3YKy7wZIEpXTaqVcd0BK2nIzCfNRQdsg0yvS
14+
DNg2wTxQOWhzdRdy4qmk50pNG8orfwIDAQABo4HgMIHdMBIGA1UdEwEB/wQIMAYB
15+
Af8CAQAwHQYDVR0OBBYEFEe+Lt2Ht7lPuF3nfHPtEBFwGzG1MIGnBgNVHSMEgZ8w
16+
gZyAFEe+Lt2Ht7lPuF3nfHPtEBFwGzG1oXmkdzB1MRMwEQYKCZImiZPyLGQBGRYD
17+
Y29tMRkwFwYKCZImiZPyLGQBGRYJbWFya2xvZ2ljMRIwEAYDVQQKEwlNYXJrTG9n
18+
aWMxFDASBgNVBAsTC2FwcGRlcGxveWVyMRkwFwYDVQQDExBNYXJrTG9naWNCb2d1
19+
c0NBggkA4ujy4Jq/3DIwDQYJKoZIhvcNAQEEBQADggEBAFcuLBC1SNtAQWqWOQ8d
20+
xJFIHjTVLsVTjdhM90cqJwcyuvn6njDJOUvL1phNM982rF3xaeNsRJ6MPmOaZCf6
21+
qH4ttYpKXGG9kh4ZDO8Uv3nlGq+5LEkPhqeAjvTcWDkds50aWUDbdD7LPN6P4F0Z
22+
DSeWfcchWbvcFuv9/89MFZe1AY3jGs09DPN33IYnSspVU0fwChwthSzBbnGlcOk6
23+
W3lXf8thj5c60rV6VXD7pJG/KsEcuIlorwoV2/1N1PafjSi3GQUVdOhrY0YCSHkt
24+
uATrgrFzA0e44kPGPUVosv37TZe4SKAQgcmrDIhr3fMU1b4LOG+mJc+tcf+HEfMn
25+
6ZY=
26+
-----END CERTIFICATE-----
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
The host2.marklogic.com.crt file exists to verify that no error occurs when a corresponding
2+
key file cannot be found.

0 commit comments

Comments
 (0)