feat(ldap): adds ldap support for Harbor

This adds a chart that allows LDAP support for Harbor dashboard.

Signed-off-by: Tin Lam <tin@irrational.io>
Change-Id: I60849d720f09296e5cc6872a77053667a6f5b69e
This commit is contained in:
Tin Lam 2021-01-20 16:41:13 -06:00
parent c835d442a3
commit 714a550ffd
19 changed files with 533 additions and 19 deletions

View File

@ -0,0 +1 @@
values_overrides

10
charts/harbor/Chart.yaml Normal file
View File

@ -0,0 +1,10 @@
apiVersion: v2
name: harbor-utils
description: Harbor utility chart
type: application
version: 0.1.0
appVersion: "1.16.0"
dependencies:
- name: harbor
version: "1.5.3"
repository: "https://helm.goharbor.io"

View File

@ -0,0 +1,10 @@
#!/bin/sh
set -xe
curl --show-error --fail --location --insecure --request PUT \
--netrc-file /etc/harbor/admin.rc \
-H "accept: application/json" \
-H "content-type: application/json" \
-d "@/config/config.json" \
${HARBOR_API_URL}

View File

@ -0,0 +1,21 @@
#!/bin/sh
set -xe
test_status() {
curl --head --show-error --silent --fail --location --insecure --request GET \
--netrc-file $1 \
-H "accept: application/json" \
-H "content-type: application/json" \
${HARBOR_API_URL}/api/v2.0/statistics | head -n 1 | awk '{print $2}'
}
if [ "$(test_status /etc/harbor/good_ldap.rc)" -ne "200" ]; then
echo "expected 200"
exit 1
fi
if [ "$(test_status /etc/harbor/bad_ldap.rc)" -ne "401" ]; then
echo "expected 401"
exit 1
fi

View File

@ -0,0 +1,14 @@
{{- define "configmap-harbor_ldap" -}}
apiVersion: v1
kind: ConfigMap
metadata:
name: harbor-ldap-bin
data:
harbor-ldap-test.sh: |
{{- tpl (.Files.Get "bin/harbor-ldap-test.sh") . | nindent 4 }}
harbor-ldap-setup.sh: |
{{- tpl (.Files.Get "bin/harbor-ldap-setup.sh") . | nindent 4 }}
{{- end -}}
{{- if $.Values.config.ldap.enabled }}
{{- include "helpers.template.overlay" (dict "Global" $ "template_definition" "configmap-harbor_ldap") }}
{{- end }}

View File

@ -0,0 +1,65 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "helpers.labels.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
*/}}
{{- define "helpers.labels.fullname" -}}
{{- if .Values.fullnameOverride -}}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- $name := default .Chart.Name .Values.nameOverride -}}
{{- if contains $name .Release.Name -}}
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "helpers.labels.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{/*
Labels to use on {deploy|sts}.spec.selector.matchLabels and svc.spec.selector
*/}}
{{- define "helpers.labels.matchLabels" -}}
{{- $Global := index . "Global" -}}
{{- $Component := index . "Component" -}}
app.kubernetes.io/name: {{ include "helpers.labels.name" $Global }}
app.kubernetes.io/instance: {{ $Global.Values.release_group | default $Global.Release.Name }}
{{- if $Component }}
app.kubernetes.io/component: {{ $Component }}
{{- end }}
{{- end -}}
{{/*
Common labels
*/}}
{{- define "helpers.labels.labels" -}}
{{- $Global := index . "Global" -}}
{{- $PartOf := index . "PartOf" -}}
{{- $Component := index . "Component" -}}
{{- $Version := index . "Version" -}}
{{ include "helpers.labels.matchLabels" (dict "Global" $Global )}}
app.kubernetes.io/managed-by: {{ $Global.Release.Service }}
{{- if $PartOf }}
app.kubernetes.io/part-of: {{ $PartOf }}
{{- end }}
{{- if $Component }}
app.kubernetes.io/component: {{ $Component }}
{{- end }}
{{- if $Version }}
app.kubernetes.io/version: {{ $Version }}
{{- end }}
helm.sh/chart: {{ include "helpers.labels.chart" $Global }}
{{- end -}}

