mirror of
https://github.com/outbackdingo/qdrant-helm.git
synced 2026-01-27 10:20:18 +00:00
Support read_only_api_key in Qdrant config, similar to the api_key co… (#146)
* Support read_only_api_key in Qdrant config, similar to the api_key config value * Fix updates of secret * Fix updates of secret * Fix updates of secret * Fix updates of secret
This commit is contained in:
@@ -68,7 +68,3 @@ Note: Make sure volume is on the same region and availability zone as where qdra
|
||||
Metrics are available through rest api (default port set to 6333) at `/metrics`
|
||||
|
||||
Refer to [qdrant metrics configuration](https://qdrant.tech/documentation/telemetry/#metrics) for more information.
|
||||
|
||||
### Enable rolling update on configuration change
|
||||
|
||||
To enable rolling update on config map modification set `updateConfigurationOnChange` to true.
|
||||
|
||||
@@ -66,15 +66,33 @@ Create the name of the service account to use
|
||||
Create secret
|
||||
*/}}
|
||||
{{- define "qdrant.secret" -}}
|
||||
{{- $readOnlyApiKey := false }}
|
||||
{{- $apiKey := false }}
|
||||
{{- if eq (.Values.apiKey | toJson) "true" -}}
|
||||
{{- /* retrieve existing randomly generated api key or create new one */ -}}
|
||||
{{- $secretObj := (lookup "v1" "Secret" .Release.Namespace (printf "%s-apikey" (include "qdrant.fullname" . ))) | default dict -}}
|
||||
{{- $secretData := (get $secretObj "data") | default dict -}}
|
||||
{{- $apiKey := (get $secretData "api-key" | b64dec) | default (randAlphaNum 32) -}}
|
||||
{{- $apiKey = (get $secretData "api-key" | b64dec) | default (randAlphaNum 32) -}}
|
||||
{{- else if .Values.apiKey -}}
|
||||
{{- $apiKey = .Values.apiKey -}}
|
||||
{{- end -}}
|
||||
{{- if eq (.Values.readOnlyApiKey | toJson) "true" -}}
|
||||
{{- /* retrieve existing randomly generated api key or create new one */ -}}
|
||||
{{- $secretObj := (lookup "v1" "Secret" .Release.Namespace (printf "%s-apikey" (include "qdrant.fullname" . ))) | default dict -}}
|
||||
{{- $secretData := (get $secretObj "data") | default dict -}}
|
||||
{{- $readOnlyApiKey = (get $secretData "read-only-api-key" | b64dec) | default (randAlphaNum 32) -}}
|
||||
{{- else if .Values.readOnlyApiKey -}}
|
||||
{{- $readOnlyApiKey = .Values.readOnlyApiKey -}}
|
||||
{{- end -}}
|
||||
{{- if and $apiKey $readOnlyApiKey -}}
|
||||
api-key: {{ $apiKey | b64enc }}
|
||||
read-only-api-key: {{ $readOnlyApiKey | b64enc }}
|
||||
local.yaml: {{ printf "service:\n api_key: %s\n read_only_api_key: %s" $apiKey $readOnlyApiKey | b64enc }}
|
||||
{{- else if $apiKey -}}
|
||||
api-key: {{ $apiKey | b64enc }}
|
||||
local.yaml: {{ printf "service:\n api_key: %s" $apiKey | b64enc }}
|
||||
{{- else if .Values.apiKey -}}
|
||||
api-key: {{ .Values.apiKey | b64enc }}
|
||||
local.yaml: {{ printf "service:\n api_key: %s" .Values.apiKey | b64enc }}
|
||||
{{- else if $readOnlyApiKey -}}
|
||||
read-only-api-key: {{ $readOnlyApiKey | b64enc }}
|
||||
local.yaml: {{ printf "service:\n read_only_api_key: %s" $readOnlyApiKey | b64enc }}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
@@ -1,4 +1,4 @@
|
||||
{{- if .Values.apiKey }}
|
||||
{{- if or .Values.apiKey .Values.readOnlyApiKey }}
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
|
||||
@@ -30,6 +30,12 @@ spec:
|
||||
credentials:
|
||||
name: {{ include "qdrant.fullname" . }}-apikey
|
||||
key: api-key
|
||||
{{- else if .Values.readOnlyApiKey }}
|
||||
authorization:
|
||||
type: Bearer
|
||||
credentials:
|
||||
name: {{ include "qdrant.fullname" . }}-apikey
|
||||
key: read-only-api-key
|
||||
{{- end }}
|
||||
selector:
|
||||
matchLabels:
|
||||
|
||||
@@ -17,8 +17,9 @@ spec:
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
{{- if (default .Values.updateConfigurationOnChange false) }}
|
||||
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
|
||||
{{- if or .Values.apiKey .Values.readOnlyApiKey }}
|
||||
checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }}
|
||||
{{- end }}
|
||||
{{- with .Values.podAnnotations }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
@@ -171,7 +172,7 @@ spec:
|
||||
- name: qdrant-config
|
||||
mountPath: /qdrant/config/production.yaml
|
||||
subPath: production.yaml
|
||||
{{- if .Values.apiKey }}
|
||||
{{- if or .Values.apiKey .Values.readOnlyApiKey }}
|
||||
- name: qdrant-secret
|
||||
mountPath: /qdrant/config/local.yaml
|
||||
subPath: local.yaml
|
||||
@@ -219,7 +220,7 @@ spec:
|
||||
{{- end }}
|
||||
- name: qdrant-init
|
||||
emptyDir: {}
|
||||
{{- if .Values.apiKey }}
|
||||
{{- if or .Values.apiKey .Values.readOnlyApiKey }}
|
||||
- name: qdrant-secret
|
||||
secret:
|
||||
secretName: {{ include "qdrant.fullname" . }}-apikey
|
||||
|
||||
@@ -64,6 +64,8 @@ data:
|
||||
API_KEY_HEADER=""
|
||||
{{- if .Values.apiKey }}
|
||||
API_KEY_HEADER="Api-key: {{ .Values.apiKey }}"
|
||||
{{- else if .Values.readOnlyApiKey }}
|
||||
API_KEY_HEADER="Api-key: {{ .Values.readOnlyApiKey }}"
|
||||
{{- end }}
|
||||
|
||||
# Delete collection if exists
|
||||
|
||||
@@ -154,8 +154,6 @@ sidecarContainers: []
|
||||
# memory: 100Mi
|
||||
# cpu: 100m
|
||||
|
||||
updateConfigurationOnChange: false
|
||||
|
||||
metrics:
|
||||
serviceMonitor:
|
||||
enabled: false
|
||||
@@ -190,6 +188,11 @@ podDisruptionBudget:
|
||||
# true: an api key will be auto-generated
|
||||
# string: the given string will be set as an apikey
|
||||
apiKey: false
|
||||
# read-only api key for authentication at qdrant
|
||||
# false: no read-only api key will be configured
|
||||
# true: an read-only api key will be auto-generated
|
||||
# string: the given string will be set as a read-only apikey
|
||||
readOnlyApiKey: false
|
||||
|
||||
additionalVolumes: []
|
||||
# - name: volumeName
|
||||
|
||||
15
test/integration/read_only_api_key.bats
Normal file
15
test/integration/read_only_api_key.bats
Normal file
@@ -0,0 +1,15 @@
|
||||
setup_file() {
|
||||
helm upgrade --install qdrant charts/qdrant --set readOnlyApiKey=barbaz -n qdrant-helm-integration --wait
|
||||
kubectl rollout status statefulset qdrant -n qdrant-helm-integration
|
||||
}
|
||||
|
||||
@test "read-only api key authentication works" {
|
||||
run kubectl exec -n default curl -- curl -s http://qdrant.qdrant-helm-integration:6333/collections -H 'api-key: barbaz' --fail-with-body
|
||||
[ $status -eq 0 ]
|
||||
[[ "${output}" =~ .*\"status\":\"ok\".* ]]
|
||||
}
|
||||
|
||||
@test "read-only api key authentication fails with key" {
|
||||
run kubectl exec -n default curl -- curl -s http://qdrant.qdrant-helm-integration:6333/collections
|
||||
[ "${output}" = "Invalid api-key" ]
|
||||
}
|
||||
@@ -144,3 +144,146 @@ func TestNoApiKey(t *testing.T) {
|
||||
require.Equal(t, hasSecretVolumeMount, false)
|
||||
require.Equal(t, hasSecretVolume, false)
|
||||
}
|
||||
|
||||
func TestStringReadOnlyApiKey(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
helmChartPath, err := filepath.Abs("../charts/qdrant")
|
||||
releaseName := "qdrant"
|
||||
require.NoError(t, err)
|
||||
|
||||
namespaceName := "qdrant-" + strings.ToLower(random.UniqueId())
|
||||
logger.Log(t, "Namespace: %s\n", namespaceName)
|
||||
|
||||
options := &helm.Options{
|
||||
SetJsonValues: map[string]string{
|
||||
"readOnlyApiKey": `"test_api_key"`,
|
||||
},
|
||||
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
|
||||
}
|
||||
|
||||
output := helm.RenderTemplate(t, options, helmChartPath, releaseName, []string{"templates/statefulset.yaml"})
|
||||
|
||||
var statefulSet appsv1.StatefulSet
|
||||
helm.UnmarshalK8SYaml(t, output, &statefulSet)
|
||||
|
||||
output = helm.RenderTemplate(t, options, helmChartPath, releaseName, []string{"templates/secret.yaml"})
|
||||
|
||||
var secret corev1.Secret
|
||||
helm.UnmarshalK8SYaml(t, output, &secret)
|
||||
|
||||
container, _ := lo.Find(statefulSet.Spec.Template.Spec.Containers, func(container corev1.Container) bool {
|
||||
return container.Name == "qdrant"
|
||||
})
|
||||
|
||||
secretVolumeMount, hasSecretVolumeMount := lo.Find(container.VolumeMounts, func(volumeMount corev1.VolumeMount) bool {
|
||||
return volumeMount.Name == "qdrant-secret"
|
||||
})
|
||||
|
||||
_, hasSecretVolume := lo.Find(statefulSet.Spec.Template.Spec.Volumes, func(volume corev1.Volume) bool {
|
||||
return volume.Name == secretVolumeMount.Name
|
||||
})
|
||||
|
||||
require.Equal(t, hasSecretVolumeMount, true)
|
||||
require.Equal(t, hasSecretVolume, true)
|
||||
require.Contains(t, lo.Keys(secret.Data), "local.yaml")
|
||||
require.Contains(t, lo.Keys(secret.Data), "read-only-api-key")
|
||||
|
||||
require.Equal(t, "service:\n read_only_api_key: test_api_key", string(secret.Data["local.yaml"]))
|
||||
}
|
||||
|
||||
func TestRandomReadOnlyApiKey(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
helmChartPath, err := filepath.Abs("../charts/qdrant")
|
||||
releaseName := "qdrant"
|
||||
require.NoError(t, err)
|
||||
|
||||
namespaceName := "qdrant-" + strings.ToLower(random.UniqueId())
|
||||
logger.Log(t, "Namespace: %s\n", namespaceName)
|
||||
|
||||
options := &helm.Options{
|
||||
SetJsonValues: map[string]string{
|
||||
"readOnlyApiKey": `true`,
|
||||
},
|
||||
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
|
||||
}
|
||||
|
||||
output := helm.RenderTemplate(t, options, helmChartPath, releaseName, []string{"templates/statefulset.yaml"})
|
||||
|
||||
var statefulSet appsv1.StatefulSet
|
||||
helm.UnmarshalK8SYaml(t, output, &statefulSet)
|
||||
|
||||
output = helm.RenderTemplate(t, options, helmChartPath, releaseName, []string{"templates/secret.yaml"})
|
||||
|
||||
var secret corev1.Secret
|
||||
helm.UnmarshalK8SYaml(t, output, &secret)
|
||||
|
||||
container, _ := lo.Find(statefulSet.Spec.Template.Spec.Containers, func(container corev1.Container) bool {
|
||||
return container.Name == "qdrant"
|
||||
})
|
||||
|
||||
secretVolumeMount, hasSecretVolumeMount := lo.Find(container.VolumeMounts, func(volumeMount corev1.VolumeMount) bool {
|
||||
return volumeMount.Name == "qdrant-secret"
|
||||
})
|
||||
|
||||
_, hasSecretVolume := lo.Find(statefulSet.Spec.Template.Spec.Volumes, func(volume corev1.Volume) bool {
|
||||
return volume.Name == secretVolumeMount.Name
|
||||
})
|
||||
|
||||
require.Equal(t, hasSecretVolumeMount, true)
|
||||
require.Equal(t, hasSecretVolume, true)
|
||||
require.Contains(t, lo.Keys(secret.Data), "local.yaml")
|
||||
require.Contains(t, lo.Keys(secret.Data), "read-only-api-key")
|
||||
|
||||
require.Regexp(t, "^service:\n read_only_api_key: [a-zA-Z0-9]+$", string(secret.Data["local.yaml"]))
|
||||
}
|
||||
|
||||
func TestAdminAndReadOnlyApiKey(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
helmChartPath, err := filepath.Abs("../charts/qdrant")
|
||||
releaseName := "qdrant"
|
||||
require.NoError(t, err)
|
||||
|
||||
namespaceName := "qdrant-" + strings.ToLower(random.UniqueId())
|
||||
logger.Log(t, "Namespace: %s\n", namespaceName)
|
||||
|
||||
options := &helm.Options{
|
||||
SetJsonValues: map[string]string{
|
||||
"readOnlyApiKey": `true`,
|
||||
"apiKey": `true`,
|
||||
},
|
||||
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
|
||||
}
|
||||
|
||||
output := helm.RenderTemplate(t, options, helmChartPath, releaseName, []string{"templates/statefulset.yaml"})
|
||||
|
||||
var statefulSet appsv1.StatefulSet
|
||||
helm.UnmarshalK8SYaml(t, output, &statefulSet)
|
||||
|
||||
output = helm.RenderTemplate(t, options, helmChartPath, releaseName, []string{"templates/secret.yaml"})
|
||||
|
||||
var secret corev1.Secret
|
||||
helm.UnmarshalK8SYaml(t, output, &secret)
|
||||
|
||||
container, _ := lo.Find(statefulSet.Spec.Template.Spec.Containers, func(container corev1.Container) bool {
|
||||
return container.Name == "qdrant"
|
||||
})
|
||||
|
||||
secretVolumeMount, hasSecretVolumeMount := lo.Find(container.VolumeMounts, func(volumeMount corev1.VolumeMount) bool {
|
||||
return volumeMount.Name == "qdrant-secret"
|
||||
})
|
||||
|
||||
_, hasSecretVolume := lo.Find(statefulSet.Spec.Template.Spec.Volumes, func(volume corev1.Volume) bool {
|
||||
return volume.Name == secretVolumeMount.Name
|
||||
})
|
||||
|
||||
require.Equal(t, hasSecretVolumeMount, true)
|
||||
require.Equal(t, hasSecretVolume, true)
|
||||
require.Contains(t, lo.Keys(secret.Data), "local.yaml")
|
||||
require.Contains(t, lo.Keys(secret.Data), "api-key")
|
||||
require.Contains(t, lo.Keys(secret.Data), "read-only-api-key")
|
||||
|
||||
require.Regexp(t, "^service:\n api_key: [a-zA-Z0-9]+\n read_only_api_key: [a-zA-Z0-9]+$", string(secret.Data["local.yaml"]))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user