Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,68 @@ haproxy:
> [!WARNING]
> Please note, by setting the haproxy service type to LoadBalancer, the MarkLogic endpoint is exposed to the public internet. Because of this, networkPolicy should be set to limit the sources that can visit MarkLogic.

## Deployment with TLS Enabled
The MarkLogic Kubernetes Operator supports installing MarkLogic with HTTPS enabled on the default app servers. The default app servers are App-Services (8000), Admin (8001), and Manage (8002)
### Choose the type of certificate
Two types of certificates are supported: standard certificates and temporary certificates.
* Temporary Certificates - A temporary certificate is ideal for development purposes. When using a temporary certificate for MarkLogic App Servers, a signed certificate does not need to be supplied. The certificate will be generated automatically.
* Standard Certificates - A standard certificate is issued by a trusted Certificate Authority (CA) for a specific domain (host name for MarkLogic server). A standard certificate is strongly recommended for production environments. Support is provided for both named certificates and wildcard certificates.
+ Named Certificate - Each host must possess a designated certificate with a matching common name (CN).
+ Wildcard Certificate - A single wildcard certificate can be used for all hosts within a cluster.

### Configure a MarkLogic cluster with a temporary certificate
To configure a temporary certificate, simply add the following option to CR yaml file:
```
tls:
enableOnDefaultAppServers: true
```

### Configure a MarkLogic cluster with a standard certificate
To configure a MarkLogic cluster with a standard certificate, follow these steps:
1. Obtain a certificate with a common name matching the hostname of the MarkLogic host. The certificate must be signed by a trusted Certificate Authority (CA). Either a publicly rooted CA or a private CA can be used. This example uses a private CA and a 2-node cluster.
2. Use this script to generate a self-signed CA certificate with openSSL. The script will create ca-private-key.pem as the CA key and cacert.pem as the private CA certificate:
```
# Generate private key for CA
openssl genrsa -out ca-private-key.pem 2048

# Generate the self-signed CA certificate
openssl req -new -x509 -days 3650 -key ca-private-key.pem -out cacert.pem
```
3. Use the script below to generate a private key and CSR for the marklogic-0 pod. After running the script, tls.key is generated as a private key and a host certificate for the marklogic-0 pod.
>Note: The filename for the private key must be tls.key and the filename for host certificate must be tls.crt.
* If the release name is "marklogic", then the host name for the marklogic-0 pod will be "marklogic-0.marklogic.default.svc.cluster.local".
* The host name for the marklogic-1 pod will be "marklogic-1.marklogic.default.svc.cluster.local".
```
# Create private key
openssl genpkey -algorithm RSA -out tls.key

# Create CSR for marklogic-0
# Use marklogic-0.marklogic.default.svc.cluster.local as Common Name(CN) for CSR
openssl req -new -key tls.key -out tls.csr

# Sign CSR with private CA
openssl x509 -req -CA cacert.pem -CAkey ca-private-key.pem -in tls.csr -out tls.crt -days 365
```
4. Use this script below to generate secrets for the host certificate and the CA certificate. Repeat these steps to generate the certificate for the marklogic-1 host and create the secret marklogic-1-cert. After running the script, secretes are created for marklogic-0 and marklogic-1. One secret is also created for the private CA certificate.
```
# Generate Secret for marklogic-0 host certificate
kubectl create secret generic marklogic-0-cert --from-file=tls.crt --from-file=tls.key

# Generate Secret for private CA certificate
kubectl create secret generic ca-cert --from-file=cacert.pem
```
1. Once the certificate is created within Kubernetes secrets, add the following section to the CR yaml file and follow the instructions outlined in Install the Operator.
```
tls:
enableOnDefaultAppServers: true
certSecretNames:
- "marklogic-0-cert"
- "marklogic-1-cert"
caSecretName: "ca-cert"
```



## Known Issues and Limitations