View File

@ -0,0 +1,21 @@
{{- define "helpers.pod.container.image" -}}
{{- $Global := index . "Global" -}}
{{- $Application := index . "Application" -}}
{{- with index $.Global.Values.images.applications $Application -}}
{{- printf "%s/%s:%s" .repo .name ( .tag | toString ) | quote -}}
{{- end -}}
{{- end -}}
{{- define "helpers.pod.node_selector" -}}
{{- $Global := index . "Global" -}}
{{- $Application := index . "Application" -}}
{{- with index $.Global.Values.node_labels $Application -}}
{{- if kindIs "slice" . -}}
{{- range $k, $item := . }}
{{ $item.key }}: {{ $item.value | quote }}
{{- end }}
{{- else -}}
{{ .key }}: {{ .value | quote }}
{{- end }}
{{- end -}}
{{- end -}}

View File

@ -0,0 +1,107 @@
{{- define "helpers.template.overlay" -}}
{{- $local := dict -}}
{{/*
By default we merge lists with a 'name' key's values
*/}}
{{- $_ := set $local "merge_same_named" true -}}
{{- if kindIs "map" $ -}}
{{- if hasKey $ "merge_same_named" -}}
{{- $_ := set $local "merge_same_named" $.merge_same_named -}}
{{- end -}}
{{- end -}}
{{- $_ := set $local "input" ( fromYaml ( toString ( include $.template_definition $.Global ) ) ) -}}
{{- $target := dict -}}
{{- $overlay_keys := regexSplit "-+" ( trimSuffix ".yaml" ( lower ( base $.Global.Template.Name ) ) ) 2 }}
{{- $_ := set $local "overlay" dict -}}
{{- if hasKey $.Global.Values.over_rides ( index $overlay_keys 0 ) -}}
{{- if hasKey ( index $.Global.Values.over_rides ( index $overlay_keys 0 ) ) ( index $overlay_keys 1 ) -}}
{{- $_ := set $local "overlay" ( index $.Global.Values.over_rides ( index $overlay_keys 0 ) ( index $overlay_keys 1 ) ) -}}
{{- end }}
{{- end }}
{{- range $item := tuple $local.input $local.overlay -}}
{{- $call := dict "target" $target "source" . "merge_same_named" $local.merge_same_named -}}
{{- $_ := include "helpers._merge" $call -}}
{{- $_ := set $local "result" $call.result -}}
{{- end -}}
{{- if kindIs "map" $ -}}
{{- $_ := set $ "result" $local.result -}}
{{- end -}}
{{ $target | toYaml }}
{{- end -}}
{{- define "helpers._merge" -}}
{{- $local := dict -}}
{{- $_ := set $ "result" $.source -}}
{{/*
TODO: Should we `fail` when trying to merge a collection (map or slice) with
either a different kind of collection or a scalar?
*/}}
{{- if and (kindIs "map" $.target) (kindIs "map" $.source) -}}
{{- range $key, $sourceValue := $.source -}}
{{- if not (hasKey $.target $key) -}}
{{- $_ := set $local "newTargetValue" $sourceValue -}}
{{- if kindIs "map" $sourceValue -}}
{{- $copy := dict -}}
{{- $call := dict "target" $copy "source" $sourceValue -}}
{{- $_ := include "helpers._merge.shallow" $call -}}
{{- $_ := set $local "newTargetValue" $copy -}}
{{- end -}}
{{- else -}}
{{- $targetValue := index $.target $key -}}
{{- $call := dict "target" $targetValue "source" $sourceValue "merge_same_named" $.merge_same_named -}}
{{- $_ := include "helpers._merge" $call -}}
{{- $_ := set $local "newTargetValue" $call.result -}}
{{- end -}}
{{- $_ := set $.target $key $local.newTargetValue -}}
{{- end -}}
{{- $_ := set $ "result" $.target -}}
{{- else if and (kindIs "slice" $.target) (kindIs "slice" $.source) -}}
{{- $call := dict "target" $.target "source" $.source -}}
{{- $_ := include "helpers._merge.append_slice" $call -}}
{{- if $.merge_same_named -}}
{{- $_ := set $local "result" list -}}
{{- $_ := set $local "named_items" dict -}}
{{- range $item := $call.result -}}
{{- $_ := set $local "has_name_key" false -}}
{{- if kindIs "map" $item -}}
{{- if hasKey $item "name" -}}
{{- $_ := set $local "has_name_key" true -}}
{{- end -}}
{{- end -}}
{{- if $local.has_name_key -}}
{{- if hasKey $local.named_items $item.name -}}
{{- $named_item := index $local.named_items $item.name -}}
{{- $call := dict "target" $named_item "source" $item "merge_same_named" $.merge_same_named -}}
{{- $_ := include "helpers._merge" $call -}}
{{- else -}}
{{- $copy := dict -}}
{{- $copy_call := dict "target" $copy "source" $item -}}
{{- $_ := include "helpers._merge.shallow" $copy_call -}}
{{- $_ := set $local.named_items $item.name $copy -}}
{{- $_ := set $local "result" (append $local.result $copy) -}}
{{- end -}}
{{- else -}}
{{- $_ := set $local "result" (append $local.result $item) -}}
{{- end -}}
{{- end -}}
{{- else -}}
{{- $_ := set $local "result" $call.result -}}
{{- end -}}
{{- $_ := set $ "result" (uniq $local.result) -}}
{{- end -}}
{{- end -}}
{{- define "helpers._merge.shallow" -}}
{{- range $key, $value := $.source -}}
{{- $_ := set $.target $key $value -}}
{{- end -}}
{{- end -}}
{{- define "helpers._merge.append_slice" -}}
{{- $local := dict -}}
{{- $_ := set $local "result" $.target -}}
{{- range $value := $.source -}}
{{- $_ := set $local "result" (append $local.result $value) -}}
{{- end -}}
{{- $_ := set $ "result" $local.result -}}
{{- end -}}

