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
31 changes: 31 additions & 0 deletions .env.release.example
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,37 @@ SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_OIDC_SCOPE=openid,profile,email
SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_OIDC_CLIENT_NAME=OIDC
SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_OIDC_ISSUER_URI=

# Optional: LDAP/Active Directory credential login. The provider remains
# hidden until enabled and every required field is valid. Keep provider code,
# authority and stable subject mapping unchanged after identities are bound.
SKILLHUB_AUTH_LDAP_ENABLED=false
SKILLHUB_AUTH_LDAP_PROVIDER_CODE=ldap-main
SKILLHUB_AUTH_LDAP_DISPLAY_NAME=Corporate Directory
SKILLHUB_AUTH_LDAP_AUTHORITY=corp-directory
SKILLHUB_AUTH_LDAP_URL=ldaps://ldap.example.com:636
SKILLHUB_AUTH_LDAP_START_TLS=false
SKILLHUB_AUTH_LDAP_ALLOW_INSECURE_FOR_TESTING=false
SKILLHUB_AUTH_LDAP_DIRECTORY_TYPE=OPENLDAP
SKILLHUB_AUTH_LDAP_BASE_DN=dc=example,dc=com
SKILLHUB_AUTH_LDAP_USER_SEARCH_BASE=ou=people
SKILLHUB_AUTH_LDAP_USER_SEARCH_FILTER=(uid={0})
SKILLHUB_AUTH_LDAP_BIND_DN=cn=skillhub,ou=services,dc=example,dc=com
SKILLHUB_AUTH_LDAP_BIND_PASSWORD=
# OPENLDAP defaults to entryUUID/ldap_entry_uuid; ACTIVE_DIRECTORY defaults
# to objectGUID/ad_object_guid. Leave both blank to use those safe defaults.
SKILLHUB_AUTH_LDAP_SUBJECT_ATTRIBUTE=
SKILLHUB_AUTH_LDAP_SUBJECT_TYPE=
SKILLHUB_AUTH_LDAP_USERNAME_ATTRIBUTE=uid
SKILLHUB_AUTH_LDAP_DISPLAY_NAME_ATTRIBUTE=displayName
SKILLHUB_AUTH_LDAP_EMAIL_ATTRIBUTE=mail
SKILLHUB_AUTH_LDAP_AVATAR_URL_ATTRIBUTE=
SKILLHUB_AUTH_LDAP_EMAIL_AUTHORITATIVE=false
SKILLHUB_AUTH_LDAP_CONNECT_TIMEOUT=PT5S
SKILLHUB_AUTH_LDAP_READ_TIMEOUT=PT10S
SKILLHUB_AUTH_LDAP_POOL_WAIT_TIMEOUT=PT2S
SKILLHUB_AUTH_LDAP_MAX_CONCURRENT_REQUESTS=16
SKILLHUB_AUTH_LDAP_MAX_ATTRIBUTE_VALUES=16