1. The latest released version of fluent/fluent-bit:3.1.1 has known high and critical security vulnerabilities. If you decide to enable the log collection feature, choose and deploy the fluent-bit or an alternate image with no vulnerabilities as per your requirements.
Expand Down
33 changes: 20 additions & 13 deletions api/v1alpha1/marklogiccluster_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,23 +47,22 @@ type MarklogicClusterSpec struct {
TerminationGracePeriodSeconds *int64 `json:"terminationGracePeriodSeconds,omitempty"`
// +kubebuilder:validation:Enum=OnDelete;RollingUpdate
// +kubebuilder:default:="OnDelete"
UpdateStrategy appsv1.StatefulSetUpdateStrategyType `json:"updateStrategy,omitempty"`
NetworkPolicy NetworkPolicy `json:"networkPolicy,omitempty"`
PodSecurityContext *corev1.PodSecurityContext `json:"podSecurityContext,omitempty"`
ContainerSecurityContext *corev1.SecurityContext `json:"securityContext,omitempty"`

Affinity *corev1.Affinity `json:"affinity,omitempty"`
NodeSelector map[string]string `json:"nodeSelector,omitempty"`
TopologySpreadConstraints []corev1.TopologySpreadConstraint `json:"topologySpreadConstraints,omitempty"`
PriorityClassName string `json:"priorityClassName,omitempty"`
License *License `json:"license,omitempty"`
EnableConverters bool `json:"enableConverters,omitempty"`
UpdateStrategy appsv1.StatefulSetUpdateStrategyType `json:"updateStrategy,omitempty"`
NetworkPolicy NetworkPolicy `json:"networkPolicy,omitempty"`
PodSecurityContext *corev1.PodSecurityContext `json:"podSecurityContext,omitempty"`
ContainerSecurityContext *corev1.SecurityContext `json:"securityContext,omitempty"`
Affinity *corev1.Affinity `json:"affinity,omitempty"`
NodeSelector map[string]string `json:"nodeSelector,omitempty"`
TopologySpreadConstraints []corev1.TopologySpreadConstraint `json:"topologySpreadConstraints,omitempty"`
PriorityClassName string `json:"priorityClassName,omitempty"`
License *License `json:"license,omitempty"`
EnableConverters bool `json:"enableConverters,omitempty"`
// +kubebuilder:default:={enabled: false, mountPath: "/dev/hugepages"}
HugePages *HugePages `json:"hugePages,omitempty"`
// +kubebuilder:default:={enabled: false, image: "fluent/fluent-bit:3.1.1", resources: {requests: {cpu: "100m", memory: "200Mi"}, limits: {cpu: "200m", memory: "500Mi"}}, files: {errorLogs: true, accessLogs: true, requestLogs: true}, outputs: "stdout"}
LogCollection *LogCollection `json:"logCollection,omitempty"`

HAProxy HAProxy `json:"haproxy,omitempty"`
HAProxy HAProxy `json:"haproxy,omitempty"`
Tls *Tls `json:"tls,omitempty"`

MarkLogicGroups []*MarklogicGroups `json:"markLogicGroups,omitempty"`
}
Expand All @@ -84,6 +83,14 @@ type MarklogicGroups struct {
LogCollection *LogCollection `json:"logCollection,omitempty"`
HAProxy *HAProxy `json:"haproxy,omitempty"`
IsBootstrap bool `json:"isBootstrap,omitempty"`
Tls *Tls `json:"tls,omitempty"`
}

type Tls struct {
// +kubebuilder:default:=false
EnableOnDefaultAppServers bool `json:"enableOnDefaultAppServers,omitempty"`
CertSecretNames []string `json:"certSecretNames,omitempty"`
CaSecretName string `json:"caSecretName,omitempty"`
}

// MarklogicClusterStatus defines the observed state of MarklogicCluster
Expand Down
2 changes: 2 additions & 0 deletions api/v1alpha1/marklogicgroup_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ type MarklogicGroupSpec struct {

Service Service `json:"service,omitempty"`
PathBasedRouting bool `json:"pathBasedRouting,omitempty"`

Tls *Tls `json:"tls,omitempty"`
}