View File

@ -0,0 +1,86 @@
{{- define "job-harbor_ldap" -}}
apiVersion: batch/v1
kind: Job
metadata:
name: harbor-ldap-job
labels: {{- include "helpers.labels.labels" (dict "Global" $ "Component" "harbor") | nindent 4 }}
spec:
template:
metadata:
labels: {{- include "helpers.labels.labels" (dict "Global" $ "Component" "harbor") | nindent 8 }}
spec:
serviceAccount: harbor-ldap
restartPolicy: OnFailure
initContainers:
- name: init
image: {{ include "helpers.pod.container.image" ( dict "Global" $ "Application" "harbor_ldap_job_init" ) }}
imagePullPolicy: {{ $.Values.images.pull.policy | quote }}
env:
- name: POD_NAME
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: metadata.name
- name: NAMESPACE
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: metadata.namespace
- name: PATH
value: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/
- name: DEPENDENCY_SERVICE
value: {{ printf "%s:%s" $.Release.Namespace "harbor-harbor-core" | quote }}
- name: DEPENDENCY_JOBS
value: ""
- name: DEPENDENCY_DAEMONSET
value: ""
- name: DEPENDENCY_CONTAINER
value: ""
- name: DEPENDENCY_POD_JSON
value: ""
- name: DEPENDENCY_CUSTOM_RESOURCE
value: ""
command:
- kubernetes-entrypoint
volumeMounts:
[]
containers:
- name: config
image: {{ include "helpers.pod.container.image" ( dict "Global" $ "Application" "harbor_ldap_job" ) }}
imagePullPolicy: {{ $.Values.images.pull.policy | quote }}
env:
- name: HARBOR_API_URL
value: {{ printf "%s/api/v2.0/configurations" $.Values.config.harbor.api_url | quote }}
command:
- /tmp/harbor-ldap-setup.sh
volumeMounts:
- name: pod-tmp
mountPath: /tmp
- name: harbor-bin
mountPath: /tmp/harbor-ldap-setup.sh
subPath: harbor-ldap-setup.sh
readOnly: true
- name: config
mountPath: /config/config.json
subPath: config.json
readOnly: true
- name: adminrc
mountPath: /etc/harbor
readOnly: true
volumes:
- name: pod-tmp
emptyDir: {}
- name: harbor-bin
configMap:
name: harbor-ldap-bin
defaultMode: 0555
- name: config
secret:
secretName: harbor-ldap-etc
- name: adminrc
secret:
secretName: harbor-adminrc
{{- end -}}
{{- if $.Values.config.ldap.enabled }}
{{- include "helpers.template.overlay" (dict "Global" $ "template_definition" "job-harbor_ldap") }}
{{- end }}

