diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 611121a..65cf658 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -45,6 +45,7 @@ jobs: owner: ModelEngine-Group repository: 'DataMate' access-token: ${{ secrets.ACCESS_TOKEN }} + branch: develop/sealed-secrets - name: DataMate Package run: | @@ -57,6 +58,27 @@ jobs: sed -i "s/latest/${{ inputs.version }}/g" helm/datamate/values.yaml sed -i 's#HOME_PAGE_URL: *""#HOME_PAGE_URL: "/data/management"#g' helm/datamate/values.yaml + - name: Sealed-Secrets Helm Chart + run: | + mkdir -p helm/sealed-secrets + helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets + helm repo update + helm pull sealed-secrets/sealed-secrets --version 2.18.6 -d helm/sealed-secrets + + - name: Download kubeseal + run: | + mkdir -p tools/bin + if [ "${{ inputs.aarch }}" = "arm64" ]; then + KUBESEAL_ARCH="arm64" + else + KUBESEAL_ARCH="amd64" + fi + wget -q "https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.27.2/kubeseal-0.27.2-linux-${KUBESEAL_ARCH}.tar.gz" + tar xzf kubeseal-0.27.2-linux-${KUBESEAL_ARCH}.tar.gz kubeseal + mv kubeseal tools/bin/kubeseal + chmod +x tools/bin/kubeseal + rm kubeseal-0.27.2-linux-${KUBESEAL_ARCH}.tar.gz + - name: DeerFlow Package if: inputs.deer-flow == true run: | @@ -95,6 +117,10 @@ jobs: docker pull quay.io/kuberay/operator:v1.4.2 --platform ${{ inputs.aarch }} docker save -o images/datamate/kuberay-operator.tar quay.io/kuberay/operator:v1.4.2 docker rmi quay.io/kuberay/operator:v1.4.2 + # Sealed-secrets controller (Docker v2 manifest for offline compat) + docker pull bitnami/sealed-secrets-controller:0.27.0 --platform ${{ inputs.aarch }} + docker save -o images/datamate/sealed-secrets-controller.tar bitnami/sealed-secrets-controller:0.27.0 + docker rmi bitnami/sealed-secrets-controller:0.27.0 - name: Download DeerFlow Image if: inputs.deer-flow == true @@ -144,4 +170,4 @@ jobs: path: | helm/ images/ - tools/ \ No newline at end of file + tools/ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e58e5a2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.env +.idea diff --git a/tools/generate-sealed-secrets.sh b/tools/generate-sealed-secrets.sh new file mode 100755 index 0000000..d6d9810 --- /dev/null +++ b/tools/generate-sealed-secrets.sh @@ -0,0 +1,196 @@ +#!/bin/bash +### Generate SealedSecret resources using the cluster's sealed-secrets controller. +### +### Usage: +### ./generate-sealed-secrets.sh [--namespace ] [--controller-name ] +### [--skip-label-studio] [--skip-milvus] [--cleanup] +### +### Password sourcing priority: +### 1. Interactive prompt +### 2. Auto-generated random value (for JWT_SECRET, MinIO keys, tokens) +### +### Required env vars (entered interactively): +### DB_PASSWORD, CERT_PASS, DOMAIN, HOME_PAGE_URL +### LABEL_STUDIO_PASSWORD, POSTGRE_PASSWORD +### JWT_SECRET, LABEL_STUDIO_USER_TOKEN, MINIO_ACCESS_KEY, MINIO_SECRET_KEY +### (empty = auto-generate where applicable) + +set -e + +NAMESPACE="datamate" +CONTROLLER_NAME="sealed-secrets" +SKIP_LABEL_STUDIO=false +SKIP_MILVUS=false +CLEANUP=false + +WORK_DIR="$(cd "$(dirname "$0")" && pwd)" +KUBESEAL="${WORK_DIR}/bin/kubeseal" +TMP_DIR=$(mktemp -d) +trap "rm -rf $TMP_DIR" EXIT + +# ========== Argument Parsing ========== +while [[ "$#" -gt 0 ]]; do + case $1 in + -n|--namespace) NAMESPACE="$2"; shift 2 ;; + --controller-name) CONTROLLER_NAME="$2"; shift 2 ;; + --skip-label-studio) SKIP_LABEL_STUDIO=true; shift ;; + --skip-milvus) SKIP_MILVUS=false; shift ;; + --cleanup) CLEANUP=true; shift ;; + *) echo "Unknown parameter: $1"; exit 1 ;; + esac +done + +# ========== Utility Functions ========== +log_info() { echo -e "\033[32m[INFO]\033[0m $*"; } +log_warn() { echo -e "\033[33m[WARN]\033[0m $*"; } +log_error() { echo -e "\033[31m[ERROR]\033[0m $*"; } + +random_hex() { head -c 32 /dev/urandom 2>/dev/null | xxd -p -c 64 || openssl rand -hex 32; } + +# ========== Check sealed-secrets controller ========== +log_info "Waiting for sealed-secrets controller..." +kubectl wait pod -l app.kubernetes.io/instance="${CONTROLLER_NAME}" \ + -n "${NAMESPACE}" --for=condition=Ready --timeout=120s + +# ========== Check kubeseal ========== +if [ ! -f "$KUBESEAL" ]; then + KUBESEAL="$(command -v kubeseal 2>/dev/null || echo "")" +fi +if [ -z "$KUBESEAL" ]; then + log_error "kubeseal not found at ${WORK_DIR}/bin/kubeseal or in PATH" + exit 1 +fi +log_info "Using kubeseal: $KUBESEAL" +chmod +x "$KUBESEAL" 2>/dev/null || true + +# ========== Secret Collection ========== +prompt_or_default() { + local var_name="$1" prompt="$2" gen_random="$3" + if [ "$gen_random" = true ]; then + local generated + generated=$(random_hex) + eval "$var_name=\"$generated\"" + log_info "Auto-generated ${var_name}" + return 0 + fi + # Interactive prompt + local is_sensitive=false + case "$var_name" in + *_PASSWORD|*_SECRET|*_TOKEN|CERT_PASS|DB_PASSWORD|MINIO_*) is_sensitive=true ;; + esac + if [ "$is_sensitive" = true ]; then + read -rsp "Enter ${prompt}: " value + echo "" + else + read -rp "Enter ${prompt}: " value + fi + eval "$var_name=\"$value\"" +} + +log_info "Collecting secrets..." + +# DataMate core secrets +prompt_or_default DB_PASSWORD "database password" false +prompt_or_default CERT_PASS "SSL certificate password (enter to skip)" false +prompt_or_default DOMAIN "domain" false +HOME_PAGE_URL="${HOME_PAGE_URL:-/data/management}" +prompt_or_default JWT_SECRET "JWT secret" true + +# Label Studio secrets +if [ "$SKIP_LABEL_STUDIO" = false ]; then + prompt_or_default LABEL_STUDIO_PASSWORD "Label Studio admin password" false + prompt_or_default POSTGRE_PASSWORD "Label Studio PostgreSQL password (same as DB_PASSWORD)" false + if [ -z "$POSTGRE_PASSWORD" ] && [ -n "$DB_PASSWORD" ]; then + POSTGRE_PASSWORD="$DB_PASSWORD" + log_info "Using DB_PASSWORD as POSTGRE_PASSWORD" + fi + prompt_or_default LABEL_STUDIO_USER_TOKEN "Label Studio API token" true +fi + +# Milvus / MinIO secrets +if [ "$SKIP_MILVUS" = false ]; then + prompt_or_default MINIO_ACCESS_KEY "MinIO access key" true + prompt_or_default MINIO_SECRET_KEY "MinIO secret key" true +fi + +# ========== Generate SealedSecret YAML ========== +SEAL_ARGS="--controller-name=${CONTROLLER_NAME} --namespace=${NAMESPACE} -o yaml" + +create_sealed_secret() { + local secret_name="$1" namespace="$2" output_file="$3" + shift 3 + local raw_secret="${TMP_DIR}/${secret_name}-raw.yaml" + + # Build raw Secret YAML + cat > "$raw_secret" <> "$raw_secret" + done + + "$KUBESEAL" ${SEAL_ARGS} -f "$raw_secret" > "$output_file" + log_info "Created SealedSecret: ${output_file}" +} + +# Datamate secret +create_sealed_secret "datamate-conf" "${NAMESPACE}" "${TMP_DIR}/datamate-sealed.yaml" \ + "DB_PASSWORD=${DB_PASSWORD}" \ + "CERT_PASS=${CERT_PASS}" \ + "DOMAIN=${DOMAIN}" \ + "HOME_PAGE_URL=${HOME_PAGE_URL}" \ + "JWT_SECRET=${JWT_SECRET}" \ + "LABEL_STUDIO_PASSWORD=${LABEL_STUDIO_PASSWORD}" \ + "LABEL_STUDIO_USER_TOKEN=${LABEL_STUDIO_USER_TOKEN}" + +# Label Studio secret +if [ "$SKIP_LABEL_STUDIO" = false ]; then + create_sealed_secret "label-studio-env" "${NAMESPACE}" "${TMP_DIR}/label-studio-sealed.yaml" \ + "POSTGRE_PASSWORD=${POSTGRE_PASSWORD}" \ + "LABEL_STUDIO_PASSWORD=${LABEL_STUDIO_PASSWORD}" \ + "LABEL_STUDIO_USER_TOKEN=${LABEL_STUDIO_USER_TOKEN}" +fi + +# Milvus/MinIO secret +if [ "$SKIP_MILVUS" = false ]; then + create_sealed_secret "milvus-minio-secret" "${NAMESPACE}" "${TMP_DIR}/milvus-sealed.yaml" \ + "accesskey=${MINIO_ACCESS_KEY}" \ + "secretkey=${MINIO_SECRET_KEY}" +fi + +# ========== Apply SealedSecrets ========== +log_info "Applying SealedSecret resources..." + +for f in "$TMP_DIR"/*-sealed.yaml; do + [ -f "$f" ] || continue + kubectl apply -f "$f" -n "${NAMESPACE}" +done + +# ========== Verify ========== +log_info "Verifying secret decryption..." +for secret_name in datamate-conf label-studio-env milvus-minio-secret; do + case "$secret_name" in + label-studio-env) [ "$SKIP_LABEL_STUDIO" = true ] && continue ;; + milvus-minio-secret) [ "$SKIP_MILVUS" = true ] && continue ;; + esac + if kubectl get secret "$secret_name" -n "${NAMESPACE}" > /dev/null 2>&1; then + log_info "✓ Secret ${secret_name} decrypted successfully" + else + log_warn "Secret ${secret_name} not yet available (may need controller restart)" + fi +done + +if [ "$CLEANUP" = true ]; then + rm -f "$TMP_DIR"/*-sealed.yaml "$TMP_DIR"/*-raw.yaml +fi + +log_info "Sealed-secrets generation complete!" diff --git a/tools/install.sh b/tools/install.sh index 57d3ea7..4428c09 100644 --- a/tools/install.sh +++ b/tools/install.sh @@ -234,6 +234,7 @@ function get_cert_pass() { function helm_install() { local release_name="$1" local chart_path="$2" + shift 2 local helm_args=() @@ -242,6 +243,11 @@ function helm_install() { helm_args+=("--namespace" "$NAMESPACE") helm_args+=("--create-namespace") + # Append extra args (e.g., --set key=value) + for arg in "$@"; do + helm_args+=("$arg") + done + log_info "即将执行: helm ${helm_args[*]}" if ! helm "${helm_args[@]}"; then @@ -250,8 +256,31 @@ function helm_install() { fi } +function install_sealed_secrets() { + local chart_tgz + chart_tgz=$(ls "${HELM_PATH}/sealed-secrets/sealed-secrets-"*.tgz 2>/dev/null | head -1) + if [ -z "$chart_tgz" ]; then + log_error "sealed-secrets Helm chart not found in ${HELM_PATH}/sealed-secrets/" + exit 1 + fi + log_info "Installing sealed-secrets controller..." + helm upgrade --install sealed-secrets "$chart_tgz" \ + -n "$NAMESPACE" --create-namespace \ + --set image.registry="${REPO}" \ + --set image.tag=0.27.0 \ + --set image.pullPolicy=IfNotPresent \ + --wait --timeout 120s + log_info "sealed-secrets controller installed." +} + function install_datamate() { - helm_install "datamate" "${HELM_PATH}/datamate" + local extra_args=() + if [ "$DATAMATE_JWT_ENABLE" == "true" ]; then + extra_args+=("--set" "datamate.jwt.enable=true") + fi + helm_install "datamate" "${HELM_PATH}/datamate" \ + --set public.secrets.create=false \ + "${extra_args[@]}" } function install_milvus() { @@ -263,6 +292,17 @@ function install_label_studio() { } function install() { + # 1. Install sealed-secrets controller first + install_sealed_secrets + + # 2. Generate sealed secrets (from .env or interactive input) + log_info "Generating SealedSecret resources..." + bash "${WORK_DIR}/generate-sealed-secrets.sh" \ + -n "$NAMESPACE" \ + $([ "$INSTALL_MILVUS" = false ] && echo "--skip-milvus") \ + $([ "$INSTALL_LABEL_STUDIO" = false ] && echo "--skip-label-studio") + + # 3. Install DataMate components install_datamate if [ "$INSTALL_MILVUS" == "true" ]; then install_milvus @@ -334,7 +374,6 @@ function main() { read_value read_storage_value - get_cert_pass load_images "datamate" if [ "$INSTALL_MILVUS" == "true" ]; then load_images "milvus"