# Optional: CAS 2.0/3.0 browser login. The provider remains hidden until
# enabled and every required field is valid. Keep provider code and authority
# stable after users have created identity bindings.
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ with the Skill's source and the problem it solves, or submit a PR by following t
- 📖 **[User Guide](https://iflytek.github.io/skillhub/)** — Skill publishing, search, CLI usage and other user guides
- 🛠️ **[Developer Docs](https://zread.ai/iflytek/skillhub)** — Architecture, API reference, local development, deployment and operations
- 🔐 **[CAS 2.0/3.0 Integration](./docs/23-cas-integration.md)** — Provider configuration, identity mapping, deployment and verification
- 🔐 **[LDAP/Active Directory Integration](./docs/24-ldap-ad-integration.md)** — Secure credential login, stable identity mapping and deployment

## Highlights

Expand Down
23 changes: 23 additions & 0 deletions charts/skillhub/templates/configmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,29 @@ data:
device-auth-verification-uri: {{ $deviceAuthVerificationUri | quote }}
auth-direct-enabled: {{ .Values.auth.direct.enabled | quote }}
auth-direct-provider: {{ .Values.auth.direct.provider | quote }}
auth-ldap-enabled: {{ .Values.auth.ldap.enabled | quote }}
auth-ldap-provider-code: {{ .Values.auth.ldap.providerCode | quote }}
auth-ldap-display-name: {{ .Values.auth.ldap.displayName | quote }}
auth-ldap-authority: {{ .Values.auth.ldap.authority | quote }}
auth-ldap-url: {{ .Values.auth.ldap.url | quote }}
auth-ldap-start-tls: {{ .Values.auth.ldap.startTls | quote }}
auth-ldap-directory-type: {{ .Values.auth.ldap.directoryType | quote }}
auth-ldap-base-dn: {{ .Values.auth.ldap.baseDn | quote }}
auth-ldap-user-search-base: {{ .Values.auth.ldap.userSearchBase | quote }}
auth-ldap-user-search-filter: {{ .Values.auth.ldap.userSearchFilter | quote }}
auth-ldap-bind-dn: {{ .Values.auth.ldap.bindDn | quote }}
auth-ldap-subject-attribute: {{ .Values.auth.ldap.subjectAttribute | quote }}
auth-ldap-subject-type: {{ .Values.auth.ldap.subjectType | quote }}
auth-ldap-username-attribute: {{ .Values.auth.ldap.usernameAttribute | quote }}
auth-ldap-display-name-attribute: {{ .Values.auth.ldap.displayNameAttribute | quote }}
auth-ldap-email-attribute: {{ .Values.auth.ldap.emailAttribute | quote }}
auth-ldap-avatar-url-attribute: {{ .Values.auth.ldap.avatarUrlAttribute | quote }}
auth-ldap-email-authoritative: {{ .Values.auth.ldap.emailAuthoritative | quote }}
auth-ldap-connect-timeout: {{ .Values.auth.ldap.connectTimeout | quote }}
auth-ldap-read-timeout: {{ .Values.auth.ldap.readTimeout | quote }}
auth-ldap-pool-wait-timeout: {{ .Values.auth.ldap.poolWaitTimeout | quote }}
auth-ldap-max-concurrent-requests: {{ .Values.auth.ldap.maxConcurrentRequests | quote }}
auth-ldap-max-attribute-values: {{ .Values.auth.ldap.maxAttributeValues | quote }}
auth-cas-enabled: {{ .Values.auth.cas.enabled | quote }}
auth-cas-provider-code: {{ .Values.auth.cas.providerCode | quote }}
auth-cas-display-name: {{ .Values.auth.cas.displayName | quote }}
Expand Down
5 changes: 5 additions & 0 deletions charts/skillhub/templates/secret.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ stringData:
oauth2-github-client-secret: {{ .Values.secrets.oauth2GithubClientSecret | quote }}
{{- end }}

# LDAP service-account password (optional unless LDAP is enabled)
{{- if .Values.secrets.ldapBindPassword }}
ldap-bind-password: {{ .Values.secrets.ldapBindPassword | quote }}
{{- end }}

# Scanner LLM 配置 (optional)
{{- if .Values.secrets.scannerLlmApiKey }}
skill-scanner-llm-api-key: {{ .Values.secrets.scannerLlmApiKey | quote }}
Expand Down
122 changes: 122 additions & 0 deletions charts/skillhub/templates/server-deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,128 @@ spec:
configMapKeyRef:
name: {{ include "skillhub.fullname" . }}-config
key: auth-direct-enabled
{{- if .Values.auth.ldap.enabled }}
- name: SKILLHUB_AUTH_LDAP_ENABLED
valueFrom:
configMapKeyRef:
name: {{ include "skillhub.fullname" . }}-config
key: auth-ldap-enabled
- name: SKILLHUB_AUTH_LDAP_PROVIDER_CODE
valueFrom:
configMapKeyRef:
name: {{ include "skillhub.fullname" . }}-config
key: auth-ldap-provider-code
- name: SKILLHUB_AUTH_LDAP_DISPLAY_NAME
valueFrom:
configMapKeyRef:
name: {{ include "skillhub.fullname" . }}-config
key: auth-ldap-display-name
- name: SKILLHUB_AUTH_LDAP_AUTHORITY
valueFrom:
configMapKeyRef:
name: {{ include "skillhub.fullname" . }}-config
key: auth-ldap-authority
- name: SKILLHUB_AUTH_LDAP_URL
valueFrom:
configMapKeyRef:
name: {{ include "skillhub.fullname" . }}-config
key: auth-ldap-url
- name: SKILLHUB_AUTH_LDAP_START_TLS
valueFrom:
configMapKeyRef:
name: {{ include "skillhub.fullname" . }}-config
key: auth-ldap-start-tls
- name: SKILLHUB_AUTH_LDAP_DIRECTORY_TYPE
valueFrom:
configMapKeyRef:
name: {{ include "skillhub.fullname" . }}-config
key: auth-ldap-directory-type
- name: SKILLHUB_AUTH_LDAP_BASE_DN
valueFrom:
configMapKeyRef:
name: {{ include "skillhub.fullname" . }}-config
key: auth-ldap-base-dn
- name: SKILLHUB_AUTH_LDAP_USER_SEARCH_BASE
valueFrom:
configMapKeyRef:
name: {{ include "skillhub.fullname" . }}-config
key: auth-ldap-user-search-base
- name: SKILLHUB_AUTH_LDAP_USER_SEARCH_FILTER
valueFrom:
configMapKeyRef:
name: {{ include "skillhub.fullname" . }}-config
key: auth-ldap-user-search-filter
- name: SKILLHUB_AUTH_LDAP_BIND_DN
valueFrom:
configMapKeyRef:
name: {{ include "skillhub.fullname" . }}-config
key: auth-ldap-bind-dn
- name: SKILLHUB_AUTH_LDAP_BIND_PASSWORD
valueFrom:
secretKeyRef:
name: {{ include "skillhub.secretName" . }}
key: ldap-bind-password
- name: SKILLHUB_AUTH_LDAP_SUBJECT_ATTRIBUTE
valueFrom:
configMapKeyRef:
name: {{ include "skillhub.fullname" . }}-config
key: auth-ldap-subject-attribute
- name: SKILLHUB_AUTH_LDAP_SUBJECT_TYPE
valueFrom:
configMapKeyRef:
name: {{ include "skillhub.fullname" . }}-config
key: auth-ldap-subject-type
- name: SKILLHUB_AUTH_LDAP_USERNAME_ATTRIBUTE
valueFrom:
configMapKeyRef:
name: {{ include "skillhub.fullname" . }}-config
key: auth-ldap-username-attribute
- name: SKILLHUB_AUTH_LDAP_DISPLAY_NAME_ATTRIBUTE
valueFrom:
configMapKeyRef:
name: {{ include "skillhub.fullname" . }}-config
key: auth-ldap-display-name-attribute
- name: SKILLHUB_AUTH_LDAP_EMAIL_ATTRIBUTE
valueFrom:
configMapKeyRef:
name: {{ include "skillhub.fullname" . }}-config
key: auth-ldap-email-attribute
- name: SKILLHUB_AUTH_LDAP_AVATAR_URL_ATTRIBUTE
valueFrom:
configMapKeyRef:
name: {{ include "skillhub.fullname" . }}-config
key: auth-ldap-avatar-url-attribute
- name: SKILLHUB_AUTH_LDAP_EMAIL_AUTHORITATIVE
valueFrom:
configMapKeyRef:
name: {{ include "skillhub.fullname" . }}-config
key: auth-ldap-email-authoritative
- name: SKILLHUB_AUTH_LDAP_CONNECT_TIMEOUT
valueFrom:
configMapKeyRef:
name: {{ include "skillhub.fullname" . }}-config
key: auth-ldap-connect-timeout
- name: SKILLHUB_AUTH_LDAP_READ_TIMEOUT
valueFrom:
configMapKeyRef:
name: {{ include "skillhub.fullname" . }}-config
key: auth-ldap-read-timeout
- name: SKILLHUB_AUTH_LDAP_POOL_WAIT_TIMEOUT
valueFrom:
configMapKeyRef:
name: {{ include "skillhub.fullname" . }}-config
key: auth-ldap-pool-wait-timeout
- name: SKILLHUB_AUTH_LDAP_MAX_CONCURRENT_REQUESTS
valueFrom:
configMapKeyRef:
name: {{ include "skillhub.fullname" . }}-config
key: auth-ldap-max-concurrent-requests
- name: SKILLHUB_AUTH_LDAP_MAX_ATTRIBUTE_VALUES
valueFrom:
configMapKeyRef:
name: {{ include "skillhub.fullname" . }}-config
key: auth-ldap-max-attribute-values
{{- end }}
- name: SKILLHUB_AUTH_CAS_ENABLED
valueFrom:
configMapKeyRef:
Expand Down
31 changes: 31 additions & 0 deletions charts/skillhub/templates/validate.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,37 @@
{{- if and .Values.auth.accountMerge.enabled (not .Values.auth.accountMerge.sessionCutoverComplete) -}}
{{- fail "auth.accountMerge.enabled=true requires auth.accountMerge.sessionCutoverComplete=true after rotating the Spring Session namespace" -}}
{{- end -}}
{{- if .Values.auth.ldap.enabled -}}
{{- if not .Values.auth.ldap.url -}}
{{- fail "auth.ldap.enabled=true requires auth.ldap.url" -}}
{{- end -}}
{{- if not .Values.auth.ldap.baseDn -}}
{{- fail "auth.ldap.enabled=true requires auth.ldap.baseDn" -}}
{{- end -}}
{{- if not .Values.auth.ldap.bindDn -}}
{{- fail "auth.ldap.enabled=true requires auth.ldap.bindDn" -}}
{{- end -}}
{{- if and (not .Values.existingSecret) (not .Values.secrets.ldapBindPassword) -}}
{{- fail "auth.ldap.enabled=true requires secrets.ldapBindPassword or existingSecret key ldap-bind-password" -}}
{{- end -}}
{{- if and (eq .Values.auth.ldap.directoryType "CUSTOM") (or (not .Values.auth.ldap.subjectAttribute) (not .Values.auth.ldap.subjectType)) -}}
{{- fail "auth.ldap.directoryType=CUSTOM requires subjectAttribute and subjectType" -}}
{{- end -}}
{{- if hasPrefix "ldaps://" .Values.auth.ldap.url -}}
{{- if .Values.auth.ldap.startTls -}}
{{- fail "auth.ldap.startTls must be false for an ldaps URL" -}}
{{- end -}}
{{- else if hasPrefix "ldap://" .Values.auth.ldap.url -}}
{{- if not .Values.auth.ldap.startTls -}}
{{- fail "auth.ldap.startTls must be true for an ldap URL" -}}
{{- end -}}
{{- else -}}
{{- fail "auth.ldap.url must start with ldap:// or ldaps://" -}}
{{- end -}}
{{- if regexMatch "[?#]" .Values.auth.ldap.url -}}
{{- fail "auth.ldap.url must not contain a query or fragment" -}}
{{- end -}}
{{- end -}}
{{- if .Values.auth.cas.enabled -}}
{{- if not .Values.auth.cas.serverUrl -}}
{{- fail "auth.cas.enabled=true requires auth.cas.serverUrl" -}}
Expand Down
31 changes: 31 additions & 0 deletions charts/skillhub/tests/configuration-contracts.sh
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,23 @@ grep -Fq 'auth-cas-enabled: "true"' "$TMP_DIR/cas.yaml"
grep -Fq 'auth-cas-server-url: "https://cas.example.com/cas"' "$TMP_DIR/cas.yaml"
grep -Fq 'name: SKILLHUB_AUTH_CAS_SERVICE_URL' "$TMP_DIR/cas.yaml"

render ldap "$CHART_DIR" \
--set auth.ldap.enabled=true \
--set auth.ldap.url=ldaps://ldap.example.com:636 \
--set-string 'auth.ldap.baseDn=dc=example\,dc=com' \
--set-string 'auth.ldap.bindDn=cn=skillhub\,dc=example\,dc=com' \
--set-string secrets.ldapBindPassword=ldap-bind-password \
--show-only templates/configmap.yaml \
--show-only templates/secret.yaml \
--show-only templates/server-deployment.yaml >"$TMP_DIR/ldap.yaml"
grep -Fq 'auth-ldap-enabled: "true"' "$TMP_DIR/ldap.yaml"
grep -Fq 'auth-ldap-url: "ldaps://ldap.example.com:636"' "$TMP_DIR/ldap.yaml"
grep -Fq 'ldap-bind-password: "ldap-bind-password"' "$TMP_DIR/ldap.yaml"
grep -Fq 'name: SKILLHUB_AUTH_LDAP_BIND_PASSWORD' "$TMP_DIR/ldap.yaml"
if grep -Fq 'ldap-bind-password:' "$TMP_DIR/default.yaml"; then
fail "disabled LDAP must not render a bind password"
fi

render tls "$CHART_DIR" \
--set ingress.enabled=true \
--set-json 'ingress.tls=[{"hosts":["skills.example.com"],"secretName":"skills-tls"}]' \
Expand Down Expand Up @@ -275,6 +292,20 @@ assert_rejected server-off --set server.enabled=false
assert_rejected direct-auth-without-provider \
--set auth.direct.enabled=true \
--set-string auth.direct.provider=
assert_rejected ldap-without-url \
--set auth.ldap.enabled=true \
--set-string secrets.ldapBindPassword=ldap-bind-password
assert_rejected ldap-without-secret \
--set auth.ldap.enabled=true \
--set auth.ldap.url=ldaps://ldap.example.com:636 \
--set-string 'auth.ldap.baseDn=dc=example\,dc=com' \
--set-string 'auth.ldap.bindDn=cn=skillhub\,dc=example\,dc=com'
assert_rejected ldap-with-insecure-url \
--set auth.ldap.enabled=true \
--set auth.ldap.url=ldap://ldap.example.com:389 \
--set-string 'auth.ldap.baseDn=dc=example\,dc=com' \
--set-string 'auth.ldap.bindDn=cn=skillhub\,dc=example\,dc=com' \
--set-string secrets.ldapBindPassword=ldap-bind-password
assert_rejected cas-without-server \
--set auth.cas.enabled=true \
--set auth.cas.serviceUrl=https://skills.example.com/api/v1/auth/cas/cas-main/callback
Expand Down
33 changes: 32 additions & 1 deletion charts/skillhub/values.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"auth": {
"type": "object",
"additionalProperties": false,
"required": ["direct", "accountMerge", "cas"],
"required": ["direct", "accountMerge", "ldap", "cas"],
"properties": {
"direct": {
"type": "object",
Expand All @@ -41,6 +41,36 @@
"sessionCutoverComplete": { "type": "boolean" }
}
},
"ldap": {
"type": "object",
"additionalProperties": false,
"required": ["enabled", "providerCode", "displayName", "authority", "url", "startTls", "directoryType", "baseDn", "userSearchBase", "userSearchFilter", "bindDn", "subjectAttribute", "subjectType", "usernameAttribute", "displayNameAttribute", "emailAttribute", "avatarUrlAttribute", "emailAuthoritative", "connectTimeout", "readTimeout", "poolWaitTimeout", "maxConcurrentRequests", "maxAttributeValues"],
"properties": {
"enabled": { "type": "boolean" },
"providerCode": { "type": "string", "pattern": "^[a-z0-9][a-z0-9._-]{0,63}$" },
"displayName": { "type": "string", "minLength": 1, "maxLength": 128 },
"authority": { "type": "string", "pattern": "^[a-z0-9][a-z0-9._:-]{0,127}$" },
"url": { "type": "string" },
"startTls": { "type": "boolean" },
"directoryType": { "enum": ["OPENLDAP", "ACTIVE_DIRECTORY", "CUSTOM"] },
"baseDn": { "type": "string" },
"userSearchBase": { "type": "string" },
"userSearchFilter": { "type": "string", "minLength": 1, "maxLength": 1024 },
"bindDn": { "type": "string" },
"subjectAttribute": { "type": "string" },
"subjectType": { "type": "string", "pattern": "^$|^[a-z][a-z0-9_]{0,63}$" },
"usernameAttribute": { "type": "string" },
"displayNameAttribute": { "type": "string" },
"emailAttribute": { "type": "string" },
"avatarUrlAttribute": { "type": "string" },
"emailAuthoritative": { "type": "boolean" },
"connectTimeout": { "type": "string", "pattern": "^P" },
"readTimeout": { "type": "string", "pattern": "^P" },
"poolWaitTimeout": { "type": "string", "pattern": "^P" },
"maxConcurrentRequests": { "type": "integer", "minimum": 1, "maximum": 256 },
"maxAttributeValues": { "type": "integer", "minimum": 1, "maximum": 64 }
}
},
"cas": {
"type": "object",
"additionalProperties": false,
Expand Down Expand Up @@ -200,6 +230,7 @@
"downloadAnonCookieSecret": { "type": "string" },
"oauth2GithubClientId": { "type": "string" },
"oauth2GithubClientSecret": { "type": "string" },
"ldapBindPassword": { "type": "string" },
"scannerLlmApiKey": { "type": "string" },
"scannerLlmBaseUrl": { "type": "string" },
"scannerLlmModel": { "type": "string" }
Expand Down
25 changes: 25 additions & 0 deletions charts/skillhub/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,30 @@ auth:
# documented legacy-session transition gate has completed.
enabled: false
sessionCutoverComplete: false
ldap:
enabled: false
providerCode: ldap-main
displayName: "Corporate Directory"
authority: corp-directory
url: ""
startTls: false
directoryType: OPENLDAP
baseDn: ""
userSearchBase: ""
userSearchFilter: "(uid={0})"
bindDn: ""
subjectAttribute: ""
subjectType: ""
usernameAttribute: uid
displayNameAttribute: displayName
emailAttribute: mail
avatarUrlAttribute: ""
emailAuthoritative: false
connectTimeout: PT5S
readTimeout: PT10S
poolWaitTimeout: PT2S
maxConcurrentRequests: 16
maxAttributeValues: 16
cas:
enabled: false
providerCode: cas-main
Expand Down Expand Up @@ -119,6 +143,7 @@ secrets:
downloadAnonCookieSecret: ""
oauth2GithubClientId: ""
oauth2GithubClientSecret: ""
ldapBindPassword: ""
scannerLlmApiKey: ""
scannerLlmBaseUrl: ""
scannerLlmModel: ""
Expand Down
Loading
Loading