View File

@ -0,0 +1,24 @@
{{- define "role-harbor_ldap" -}}
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: {{ printf "%s-%s-%s" $.Release.Name $.Release.Namespace "harbor-ldap" }}
namespace: {{ $.Release.Namespace }}
rules:
- apiGroups:
- ""
- extensions
- batch
- apps
verbs:
- get
- list
resources:
- services
- endpoints
- jobs
- pods
{{- end -}}
{{- if $.Values.config.ldap.enabled }}
{{- include "helpers.template.overlay" (dict "Global" $ "template_definition" "role-harbor_ldap") }}
{{- end }}

View File

@ -0,0 +1,18 @@
{{- define "rolebinding-harbor_ldap" -}}
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: {{ printf "%s-%s-%s" $.Release.Name $.Release.Namespace "harbor-ldap" }}
namespace: {{ $.Release.Namespace }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: {{ printf "%s-%s-%s" $.Release.Name $.Release.Namespace "harbor-ldap" }}
subjects:
- kind: ServiceAccount
name: harbor-ldap
namespace: {{ $.Release.Namespace }}
{{- end -}}
{{- if $.Values.config.ldap.enabled }}
{{- include "helpers.template.overlay" (dict "Global" $ "template_definition" "rolebinding-harbor_ldap") }}
{{- end }}

View File

@ -0,0 +1,13 @@
{{- define "secret-adminrc" -}}
{{- $p := urlParse $.Values.config.harbor.api_url -}}
apiVersion: v1
kind: Secret
type: Opaque
metadata:
name: harbor-adminrc
data:
admin.rc: {{ b64enc ( printf "machine %s login %s password %s" $p.host "admin" $.Values.config.harbor.admin_password ) }}
{{- end -}}
{{- if $.Values.config.ldap.enabled }}
{{- include "helpers.template.overlay" (dict "Global" $ "template_definition" "secret-adminrc") }}
{{- end }}

View File

@ -0,0 +1,12 @@
{{- define "secret-harbor_ldap" -}}
apiVersion: v1
kind: Secret
type: Opaque
metadata:
name: harbor-ldap-etc
data:
config.json: {{ $.Values.config.ldap.data | toJson | b64enc }}
{{- end -}}
{{- if $.Values.config.ldap.enabled }}
{{- include "helpers.template.overlay" (dict "Global" $ "template_definition" "secret-harbor_ldap") }}
{{- end }}

View File

@ -0,0 +1,14 @@
{{- define "secret-netrc" -}}
{{- $p := urlParse $.Values.config.harbor.api_url -}}
apiVersion: v1
kind: Secret
type: Opaque
metadata:
name: harbor-netrc
data:
good_ldap.rc: {{ b64enc ( printf "machine %s login %s password %s" $p.host $.Values.config.test.ldap_username $.Values.config.test.ldap_password ) }}
bad_ldap.rc: {{ b64enc ( printf "machine %s login %s password %s" $p.host $.Values.config.test.ldap_username ( randAlphaNum 10 ) ) }}
{{- end -}}
{{- if $.Values.config.ldap.enabled }}
{{- include "helpers.template.overlay" (dict "Global" $ "template_definition" "secret-netrc") }}
{{- end }}

View File

@ -0,0 +1,10 @@
{{- define "serviceaccount-harbor_ldap" -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: harbor-ldap
namespace: {{ $.Release.Namespace }}
{{- end -}}
{{- if $.Values.config.ldap.enabled }}
{{- include "helpers.template.overlay" (dict "Global" $ "template_definition" "serviceaccount-harbor_ldap") }}
{{- end }}

View File

@ -0,0 +1,43 @@
{{- define "test-harbor_ldap" -}}
apiVersion: v1
kind: Pod
metadata:
name: test-harbor-ldap
labels: {{- include "helpers.labels.labels" (dict "Global" $ "Component" "harbor" ) | nindent 4 }}
annotations:
"helm.sh/hook": test-success
spec:
restartPolicy: Never
containers:
- name: harbor-ldap-test
image: {{ include "helpers.pod.container.image" ( dict "Global" $ "Application" "harbor_ldap_test" ) }}
imagePullPolicy: {{ $.Values.images.pull.policy | quote }}
env:
- name: HARBOR_API_URL
value: {{ $.Values.config.harbor.api_url | quote }}
command:
- /tmp/harbor-ldap-test.sh
volumeMounts:
- name: pod-tmp
mountPath: /tmp
- name: harbor-bin
mountPath: /tmp/harbor-ldap-test.sh
subPath: harbor-ldap-test.sh
readOnly: true
- name: netrc
mountPath: /etc/harbor
readOnly: true
volumes:
- name: pod-tmp
emptyDir: {}
- name: harbor-bin
configMap:
name: harbor-ldap-bin
defaultMode: 0555
- name: netrc
secret:
secretName: harbor-netrc
{{- end -}}
{{- if $.Values.config.ldap.enabled }}
{{- include "helpers.template.overlay" (dict "Global" $ "template_definition" "test-harbor_ldap") }}
{{- end }}

39
charts/harbor/values.yaml Normal file
View File

@ -0,0 +1,39 @@
config:
test:
ldap_username: jarvis
ldap_password: password
harbor:
# NOTE(lamt): this url should include the scheme (http or https) and should
# exclude trailing "/"
api_url: https://harbor-harbor-core.harbor.svc.cluster.local
admin_password: Harbor12345
ldap:
enabled: true
data:
auth_mode: ldap_auth
ldap_base_dn: 'dc=jarvis,dc=local'
ldap_search_dn: 'cn=readonly,dc=jarvis,dc=local'
ldap_search_password: readonly
ldap_uid: uid
ldap_url: 'ldap://ldap-openldap.ldap.svc.cluster.local'
params: {}
images:
applications:
harbor_ldap_job_init:
name: kubernetes-entrypoint
repo: quay.io/airshipit
tag: v1.0.0
harbor_ldap_job:
name: curl
repo: quay.io/stannum
tag: 7.74.0
harbor_ldap_test:
name: curl
repo: quay.io/stannum
tag: 7.74.0
pull:
policy: IfNotPresent
over_rides: {}

View File

@ -1,17 +1,18 @@
harborAdminPassword: Harbor12345
secretKey: not-a-secure-key
externalURL: https://harbor-core.jarvis.local
expose:
ingress:
hosts:
core: harbor-core.jarvis.local
notary: harbor-notary.jarvis.local
annotations:
cert-manager.io/cluster-issuer: jarvis-ca-issuer
tls:
certSource: secret
secret:
secretName: harbor-core-tls
notarySecretName: harbor-notary-tls
internalTLS:
enabled: true
harbor:
harborAdminPassword: Harbor12345
secretKey: not-a-secure-key
externalURL: https://harbor-core.jarvis.local
expose:
ingress:
hosts:
core: harbor-core.jarvis.local
notary: harbor-notary.jarvis.local
annotations:
cert-manager.io/cluster-issuer: jarvis-ca-issuer
tls:
certSource: secret
secret:
secretName: harbor-core-tls
notarySecretName: harbor-notary-tls
internalTLS:
enabled: true

View File

@ -1,6 +1,9 @@
#!/bin/bash
set -ex
helm repo add harbor https://helm.goharbor.io
cd ./charts/harbor
helm dep up
cd -
# shellcheck disable=SC2046
helm upgrade \
@ -8,11 +11,13 @@ helm upgrade \
--install \
--namespace=harbor \
harbor \
harbor/harbor \
./charts/harbor \
$(./tools/deployment/common/get-values-overrides.sh harbor)
./tools/deployment/common/wait-for-pods.sh harbor
helm -n harbor test harbor --logs
function validate() {
helm plugin update push || helm plugin install https://github.com/chartmuseum/helm-push