// InternalState defines the observed state of MarklogicGroup
Expand Down
35 changes: 35 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions config/crd/bases/database.marklogic.com_marklogicclusters.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6259,6 +6259,18 @@ spec:
type: array
type: object
type: object
tls:
properties:
caSecretName:
type: string
certSecretNames:
items:
type: string
type: array
enableOnDefaultAppServers:
default: false
type: boolean
type: object
topologySpreadConstraints:
items:
description: TopologySpreadConstraint specifies how to spread
Expand Down Expand Up @@ -9195,6 +9207,18 @@ spec:
terminationGracePeriodSeconds:
format: int64
type: integer
tls:
properties:
caSecretName:
type: string
certSecretNames:
items:
type: string
type: array
enableOnDefaultAppServers:
default: false
type: boolean
type: object
topologySpreadConstraints:
items:
description: TopologySpreadConstraint specifies how to spread matching
Expand Down
12 changes: 12 additions & 0 deletions config/crd/bases/database.marklogic.com_marklogicgroups.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4044,6 +4044,18 @@ spec:
terminationGracePeriodSeconds:
format: int64
type: integer
tls:
properties:
caSecretName:
type: string
certSecretNames:
items:
type: string
type: array
enableOnDefaultAppServers:
default: false
type: boolean
type: object
topologySpreadConstraints:
items:
description: TopologySpreadConstraint specifies how to spread matching
Expand Down
8 changes: 5 additions & 3 deletions config/samples/marklogiccluster.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ spec:
# ports:
# - protocol: TCP
# port: 8000
tls:
enableOnDefaultAppServers: true
markLogicGroups:
- replicas: 1
name: dnode
Expand All @@ -111,6 +113,6 @@ spec:
# memory: "5Gi"
# cpu: "1"
isBootstrap: true
- replicas: 1
name: enode
isBootstrap: false
# - replicas: 1
# name: enode
# isBootstrap: false
11 changes: 11 additions & 0 deletions internal/controller/marklogiccluster_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,14 @@ var _ = Describe("MarklogicCluster Controller", func() {
},
},
},
Tls: &databasev1alpha1.Tls{
EnableOnDefaultAppServers: true,
CertSecretNames: []string{
"cert-secret-1",
"cert-secret-2",
},
CaSecretName: "ca-secret",
},
},
}
Expect(k8sClient.Create(ctx, mlCluster)).Should(Succeed())
Expand Down Expand Up @@ -140,6 +148,9 @@ var _ = Describe("MarklogicCluster Controller", func() {
Expect(clusterCR.Spec.NetworkPolicy.PodSelector).Should(Equal(metav1.LabelSelector{MatchLabels: map[string]string{"app.kubernetes.io/name": "marklogic", "app.kubernetes.io/instance": "dnode"}}))
Expect(clusterCR.Spec.NetworkPolicy.Ingress[0].From[0].PodSelector.MatchLabels).Should(Equal(map[string]string{"app.kubernetes.io/name": "marklogic", "app.kubernetes.io/instance": "dnode"}))
Expect(clusterCR.Spec.NetworkPolicy.Ingress[0].Ports[0].Port).Should(Equal(&intstr.IntOrString{IntVal: 8000}))
Expect(clusterCR.Spec.Tls.EnableOnDefaultAppServers).Should(Equal(true))
Expect(clusterCR.Spec.Tls.CertSecretNames).Should(ContainElements("cert-secret-1", "cert-secret-2"))
Expect(clusterCR.Spec.Tls.CaSecretName).Should(Equal("ca-secret"))
})
})
})
20 changes: 13 additions & 7 deletions pkg/k8sutil/haProxyHelper.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type HAProxyTemplateData struct {
NSName string
ClusterName string
SslCert string
sslEnabledServer bool
}

