Securing and Exposing Qdrant Private Cloud Clusters
Qdrant has no access to a Private Cloud installation, so you own its security. This page covers how to reach a cluster, who can reach it, how it authenticates clients, and how to verify the software you run. For the split of duties across deployment models, see Shared Responsibility.
By default, a cluster is reachable only through an internal ClusterIP Service inside your Kubernetes network, and it has no API key. If you expose it to anything beyond that, configure an API key first.
What Protects What
| Layer | What it does | Who enforces it |
|---|---|---|
| API keys and JWT | Authenticate clients on REST (6333) and gRPC (6334) | The database |
| TLS | Encrypts client traffic, and optionally traffic between nodes | The database, or your ingress or load balancer |
| Network policies | Limit which Pods can reach the database Pods | Your CNI plugin |
| Load balancer source ranges | Limit which client IP ranges can reach a LoadBalancer Service | Your cloud provider and Kubernetes load balancer stack |
| Ingress source ranges | Limit which client IP ranges can reach an ingress | Your ingress controller |
Exposing a Cluster
By default, a QdrantCluster is exposed through an internal ClusterIP Service. Inside the Kubernetes cluster, reach it by Service name:
http://qdrant-a7d8d973-0cc5-42de-8d7b-c29d14d24840.qdrant-private-cloud.svc:6333
From your own machine, forward the port:
kubectl --namespace qdrant-private-cloud \
port-forward service/qdrant-a7d8d973-0cc5-42de-8d7b-c29d14d24840 6333:6333
To expose the cluster outside Kubernetes, use a NodePort Service, a LoadBalancer Service, or an Ingress.
LoadBalancer Service
This is an example of a QdrantCluster with a LoadBalancer Service:
apiVersion: qdrant.io/v1
kind: QdrantCluster
metadata:
name: qdrant-a7d8d973-0cc5-42de-8d7b-c29d14d24840
labels:
cluster-id: "a7d8d973-0cc5-42de-8d7b-c29d14d24840"
customer-id: "acme-industries"
spec:
id: "a7d8d973-0cc5-42de-8d7b-c29d14d24840"
version: "v1.20.0"
size: 1
resources:
cpu: 100m
memory: "1Gi"
storage: "2Gi"
service:
type: LoadBalancer
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: nlb
loadBalancerSourceRanges:
- 203.0.113.0/24
You may need annotations for the load balancer configuration. See the documentation of your cloud provider:
- AWS EKS LoadBalancer annotations
- Azure AKS Public LoadBalancer annotations
- Azure AKS Internal LoadBalancer annotations
- GCP GKE LoadBalancer annotations
Ingress
To expose a cluster through an ingress, set spec.ingress. The ingress provider is chosen for the whole installation in the Operator Helm values (KubernetesIngress by default, see Configuration).
spec:
ingress:
enabled: true
host: qdrant.example.com
ingressClassName: nginx
tls: true
tlsSecretName: qdrant-tls
The NGINX and Traefik providers also take allowedSourceRanges, a list of CIDR ranges allowed to reach the ingress. As with load balancers, your ingress controller does the enforcing. See the API Reference for every field.
Network Policies
When network policies are enabled, which is the default, the Operator creates one NetworkPolicy for each cluster. It is named qdrant-<cluster-id> and selects the Pods of that cluster. It allows:
| Direction | Allowed traffic |
|---|---|
| Ingress and egress | Between the Pods of the cluster, on 6333 (REST), 6334 (gRPC), and 6335 (peer-to-peer) |
| Ingress | From the Operator and cluster manager Pods, in any namespace, on 6333 and 6334 |
| Ingress | On 6333 and 6334 with no source restriction, so any Pod on the cluster network can connect. This comes from the Helm values below |
| Egress | UDP 53 to the Kubernetes DNS server |
| Egress | REST to the write cluster, only if spec.writeCluster is set |
A NetworkPolicy only takes effect if your CNI plugin enforces them.
The policy does not authenticate anyone. It does not replace API keys or TLS. It also does not filter by client IP. With the default values, anything that can reach the Pods on 6333 or 6334 can send requests, and the API key is the only barrier.
Restricting Client Access
The ingress and egress rules that the chart adds are in the Operator Helm values under operator.settings.features.clusterManagement.qdrant.networkPolicies. The default allows 6333 and 6334 from anywhere on the cluster network. To limit it to known clients, replace the default ingress entry with from rules:
operator:
settings:
features:
clusterManagement:
qdrant:
networkPolicies:
enable: true
ingress:
- from:
- ipBlock:
cidr: 192.168.0.0/22
- podSelector:
matchLabels:
app: client-app
namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: client-namespace
- podSelector:
matchLabels:
app: traefik
namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- port: 6333
protocol: TCP
- port: 6334
protocol: TCP
Include the Pods of your ingress controller or load balancer in the allowed sources, or external clients lose access. To turn network policy management off, set enable: false. Then no NetworkPolicy is created, and you must secure the port 6335 yourself.
Authentication and Authorization
Authentication information is provided by Kubernetes secrets.
One way to create a secret is with kubectl:
kubectl create secret generic qdrant-api-key \
--from-literal=api-key=your-secret-api-key \
--from-literal=read-only-api-key=your-secret-read-only-api-key \
--namespace qdrant-private-cloud
The resulting secret will look like this:
apiVersion: v1
data:
api-key: ...
read-only-api-key: ...
kind: Secret
metadata:
name: qdrant-api-key
namespace: qdrant-private-cloud
type: kubernetes.io/generic
You can reference the secret in the QdrantCluster spec:
apiVersion: qdrant.io/v1
kind: QdrantCluster
metadata:
name: qdrant-a7d8d973-0cc5-42de-8d7b-c29d14d24840
labels:
cluster-id: "a7d8d973-0cc5-42de-8d7b-c29d14d24840"
customer-id: "acme-industries"
spec:
id: "a7d8d973-0cc5-42de-8d7b-c29d14d24840"
version: "v1.20.0"
size: 1
resources:
cpu: 100m
memory: "1Gi"
storage: "2Gi"
config:
service:
api_key:
secretKeyRef:
name: qdrant-api-key
key: api-key
read_only_api_key:
secretKeyRef:
name: qdrant-api-key
key: read-only-api-key
jwt_rbac: true
If you set the jwt_rbac flag, you can also create granular JWT tokens for role based access control.
TLS
Configuring TLS for Database Access
To configure TLS for client access to your Qdrant database, there are two options:
- Offload TLS at the ingress or load balancer level.
- Configure TLS directly in the Qdrant database.
To configure TLS directly in the database, provide the certificate as a secret.
To create such a secret, you can use kubectl:
kubectl create secret tls qdrant-tls \
--cert=mydomain.com.crt --key=mydomain.com.key \
--namespace the-qdrant-namespace
The resulting secret will look like this:
apiVersion: v1
data:
tls.crt: ...
tls.key: ...
kind: Secret
metadata:
name: qdrant-tls
namespace: the-qdrant-namespace
type: kubernetes.io/tls
You can reference the secret in the QdrantCluster spec:
apiVersion: qdrant.io/v1
kind: QdrantCluster
metadata:
name: test-cluster
spec:
id: "a7d8d973-0cc5-42de-8d7b-c29d14d24840"
version: "v1.20.0"
size: 1
resources:
cpu: 100m
memory: "1Gi"
storage: "2Gi"
config:
service:
enable_tls: true
tls:
cert:
secretKeyRef:
name: qdrant-tls
key: tls.crt
key:
secretKeyRef:
name: qdrant-tls
key: tls.key
Configuring TLS for Inter-cluster Communication
Available as of Operator v2.2.0
To encrypt communication between Qdrant nodes, enable TLS by providing a certificate, a key, and the root CA certificate that was used to generate them.
As in the previous section, create a secret:
kubectl create secret generic qdrant-p2p-tls \
--from-file=tls.crt=qdrant-nodes.crt \
--from-file=tls.key=qdrant-nodes.key \
--from-file=ca.crt=root-ca.crt \
--namespace the-qdrant-namespace
The resulting secret will look like this:
apiVersion: v1
data:
tls.crt: ...
tls.key: ...
ca.crt: ...
kind: Secret
metadata:
name: qdrant-p2p-tls
namespace: the-qdrant-namespace
type: Opaque
You can reference the secret in the QdrantCluster spec:
apiVersion: qdrant.io/v1
kind: QdrantCluster
metadata:
name: test-cluster
labels:
cluster-id: "my-cluster"
customer-id: "acme-industries"
spec:
id: "my-cluster"
version: "v1.20.0"
size: 2
resources:
cpu: 100m
memory: "1Gi"
storage: "2Gi"
config:
service:
enable_tls: true
tls:
caCert:
secretKeyRef:
name: qdrant-p2p-tls
key: ca.crt
cert:
secretKeyRef:
name: qdrant-p2p-tls
key: tls.crt
key:
secretKeyRef:
name: qdrant-p2p-tls
key: tls.key
Audit Logging
Audit logging records API operations that require authentication or authorization. See Audit Logging.
Verifying Container Images
Qdrant attaches a Software Bill of Materials (SBOM) to every released container image of the Qdrant components that run in your cluster. All images are scanned for vulnerabilities with Trivy, and all images and Helm charts are signed with Cosign.
Inspect the SBOM of an image with the tool of your choice, for example:
docker buildx imagetools inspect \
registry.cloud.qdrant.io/qdrant/operator:latest \
--format "{{ json .SBOM }}"
Verify the signature of an image with cosign:
cosign verify registry.cloud.qdrant.io/qdrant/operator:latest \
--certificate-oidc-issuer=https://token.actions.githubusercontent.com \
--certificate-identity-regexp='https://github.com/qdrant/.*'
If you mirror the images into your own registry, run the checks against the mirror, or before you mirror. See Cloud Security for more on how Qdrant secures its software.