Skip to content

Commit 06de8d9

Browse files
authored
Merge pull request #51 from marklogic/feature/CLD-278
CLD-278: Enable Intra-Cluster Network Traffic Encryption (XDQP)
2 parents 5a22f57 + f574323 commit 06de8d9

File tree

6 files changed

+118
-55
lines changed

6 files changed

+118
-55
lines changed

README.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
- [Adding and Removing Hosts from Clusters](#adding-and-removing-hosts-from-clusters)
2424
- [Adding Hosts](#adding-hosts)
2525
- [Removing Hosts](#removing-hosts)
26+
- [Enabling SSL over XDQP](#enabling-ssl-over-xdqp)
27+
- [Deploying a MarkLogic Cluster with Multiple Groups](#deploying-a-marklogic-cluster-with-multiple-groups)
2628
- [Access the MarkLogic Server](#access-the-marklogic-server)
2729
- [Service](#service)
2830
- [Get the ClusterIP Service Name](#get-the-clusterip-service-name)
@@ -294,8 +296,13 @@ kubectl logs pod/terminated-host-pod-name
294296
```
295297

296298
If you are permanently removing the host from the MarkLogic cluster, once the pod is terminated, follow standard MarkLogic administrative procedures using the administrative UI or APIs to remove the MarkLogic host from the cluster. Also, because Kubernetes keeps the Persistent Volume Claims and Persistent Volumes around until they are explicitly deleted, you must manually delete them using the Kubernetes APIs before attempting to scale the hosts in the StatefulSet back up again.
299+
### Enabling SSL over XDQP
297300

298-
## Deploying a MarkLogic Cluster with Multiple Groups
301+
To enable SSL over XDQP, set the `enableXdqpSsl` to true either in the values.yaml file or using the `--set` flag. All communications to and from hosts in the cluster will be secured. When this setting is on, default SSL certificates will be used for XDQP encryption.
302+
303+
Note: To enable other XDQP/SSL settings like `xdqp ssl allow sslv3`, `xdqp ssl allow tls`, `xdqp ssl ciphers`, use MarkLogic REST Management API. See the MarkLogic documentation [here](https://docs.marklogic.com/REST/management).
304+
305+
# Deploying a MarkLogic Cluster with Multiple Groups
299306

300307
To deploy a MarkLogic cluster with multiple groups (separate E and D nodes for example) the `bootstrapHostName` and `group.name` must be configured in values.yaml or set the values provided for these configurations using the `--set` flag while installing helm charts.
301308
For example, if you want to create a MarkLogic cluster with three nodes in a "dnode" group and two nodes in an "enode" group, start with the following helm command:
@@ -424,7 +431,10 @@ This table describes the list of available parameters for Helm Chart.
424431
| `nameOverride` | String to override the app name | `""` |
425432
| `fullnameOverride` | String to completely replace the generated name | `""` |
426433
| `auth.adminUsername` | Username for default MarkLogic Administrator | `admin` |
427-
| `auth.adminPassword` | Password for default MarkLogic Administrator | `admin` |
434+
| `auth.adminPassword` | Password for default MarkLogic Administrator | `admin`
435+
| `bootstrapHostName` | Host name of MarkLogic bootstrap host | `""`
436+
| `group.name` | group name for joining MarkLogic cluster | `Default` |
437+
| `group.enableXdqpSsl` | SSL encryption for XDQP | `true` |
428438
| `affinity` | Affinity property for pod assignment | `{}` |
429439
| `nodeSelector` | nodeSelector property for pod assignment | `{}` |
430440
| `persistence.enabled` | Enable MarkLogic data persistence using Persistence Volume Claim (PVC). If set to false, EmptyDir will be used | `true` |

charts/templates/configmap.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ data:
1414
MARKLOGIC_FQDN_SUFFIX: {{ include "marklogic.headlessURL" . }}
1515
MARKLOGIC_INIT: "true"
1616
MARKLOGIC_JOIN_CLUSTER: "true"
17+
MARKLOGIC_GROUP: {{ .Values.group.name }}
18+
XDQP_SSL_ENABLED: {{ quote .Values.group.enableXdqpSsl }}
1719
---
1820
{{- if .Values.logCollection.enabled }}
1921
apiVersion: v1

charts/templates/statefulset.yaml

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ spec:
4343
log "Error: [initContainer] Bootstrap host $MARKLOGIC_BOOTSTRAP_HOST not found, exiting Init container."
4444
exit 1
4545
fi
46-
GROUP_CFG_TEMPLATE='{"group-name":"%s"}'
47-
GROUP_CFG=$(printf "$GROUP_CFG_TEMPLATE" "$MARKLOGIC_GROUP")
46+
GROUP_CFG_TEMPLATE='{"group-name":"%s", "xdqp-ssl-enabled":"%s"}'
47+
GROUP_CFG=$(printf "$GROUP_CFG_TEMPLATE" "$MARKLOGIC_GROUP" "$XDQP_SSL_ENABLED")
4848
GROUP_RESP_CODE=`curl --anyauth -m 20 -s -o /dev/null -w "%{http_code}" -X GET http://${MARKLOGIC_BOOTSTRAP_HOST}:8002/manage/v2/groups/${MARKLOGIC_GROUP} --anyauth --user ${MARKLOGIC_ADMIN_USERNAME}:${MARKLOGIC_ADMIN_PASSWORD}`
4949
if [[ ${GROUP_RESP_CODE} -eq 200 ]]; then
5050
log "Info: [initContainer] Skipping creation of group $MARKLOGIC_GROUP as it already exists on the MarkLogic cluster."
@@ -67,8 +67,6 @@ spec:
6767
exit 1
6868
fi
6969
env:
70-
- name: MARKLOGIC_GROUP
71-
value: {{ .Values.group.name }}
7270
- name: MARKLOGIC_ADMIN_USERNAME
7371
valueFrom:
7472
secretKeyRef:
@@ -108,8 +106,6 @@ spec:
108106
secretKeyRef:
109107
name: {{ include "marklogic.fullname" . }}-admin
110108
key: password
111-
- name: MARKLOGIC_GROUP
112-
value: {{ .Values.group.name }}
113109
- name: POD_NAME
114110
valueFrom:
115111
fieldRef:
@@ -148,19 +144,19 @@ spec:
148144
echo "${TIMESTAMP} $@" > /proc/$pid/fd/1
149145
}
150146
log "Info: [poststart] Begin Poststart Hook Execution"
151-
if [[ $MARKLOGIC_GROUP == "Default" || $POD_NAME != *-0 ]]; then
152-
log "Info: [poststart] This is not a bootstrap host or group specified is Default"
147+
if [[ $POD_NAME != *-0 ]]; then
148+
log "Info: [poststart] Skipping group configuration."
153149
else
154150
while [ ! -f /var/opt/MarkLogic/ready ]; do
151+
log "[poststart] wait for marklogic server to be ready"
155152
sleep 5s
156153
done
157-
158-
GROUP_CFG_TEMPLATE='{"group-name":"%s"}'
159-
GROUP_CFG=$(printf "$GROUP_CFG_TEMPLATE" "$MARKLOGIC_GROUP")
160-
161-
log "Info: [poststart] Updating Default group on cluster"
162-
curl --anyauth -m 20 -s -X PUT -H "Content-type: application/json" -d "${GROUP_CFG}" http://${MARKLOGIC_BOOTSTRAP_HOST}:8002/manage/v2/groups/Default/properties --user ${MARKLOGIC_ADMIN_USERNAME}:${MARKLOGIC_ADMIN_PASSWORD}
163154
sleep 10s
155+
GROUP_CFG_TEMPLATE='{"group-name":"%s", "xdqp-ssl-enabled":"%s"}'
156+
GROUP_CFG=$(printf "$GROUP_CFG_TEMPLATE" "$MARKLOGIC_GROUP" "$XDQP_SSL_ENABLED")
157+
log "Info: [poststart] Updating group configuration: ${GROUP_CFG}"
158+
curl --anyauth -m 20 -X PUT -H "Content-type: application/json" -d "${GROUP_CFG}" http://${MARKLOGIC_BOOTSTRAP_HOST}:8002/manage/v2/groups/Default/properties --user ${MARKLOGIC_ADMIN_USERNAME}:${MARKLOGIC_ADMIN_PASSWORD}
159+
sleep 2s
164160
fi
165161
log "Info: [poststart] Poststart Hook Execution Completed"
166162
{{- end }}

charts/values.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,12 @@ terminationGracePeriod: 120
1010
group:
1111
# the group name of the current Marklogic Helm Deployment
1212
name: Default
13+
# xdqp encryption for intra cluster network traffic
14+
enableXdqpSsl: true
1315

1416
# The name of the host to join. If not provided, the deployment is a bootstrap host.
1517
bootstrapHostName: ""
1618

17-
1819
# Marklogic image parameters
1920
image:
2021
repository: marklogicdb/marklogic-db

test/e2e/install_test.go

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,26 @@ func TestHelmInstall(t *testing.T) {
113113
// the generated password should be able to access the manage endpoint
114114
assert.Equal(t, 200, response.StatusCode)
115115

116-
t.Log("====Verify no groups beyond enode were created/modified====")
116+
t.Log("====Verify xdqp-ssl-enabled is set to true by default")
117+
endpoint := fmt.Sprintf("http://%s/manage/v2/groups/Default/properties?format=json", tunnel8002.Endpoint())
118+
t.Logf(`Endpoint for group properties: %s`, endpoint)
119+
120+
request = digestAuth.NewRequest(username, password, "GET", endpoint, "")
121+
resp, err = request.Execute()
122+
if err != nil {
123+
t.Fatalf(err.Error())
124+
}
125+
defer resp.Body.Close()
126+
body, err = ioutil.ReadAll(resp.Body)
127+
if err != nil {
128+
t.Fatalf(err.Error())
129+
}
130+
131+
xdqpSSLEnabled := gjson.Get(string(body), `xdqp-ssl-enabled`)
132+
// verify xdqp-ssl-enabled is set to trues
133+
assert.Equal(t, true, xdqpSSLEnabled.Bool(), "xdqp-ssl-enabled should be set to true")
134+
135+
t.Log("====Verify no groups beyond default were created/modified====")
117136
groupStatusEndpoint := fmt.Sprintf("http://%s/manage/v2/groups?format=json", tunnel8002.Endpoint())
118137
groupStatus := digestAuth.NewRequest(username, password, "GET", groupStatusEndpoint, "")
119138
t.Logf(`groupStatusEndpoint: %s`, groupStatusEndpoint)
@@ -129,5 +148,4 @@ func TestHelmInstall(t *testing.T) {
129148
t.Errorf("Only one group should exist, instead %v groups exist", groupQuantityJSON.Num)
130149
}
131150

132-
t.Logf("Groups status response:\n" + string(body))
133151
}

test/e2e/separate_nodes_test.go

Lines changed: 72 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package e2e
33
import (
44
"fmt"
55
"io/ioutil"
6-
"net/http"
76
"os"
87
"path/filepath"
98
"strings"
@@ -13,15 +12,12 @@ import (
1312
"github.com/gruntwork-io/terratest/modules/helm"
1413
"github.com/gruntwork-io/terratest/modules/k8s"
1514
"github.com/gruntwork-io/terratest/modules/random"
15+
"github.com/stretchr/testify/assert"
1616
"github.com/tidwall/gjson"
1717
digestAuth "github.com/xinsnake/go-http-digest-auth-client"
1818
)
1919

2020
func TestSeparateEDnode(t *testing.T) {
21-
var resp *http.Response
22-
var body []byte
23-
var err error
24-
2521
username := "admin"
2622
password := "admin"
2723
imageRepo, repoPres := os.LookupEnv("dockerRepository")
@@ -60,6 +56,7 @@ func TestSeparateEDnode(t *testing.T) {
6056
"auth.adminUsername": username,
6157
"auth.adminPassword": password,
6258
"group.name": "dnode",
59+
"group.enableXdqpSsl": "true",
6360
"logCollection.enabled": "false",
6461
},
6562
}
@@ -86,14 +83,15 @@ func TestSeparateEDnode(t *testing.T) {
8683

8784
getHostsDR := digestAuth.NewRequest(username, password, "GET", hostsEndpoint, "")
8885

89-
if resp, err = getHostsDR.Execute(); err != nil {
86+
resp, err := getHostsDR.Execute()
87+
if err != nil {
9088
t.Fatalf(err.Error())
9189
}
9290
defer resp.Body.Close()
93-
if body, err = ioutil.ReadAll(resp.Body); err != nil {
91+
body, err := ioutil.ReadAll(resp.Body)
92+
if err != nil {
9493
t.Fatalf(err.Error())
9594
}
96-
t.Logf("Get hosts response:\n" + string(body))
9795

9896
bootstrapHostJSON := gjson.Get(string(body), `host-default-list.list-items.list-item.#(roleref="bootstrap").nameref`)
9997
t.Logf(`BootstrapHost: = %s`, bootstrapHostJSON)
@@ -102,6 +100,25 @@ func TestSeparateEDnode(t *testing.T) {
102100
t.Errorf("Bootstrap does not exists on cluster")
103101
}
104102

103+
t.Log("====Verify xdqp-ssl-enabled is set to true")
104+
endpoint := fmt.Sprintf("http://%s/manage/v2/groups/dnode/properties?format=json", tunnel.Endpoint())
105+
t.Logf(`Endpoint for group properties: %s`, endpoint)
106+
107+
request := digestAuth.NewRequest(username, password, "GET", endpoint, "")
108+
resp, err = request.Execute()
109+
if err != nil {
110+
t.Fatalf(err.Error())
111+
}
112+
defer resp.Body.Close()
113+
body, err = ioutil.ReadAll(resp.Body)
114+
if err != nil {
115+
t.Fatalf(err.Error())
116+
}
117+
118+
xdqpSSLEnabled := gjson.Get(string(body), `xdqp-ssl-enabled`)
119+
// verify xdqp-ssl-enabled is set to trues
120+
assert.Equal(t, true, xdqpSSLEnabled.Bool(), "xdqp-ssl-enabled should be set to true")
121+
105122
enodeOptions := &helm.Options{
106123
KubectlOptions: kubectlOptions,
107124
SetValues: map[string]string{
@@ -113,6 +130,7 @@ func TestSeparateEDnode(t *testing.T) {
113130
"auth.adminPassword": password,
114131
"group.name": "enode",
115132
"bootstrapHostName": bootstrapHostJSON.Str,
133+
"group.enableXdqpSsl": "false",
116134
"logCollection.enabled": "false",
117135
},
118136
}
@@ -122,6 +140,25 @@ func TestSeparateEDnode(t *testing.T) {
122140
// wait until the first enode pod is in Ready status
123141
k8s.WaitUntilPodAvailable(t, kubectlOptions, enodePodName0, 45, 20*time.Second)
124142

143+
t.Log("====Verify xdqp-ssl-enabled is set to false on Enode")
144+
endpoint = fmt.Sprintf("http://%s/manage/v2/groups/enode/properties?format=json", tunnel.Endpoint())
145+
t.Logf(`Endpoint for group properties: %s`, endpoint)
146+
147+
request = digestAuth.NewRequest(username, password, "GET", endpoint, "")
148+
resp, err = request.Execute()
149+
if err != nil {
150+
t.Fatalf(err.Error())
151+
}
152+
defer resp.Body.Close()
153+
body, err = ioutil.ReadAll(resp.Body)
154+
if err != nil {
155+
t.Fatalf(err.Error())
156+
}
157+
158+
xdqpSSLEnabled = gjson.Get(string(body), `xdqp-ssl-enabled`)
159+
// verify xdqp-ssl-enabled is set to false
160+
assert.Equal(t, false, xdqpSSLEnabled.Bool())
161+
125162
groupEndpoint := fmt.Sprintf("http://%s/manage/v2/groups", tunnel.Endpoint())
126163
t.Logf(`Endpoint: %s`, groupEndpoint)
127164

@@ -134,7 +171,6 @@ func TestSeparateEDnode(t *testing.T) {
134171
if body, err = ioutil.ReadAll(resp.Body); err != nil {
135172
t.Fatalf(err.Error())
136173
}
137-
t.Logf("Groups status response:\n" + string(body))
138174

139175
// verify groups dnode, enode exists on the cluster
140176
if !strings.Contains(string(body), "<nameref>dnode</nameref>") && !strings.Contains(string(body), "<nameref>enode</nameref>") {
@@ -149,12 +185,14 @@ func TestSeparateEDnode(t *testing.T) {
149185

150186
getEnodeDR := digestAuth.NewRequest(username, password, "GET", enodeEndpoint, "")
151187

152-
if resp, err = getEnodeDR.Execute(); err != nil {
188+
resp, err = getEnodeDR.Execute()
189+
if err != nil {
153190
t.Fatalf(err.Error())
154191
}
155192
defer resp.Body.Close()
156193

157-
if body, err = ioutil.ReadAll(resp.Body); err != nil {
194+
body, err = ioutil.ReadAll(resp.Body)
195+
if err != nil {
158196
t.Fatalf(err.Error())
159197
}
160198
t.Logf("Get enode group response:\n" + string(body))
@@ -169,10 +207,6 @@ func TestSeparateEDnode(t *testing.T) {
169207
}
170208

171209
func TestIncorrectBootsrapHostname(t *testing.T) {
172-
var resp *http.Response
173-
var body []byte
174-
var err error
175-
176210
username := "admin"
177211
password := "admin"
178212
imageRepo, repoPres := os.LookupEnv("dockerRepository")
@@ -184,7 +218,7 @@ func TestIncorrectBootsrapHostname(t *testing.T) {
184218
dnodePodName := dnodeReleaseName + "-marklogic-0"
185219

186220
// Incorrect boostrap hostname for negative test
187-
bootstrapHost := "Incorrect Host Name"
221+
incorrectBootstrapHost := "Incorrect Host Name"
188222

189223
// Path to the helm chart we will test
190224
helmChartPath, e := filepath.Abs("../../charts")
@@ -240,19 +274,19 @@ func TestIncorrectBootsrapHostname(t *testing.T) {
240274
t.Logf(`Endpoint: %s`, hostsEndpoint)
241275

242276
getHostsRequest := digestAuth.NewRequest(username, password, "GET", hostsEndpoint, "")
243-
244-
if resp, err = getHostsRequest.Execute(); err != nil {
277+
resp, err := getHostsRequest.Execute()
278+
if err != nil {
245279
t.Fatalf(err.Error())
246280
}
247281

248282
defer resp.Body.Close()
249283

250-
if body, err = ioutil.ReadAll(resp.Body); err != nil {
284+
body, err := ioutil.ReadAll(resp.Body)
285+
if err != nil {
251286
t.Fatalf(err.Error())
252287
}
253288

254-
t.Logf("Response:\n" + string(body))
255-
t.Logf(`BootstrapHost: = %s`, bootstrapHost)
289+
t.Logf(`BootstrapHost: = %s`, incorrectBootstrapHost)
256290

257291
// Helm options for enode creation
258292
enodeOptions := &helm.Options{
@@ -265,7 +299,7 @@ func TestIncorrectBootsrapHostname(t *testing.T) {
265299
"auth.adminUsername": username,
266300
"auth.adminPassword": password,
267301
"group.name": "enode",
268-
"bootstrapHostName": bootstrapHost,
302+
"bootstrapHostName": incorrectBootstrapHost,
269303
"logCollection.enabled": "false",
270304
},
271305
}
@@ -276,32 +310,34 @@ func TestIncorrectBootsrapHostname(t *testing.T) {
276310
// Give pod time to fail before checking if it did
277311
time.Sleep(20 * time.Second)
278312

313+
totalHostsJSON := gjson.Get(string(body), "host-default-list.list-items.list-count.value")
314+
315+
// Total hosts be one as second host should have failed to create
316+
if totalHostsJSON.Num != 1 {
317+
t.Errorf("Wrong number of hosts: %v instead of 1", totalHostsJSON.Num)
318+
}
319+
279320
// Verify clustering failed given incorrect hostname
280321
clusterStatusEndpoint := fmt.Sprintf("http://%s/manage/v2?view=status", tunnel.Endpoint())
281322
clusterStatus := digestAuth.NewRequest(username, password, "GET", clusterStatusEndpoint, "")
282323
t.Logf(`clusterStatusEndpoint: %s`, clusterStatusEndpoint)
283-
if resp, err = clusterStatus.Execute(); err != nil {
324+
resp, err = clusterStatus.Execute()
325+
if err != nil {
284326
t.Fatalf(err.Error())
285327
}
286-
totalHostsJSON := gjson.Get(string(body), "host-default-list.list-items.list-count.value")
287-
// Total hosts be one as second host should have failed to create
288-
if totalHostsJSON.Num != 1 {
289-
t.Errorf("Wrong number of hosts: %v instead of 1", totalHostsJSON.Num)
328+
_, err = ioutil.ReadAll(resp.Body)
329+
if err != nil {
330+
t.Fatalf(err.Error())
290331
}
291-
t.Logf("\nCluster Status Response:\n\n" + string(body))
292332

293333
// Verify enode group creation failed given incorrect hostname
294334
enodeGroupStatusEndpoint := fmt.Sprintf("http://%s/manage/v2/groups/enode", tunnel.Endpoint())
295335
groupStatus := digestAuth.NewRequest(username, password, "GET", enodeGroupStatusEndpoint, "")
296336
t.Logf(`enodeGroupStatusEndpoint: %s`, enodeGroupStatusEndpoint)
297-
if resp, err = groupStatus.Execute(); err != nil {
337+
resp, err = groupStatus.Execute()
338+
if err != nil {
298339
t.Fatalf(err.Error())
299340
}
300-
if body, err = ioutil.ReadAll(resp.Body); err != nil {
301-
t.Fatalf(err.Error())
302-
}
303-
if !strings.Contains(string(body), "404") {
304-
t.Errorf("Enode group should not exist")
305-
}
306-
t.Logf("Enode group status response:\n" + string(body))
341+
// the request for enode should be 404
342+
assert.Equal(t, 404, resp.StatusCode)
307343
}

0 commit comments

Comments
 (0)