// generates frontend config for HAProxy depending on pathBasedRouting flag
Expand Down Expand Up @@ -114,13 +115,14 @@ backend marklogic-{{ .PortNumber}}-backend
}
for i := 0; i < groupReplicas; i++ {
data := &HAProxyTemplateData{
PortNumber: int(appServer.Port),
PodName: name,
Path: appServer.Path,
Index: i,
ServiceName: name,
NSName: cr.ObjectMeta.Namespace,
ClusterName: cr.Spec.ClusterDomain,
PortNumber: int(appServer.Port),
PodName: name,
Path: appServer.Path,
Index: i,
ServiceName: name,
NSName: cr.ObjectMeta.Namespace,
ClusterName: cr.Spec.ClusterDomain,
sslEnabledServer: cr.Spec.Tls != nil && cr.Spec.Tls.EnableOnDefaultAppServers,
}
result += getBackendServerConfigs(data)
}
Expand All @@ -134,6 +136,10 @@ backend marklogic-{{ .PortNumber}}-backend
func getBackendServerConfigs(data *HAProxyTemplateData) string {
backend := `
server {{.PodName}}-{{.PortNumber}}-{{.Index}} {{.PodName}}-{{.Index}}.{{.ServiceName}}.{{.NSName}}.svc.{{.ClusterName}}:{{.PortNumber}} resolvers dns init-addr none cookie {{.PodName}}-{{.PortNumber}}-{{.Index}}`
if data.sslEnabledServer {
backend += " ssl verify none"
}

return parseTemplateToString(backend, data)
}

Expand Down
8 changes: 8 additions & 0 deletions pkg/k8sutil/marklogicServer.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ type MarkLogicGroupParameters struct {
IsBootstrap bool
LogCollection *databasev1alpha1.LogCollection
IsPathBasedRouting bool
Tls *databasev1alpha1.Tls
}

type MarkLogicClusterParameters struct {
Expand All @@ -60,6 +61,7 @@ type MarkLogicClusterParameters struct {
LogCollection *databasev1alpha1.LogCollection
PodSecurityContext *corev1.PodSecurityContext
ContainerSecurityContext *corev1.SecurityContext
Tls *databasev1alpha1.Tls
TerminationGracePeriodSeconds *int64
}

Expand Down Expand Up @@ -112,6 +114,7 @@ func GenerateMarkLogicGroupDef(cr *databasev1alpha1.MarklogicCluster, index int,
PodSecurityContext: params.PodSecurityContext,
ContainerSecurityContext: params.ContainerSecurityContext,
PathBasedRouting: params.IsPathBasedRouting,
Tls: params.Tls,
},
}
AddOwnerRefToObject(MarkLogicGroupDef, ownerDef)
Expand Down Expand Up @@ -198,6 +201,7 @@ func generateMarkLogicClusterParams(cr *databasev1alpha1.MarklogicCluster) *Mark
Auth: cr.Spec.Auth,
PodSecurityContext: cr.Spec.PodSecurityContext,
ContainerSecurityContext: cr.Spec.ContainerSecurityContext,
Tls: cr.Spec.Tls,
TerminationGracePeriodSeconds: cr.Spec.TerminationGracePeriodSeconds,
}

Expand Down Expand Up @@ -229,6 +233,7 @@ func generateMarkLogicGroupParams(cr *databasev1alpha1.MarklogicCluster, index i
IsBootstrap: cr.Spec.MarkLogicGroups[index].IsBootstrap,
LogCollection: clusterParams.LogCollection,
IsPathBasedRouting: cr.Spec.HAProxy.PathBasedRouting,
Tls: clusterParams.Tls,
}
if cr.Spec.MarkLogicGroups[index].Image != "" {
MarkLogicGroupParameters.Image = cr.Spec.MarkLogicGroups[index].Image
Expand Down Expand Up @@ -263,5 +268,8 @@ func generateMarkLogicGroupParams(cr *databasev1alpha1.MarklogicCluster, index i
if cr.Spec.MarkLogicGroups[index].LogCollection != nil {
MarkLogicGroupParameters.LogCollection = cr.Spec.MarkLogicGroups[index].LogCollection
}
if cr.Spec.MarkLogicGroups[index].Tls != nil {
MarkLogicGroupParameters.Tls = cr.Spec.MarkLogicGroups[index].Tls
}
return MarkLogicGroupParameters
}
2 changes: 1 addition & 1 deletion pkg/k8sutil/scripts/poststart-hook.sh
Original file line number Diff line number Diff line change
Expand Up @@ -492,7 +492,7 @@ function configure_tls {
cat <<'EOF' > defaultCertificateTemplate.json
{
"template-name": "defaultTemplate",
"template-description": "defaultTemplate",
"template-description": "Default certificate template created by MarkLogic Kubernetes Operator",
"key-type": "rsa",
"key-options": {
"key-length": "2048"
Expand Down
Loading