diff --git a/CHANGELOG.md b/CHANGELOG.md index f2af57404..b8a092952 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Added - Webhook proxy security enhancements ([#1398](https://github.com/opendevstack/ods-core/pull/1398/)) - Add client credentials support to external services templates in the ODS API Service ([#1388](https://github.com/opendevstack/ods-core/pull/1388)) +- Clean up configuration ([#1404](https://github.com/opendevstack/ods-core/pull/1404)) ### Changed - Adapt webhook proxy to use HMAC ([#1403](https://github.com/opendevstack/ods-core/pull/1403)) diff --git a/create-projects/Jenkinsfile b/create-projects/Jenkinsfile index 1222af9c1..257454129 100644 --- a/create-projects/Jenkinsfile +++ b/create-projects/Jenkinsfile @@ -243,36 +243,38 @@ podTemplate( def odsConfigPath = "${env.WORKSPACE}/ods-configuration/ods-core.env" def scriptsConfigPath = "${env.WORKSPACE}/post-creation-config/${env.POST_SCRIPTS_CONFIG_FILE}" - withCredentials([usernamePassword(credentialsId: "${credentialsId}", passwordVariable: 'password', usernameVariable: 'username')]) { - withEnv([ - "PROJECT_ID=${projectId}", - "PROJECT_GROUPS=${projectGroups}", - "PROJECT_ADMIN=${projectAdmins}", - "ODS_NAMESPACE=${odsNamespace}", - "ODS_IMAGE_TAG=${odsImageTag}", - "ODS_GIT_REF=${odsGitRef}", - "ODS_BITBUCKET_PROJECT=${odsBitbucketProject}", - "BITBUCKET_URL=${bitbucketUrl}", - "DOCKER_REGISTRY=${dockerRegistry}", - "CD_USERNAME=${username}", - "CD_PASSWORD=${password}", - "CD_USER_CREDENTIALS_ID=${credentialsId}" - ]) { - echo("Environment variables for scripts:") - - for (script in scripts) { - def scriptName = script.trim() - def scriptPath = "post-project-creation/${scriptName}" - - echo("Executing script: ${scriptName}") - - sh( - script: """ - "${scriptPath}" "${odsConfigPath}" "${scriptsConfigPath}" - """, - label: "Execute script: ${scriptName}" - ) - } + withCredentials([string(credentialsId: "ods-jenkins-webhook-secret", variable: 'WEBHOOK_HMAC_KEY')]) { + withCredentials([usernamePassword(credentialsId: "${credentialsId}", passwordVariable: 'password', usernameVariable: 'username')]) { + withEnv([ + "PROJECT_ID=${projectId}", + "PROJECT_GROUPS=${projectGroups}", + "PROJECT_ADMIN=${projectAdmins}", + "ODS_NAMESPACE=${odsNamespace}", + "ODS_IMAGE_TAG=${odsImageTag}", + "ODS_GIT_REF=${odsGitRef}", + "ODS_BITBUCKET_PROJECT=${odsBitbucketProject}", + "BITBUCKET_URL=${bitbucketUrl}", + "DOCKER_REGISTRY=${dockerRegistry}", + "CD_USERNAME=${username}", + "CD_PASSWORD=${password}", + "CD_USER_CREDENTIALS_ID=${credentialsId}" + ]) { + echo("Environment variables for scripts:") + + for (script in scripts) { + def scriptName = script.trim() + def scriptPath = "post-project-creation/${scriptName}" + + echo("Executing script: ${scriptName}") + + sh( + script: """ + "${scriptPath}" "${odsConfigPath}" "${scriptsConfigPath}" + """, + label: "Execute script: ${scriptName}" + ) + } + } } } } diff --git a/jenkins/webhook-proxy/generate_hmac_secret.sh b/jenkins/webhook-proxy/generate_hmac_secret.sh index 04062ff9f..ead7fb451 100755 --- a/jenkins/webhook-proxy/generate_hmac_secret.sh +++ b/jenkins/webhook-proxy/generate_hmac_secret.sh @@ -4,27 +4,12 @@ set -euo pipefail # Generates a high-entropy secret suitable for WEBHOOK_HMAC_SECRET. # Usage: # bash generate_hmac_secret.sh -# bash generate_hmac_secret.sh 64 - -length="${1:-64}" - -if ! [[ "$length" =~ ^[0-9]+$ ]]; then - echo "error: length must be a positive integer" >&2 - exit 1 -fi - -if (( length < 32 )); then - echo "error: length must be at least 32 characters" >&2 - exit 1 -fi if command -v openssl >/dev/null 2>&1; then - secret=$(openssl rand -hex $(((length + 1) / 2)) | cut -c1-"$length") -elif [[ -r /dev/urandom ]]; then - secret=$(tr -dc 'a-f0-9' &2 + echo "error: openssl is not available" >&2 exit 1 fi -printf 'WEBHOOK_HMAC_SECRET=%s\n' "$secret" \ No newline at end of file +printf 'WEBHOOK_HMAC_SECRET=%s\n' "$secret" diff --git a/ods-provisioning-app/ocp-config/Tailorfile b/ods-provisioning-app/ocp-config/Tailorfile index 9064992e8..6c59756c4 100644 --- a/ods-provisioning-app/ocp-config/Tailorfile +++ b/ods-provisioning-app/ocp-config/Tailorfile @@ -8,4 +8,4 @@ preserve pvc:/metadata/annotations/volume.beta.kubernetes.io/storage-class preserve-immutable-fields true ignore-unknown-parameters true -cm,dc,pvc,route,secret,svc,is +pvc,is,cm,secret,dc,svc,route diff --git a/ods-provisioning-app/ocp-config/cm.yml b/ods-provisioning-app/ocp-config/cm.yml index 830e2a885..7f4c41841 100644 --- a/ods-provisioning-app/ocp-config/cm.yml +++ b/ods-provisioning-app/ocp-config/cm.yml @@ -42,26 +42,12 @@ parameters: - name: PROV_APP_ATLASSIAN_DOMAIN required: true description: the domain of the atlassian toolsuite needed for single signon cookies -- name: PROV_APP_CROWD_PASSWORD - required: true - description: password of the crowd app to authenticate the provision app against -- name: PROV_APP_JASYPT_PASSWORD - required: true - name: PROV_APP_MAIL_HOST required: true description: The hostname of the mailserver -- name: PROV_APP_MAIL_PASSWORD - required: true - description: The password to authenticate against the mail server -- name: PROV_APP_MAIL_USERNAME - required: true - description: The username to authenticate against the mail server - name: PROV_APP_CROWD_ADMIN_GROUP required: true description: The crowd admin group name -- name: PIPELINE_TRIGGER_SECRET - required: true - description: The trigger secret to pass to the webhook proxy - name: PROV_APP_LOG_LEVEL_ATLASSIAN_CROWD required: true description: Log level of Atlassian crowd package @@ -232,8 +218,6 @@ objects: # list of supported webhook events openshift.jenkins.project.webhookproxy.events=${WEBHOOK_PROXY_EVENTS} - openshift.jenkins.trigger.secret=${PIPELINE_TRIGGER_SECRET} - artifact.group.pattern=${PROV_APP_PACKAGE_PREFIX}.%s # Cookie Domain @@ -261,8 +245,6 @@ objects: # crowd properties crowd.local.directory=LocalDirectory - crowd.application.name=provision - crowd.application.password=${PROV_APP_CROWD_PASSWORD} crowd.server.url=${CROWD_URL}/services/ crowd.cookie.domain=${OPENSHIFT_APPS_BASEDOMAIN} @@ -272,8 +254,6 @@ objects: # local storage project.storage.local=/opt/provision/history/ - jasypt.encryptor.password=${PROV_APP_JASYPT_PASSWORD} - # mail properties # enable mail sendout with project details mail.enabled=true @@ -288,8 +268,6 @@ objects: spring.mail.smtps.connectiontimeout=1000 spring.mail.properties.mail.smtp.ssl.enable=true spring.mail.properties.mail.smtp.auth=true - spring.mail.username=${PROV_APP_MAIL_USERNAME} - spring.mail.password=${PROV_APP_MAIL_PASSWORD} provison.mail.sender=provision@${PROV_APP_MAIL_HOST} spring.main.allow-bean-definition-overriding=true diff --git a/ods-provisioning-app/ocp-config/dc.yml b/ods-provisioning-app/ocp-config/dc.yml index af8584d44..93a23e853 100644 --- a/ods-provisioning-app/ocp-config/dc.yml +++ b/ods-provisioning-app/ocp-config/dc.yml @@ -54,6 +54,9 @@ objects: memory: ${PROV_APP_MEMORY_REQUEST} terminationMessagePath: /dev/termination-log terminationMessagePolicy: File + envFrom: + - secretRef: + name: ods-provisioning-app volumeMounts: - mountPath: /opt/provision/history name: volume-history diff --git a/ods-provisioning-app/ocp-config/secret.yml b/ods-provisioning-app/ocp-config/secret.yml new file mode 100644 index 000000000..2e39f8c66 --- /dev/null +++ b/ods-provisioning-app/ocp-config/secret.yml @@ -0,0 +1,32 @@ +apiVersion: template.openshift.io/v1 +kind: Template +parameters: + - name: PROV_APP_ODS_WEBHOOK_HMAC_SECRET + required: true + - name: PROV_APP_CROWD_PASSWORD + required: true + description: password of the crowd app to authenticate the provision app against + - name: PROV_APP_JASYPT_PASSWORD + required: true + - name: PROV_APP_MAIL_PASSWORD + required: true + description: The password to authenticate against the mail server + - name: PROV_APP_MAIL_USERNAME + required: true + description: The username to authenticate against the mail server + - name: PROV_APP_CROWD_APPLICATION_NAME + value: provision +labels: + app: ods-provisioning-app +objects: + - apiVersion: v1 + kind: Secret + metadata: + name: ods-provisioning-app + stringData: + OPENSHIFT_JENKINS_TRIGGER_HMAC_KEY=${PROV_APP_ODS_WEBHOOK_HMAC_SECRET} + CROWD_APPLICATION_NAME=${PROV_APP_CROWD_APPLICATION_NAME} + CROWD_APPLICATION_PASSWORD=${PROV_APP_CROWD_PASSWORD} + JASYPT_ENCRYPTOR_PASSWORD=${PROV_APP_JASYPT_PASSWORD} + SPRING_MAIL_USERNAME=${PROV_APP_MAIL_USERNAME} + SPRING_MAIL_PASSWORD=${PROV_APP_MAIL_PASSWORD} diff --git a/scripts/migrate-provapp-secrets.sh b/scripts/migrate-provapp-secrets.sh new file mode 100755 index 000000000..bccab6677 --- /dev/null +++ b/scripts/migrate-provapp-secrets.sh @@ -0,0 +1,170 @@ +#!/bin/bash + +set -euo pipefail + +CONFIGMAP_NAME="application.properties" +SECRET_NAME="ods-provisioning-app" +DEPLOYMENT_CONFIG="ods-provisioning-app" + +usage() { + echo "Usage: $0 -n " + exit 1 +} + +NAMESPACE="" + +while [[ $# -gt 0 ]]; do + case "$1" in + -n|--namespace) + NAMESPACE="$2" + shift 2 + ;; + *) + echo "Unknown option: $1" + usage + ;; + esac +done + +if [[ -z "$NAMESPACE" ]]; then + echo "Namespace is required." + usage +fi + +# Properties to migrate from ConfigMap to Secret +PROPERTIES=( + "crowd.application.name" + "crowd.application.password" + "jira.admin_user" + "jira.admin_password" + "confluence.admin_user" + "confluence.admin_password" + "bitbucket.admin_user" + "bitbucket.admin_password" + "spring.mail.username" + "spring.mail.password" + "jasypt.encryptor.password" +) + +TMP_FILE=$(mktemp) + +cleanup() { + rm -f "$TMP_FILE" +} +trap cleanup EXIT + +echo "Reading application.properties from ConfigMap ${CONFIGMAP_NAME}..." + +oc get configmap "${CONFIGMAP_NAME}" \ + -n "${NAMESPACE}" \ + -o jsonpath='{.data.properties}' > "${TMP_FILE}" + +# Create secret if it does not exist +if ! oc get secret "${SECRET_NAME}" -n "${NAMESPACE}" >/dev/null 2>&1; then + echo "Creating secret ${SECRET_NAME}..." + oc create secret generic "${SECRET_NAME}" \ + -n "${NAMESPACE}" +fi + +MODIFIED=false + +for PROPERTY in "${PROPERTIES[@]}"; do + + # Find first non-commented occurrence + LINE=$(grep -E "^[[:space:]]*${PROPERTY}[[:space:]]*=" "${TMP_FILE}" || true) + + if [[ -z "${LINE}" ]]; then + echo "Property ${PROPERTY} not found" + continue + fi + + VALUE="${LINE#*=}" + + # Spring relaxed binding: + # my.property.name -> MY_PROPERTY_NAME + ENV_VAR=$(echo "${PROPERTY}" | tr '[:lower:].-' '[:upper:]__') + + echo "Found ${PROPERTY} -> ${ENV_VAR}" + + # Check if entry already exists in secret + if oc get secret "${SECRET_NAME}" \ + -n "${NAMESPACE}" \ + -o jsonpath="{.data.${ENV_VAR}}" 2>/dev/null | grep -q .; then + + echo "Secret key ${ENV_VAR} already exists, leaving untouched" + + else + echo "Adding ${ENV_VAR} to secret" + + PATCH=$(jq -n \ + --arg key "$ENV_VAR" \ + --arg value "$VALUE" \ + '{stringData:{($key):$value}}') + + oc patch secret "${SECRET_NAME}" \ + -n "${NAMESPACE}" \ + --type merge \ + -p "$PATCH" + fi + + # Remove property from application.properties + sed -i "/^[[:space:]]*${PROPERTY}[[:space:]]*=/d" "${TMP_FILE}" + + MODIFIED=true + +done + +if [[ "${MODIFIED}" == "true" ]]; then + echo "Updating ConfigMap ${CONFIGMAP_NAME}..." + + oc create configmap "${CONFIGMAP_NAME}" \ + --from-file=properties="${TMP_FILE}" \ + -n "${NAMESPACE}" \ + --dry-run=client -o yaml | oc apply -f - + + echo "ConfigMap updated" +else + echo "No changes required" +fi + +echo "Ensuring DeploymentConfig ${DEPLOYMENT_CONFIG} exposes secret values as environment variables..." + +if oc get dc "${DEPLOYMENT_CONFIG}" -n "${NAMESPACE}" >/dev/null 2>&1; then + + if oc get dc "${DEPLOYMENT_CONFIG}" \ + -n "${NAMESPACE}" \ + -o jsonpath='{.spec.template.spec.containers[0].envFrom[*].secretRef.name}' \ + | grep -qw "${SECRET_NAME}"; then + + echo "Secret ${SECRET_NAME} already referenced in DeploymentConfig" + + else + + oc patch dc "${DEPLOYMENT_CONFIG}" \ + -n "${NAMESPACE}" \ + --type=json \ + -p="$(cat <&2 || echo "$current_properties" >&2 +echo "$current_properties" | jq . >&2 || { + echo "$current_properties" >&2 + exit 1 +} # Extract the existing property if it exists existing_property=$(echo "$current_properties" | jq --arg key "$PROPERTY_KEY" '.[] | select(.propertyKey == $key)' 2>/dev/null || echo "") diff --git a/scripts/webhook-proxy-migration/migrate-bitbucket-webhook-hmac.sh b/scripts/webhook-proxy-migration/migrate-bitbucket-webhook-hmac.sh index c10e9dfe1..f8b719b1d 100755 --- a/scripts/webhook-proxy-migration/migrate-bitbucket-webhook-hmac.sh +++ b/scripts/webhook-proxy-migration/migrate-bitbucket-webhook-hmac.sh @@ -163,7 +163,7 @@ list_repos() { local limit=100 while true; do local response - response="$(curl_api GET "/rest/api/latest/projects/${PROJECT_KEY}/repos?limit=${limit}&start=${start}")" + response="$(curl_api GET "/rest/api/latest/projects/${PROJECT_KEY}/repos?limit=${limit}&start=${start}")" || exit 1 jq -r '.values[].slug' <<<"$response" @@ -244,13 +244,23 @@ log "Backup file: $BACKUP_FILE" processed_repos=0 updated_hooks=0 -while IFS= read -r repo_slug; do +if ! repos_text="$(list_repos)"; then + log "Failed to retrieve repository list" + exit 1 +fi + +mapfile -t repos <<< "$repos_text" + +for repo_slug in "${repos[@]}"; do repo_slug=${repo_slug%$'\r'} [[ -z "$repo_slug" ]] && continue processed_repos=$((processed_repos + 1)) log "Scanning repo: ${repo_slug}" - hooks_json="$(curl_api GET "/rest/api/latest/projects/${PROJECT_KEY}/repos/${repo_slug}/webhooks")" + if ! hooks_json="$(curl_api GET "/rest/api/latest/projects/${PROJECT_KEY}/repos/${repo_slug}/webhooks")"; then + log "Failed to retrieve webhooks for ${repo_slug}. Skipping repository." + continue + fi # Some Bitbucket/plugin setups may return non-JSON bodies for specific repos. # Skip those repos with a clear log instead of aborting the whole migration. @@ -266,8 +276,14 @@ while IFS= read -r repo_slug; do continue fi - # Process only webhooks that target the webhook proxy host pattern. - mapfile -t hook_objs < <(jq -c --arg m "$URL_MATCH" '.values[] | select((.url // "") | contains($m))' <<<"$hooks_json") + if ! mapfile -t hook_objs < <( + jq -c --arg m "$URL_MATCH" \ + '.values[] | select((.url // "") | contains($m))' \ + <<<"$hooks_json" + ); then + log "Failed to parse webhook list for ${repo_slug}. Skipping repository." + continue + fi if [[ ${#hook_objs[@]} -eq 0 ]]; then log "No matching webhooks in ${repo_slug}" @@ -275,8 +291,17 @@ while IFS= read -r repo_slug; do fi for hook_obj in "${hook_objs[@]}"; do - hook_id="$(jq -r '.id | tostring' <<<"$hook_obj")" - old_url="$(jq -r '.url' <<<"$hook_obj")" + + if ! hook_id="$(jq -r '.id | tostring' <<<"$hook_obj")"; then + log "Failed to read webhook id in ${repo_slug}. Skipping webhook." + continue + fi + + if ! old_url="$(jq -r '.url' <<<"$hook_obj")"; then + log "Failed to read webhook URL for webhook ${hook_id} in ${repo_slug}. Skipping webhook." + continue + fi + proxy_project_key="$(extract_proxy_project_key "$old_url")" if [[ -n "$proxy_project_key" && "${proxy_project_key,,}" != "${PROJECT_KEY,,}" ]]; then @@ -286,24 +311,33 @@ while IFS= read -r repo_slug; do fi new_url="$(printf '%s' "$old_url" | remove_trigger_secret)" - hook_secret_key="$(detect_secret_config_key "$hook_obj")" - - payload="$(jq -cn \ - --argjson src "$hook_obj" \ - --arg newUrl "$new_url" \ - --arg key "$hook_secret_key" \ - --arg secret "$WEBHOOK_SECRET" ' - { - name: $src.name, - url: $newUrl, - active: $src.active, - events: $src.events, - sslVerificationRequired: $src.sslVerificationRequired, - configuration: (($src.configuration // {}) + {($key): $secret}), - credentials: $src.credentials - } - | with_entries(select(.value != null)) - ')" + + if ! hook_secret_key="$(detect_secret_config_key "$hook_obj")"; then + log "Failed to determine secret key for webhook ${hook_id} in ${repo_slug}. Skipping webhook." + continue + fi + + if ! payload="$( + jq -cn \ + --argjson src "$hook_obj" \ + --arg newUrl "$new_url" \ + --arg key "$hook_secret_key" \ + --arg secret "$WEBHOOK_SECRET" ' + { + name: $src.name, + url: $newUrl, + active: $src.active, + events: $src.events, + sslVerificationRequired: $src.sslVerificationRequired, + configuration: (($src.configuration // {}) + {($key): $secret}), + credentials: $src.credentials + } + | with_entries(select(.value != null)) + ' + )"; then + log "Failed to build payload for webhook ${hook_id} in ${repo_slug}. Skipping webhook." + continue + fi jq -cn --arg repo "$repo_slug" --argjson hook "$hook_obj" '{repo:$repo,hook:$hook}' >> "$BACKUP_FILE" @@ -320,14 +354,21 @@ while IFS= read -r repo_slug; do fi if [[ "$APPLY" == true ]]; then - curl_api PUT "/rest/api/latest/projects/${PROJECT_KEY}/repos/${repo_slug}/webhooks/${hook_id}" "$payload" >/dev/null + if ! curl_api \ + PUT \ + "/rest/api/latest/projects/${PROJECT_KEY}/repos/${repo_slug}/webhooks/${hook_id}" \ + "$payload" >/dev/null; then + log "Failed to update webhook ${hook_id} in ${repo_slug}. Continuing." + continue + fi + updated_hooks=$((updated_hooks + 1)) log "Updated webhook ${hook_id} in ${repo_slug}" else log "DRY-RUN would update webhook ${hook_id} in ${repo_slug}" fi done -done < <(list_repos) +done log "Processed repositories: ${processed_repos}" log "Updated webhooks: ${updated_hooks}" diff --git a/scripts/webhook-proxy-migration/migrate-jira-webhook-properties.sh b/scripts/webhook-proxy-migration/migrate-jira-webhook-properties.sh index 2c3d5ce1a..072b219b7 100755 --- a/scripts/webhook-proxy-migration/migrate-jira-webhook-properties.sh +++ b/scripts/webhook-proxy-migration/migrate-jira-webhook-properties.sh @@ -142,7 +142,9 @@ else fi log "Updating WEBHOOK_PROXY.URL in Jira..." - "${jira_url_cmd[@]}" + if ! "${jira_url_cmd[@]}"; then + log "Warning: Failed to update WEBHOOK_PROXY.URL. Continuing with WEBHOOK_PROXY.SECRET update." + fi fi jira_secret_cmd=( diff --git a/scripts/webhook-proxy-migration/migrate-openshift-webhook-secret.sh b/scripts/webhook-proxy-migration/migrate-openshift-webhook-secret.sh index 8c471f2b6..d25aede69 100755 --- a/scripts/webhook-proxy-migration/migrate-openshift-webhook-secret.sh +++ b/scripts/webhook-proxy-migration/migrate-openshift-webhook-secret.sh @@ -47,7 +47,7 @@ Required: Behavior: - Target namespace is always: -cd - - If secret/webhook-proxy does not exist, the namespace is skipped. + - If secret/webhook-proxy does not exist, it will be created. Examples: scripts/migrate-openshift-webhook-secret.sh --project foo --hmac-secret "$(openssl rand -hex 32)" --allowed-ip-ranges 10.0.0.0/8 @@ -121,34 +121,69 @@ NAMESPACE="${PROJECT}-cd" log "Processing namespace: ${NAMESPACE}" +ROLLOUT_PAUSED_BY_SCRIPT=false + +rollout() { + "$OC_BIN" -n "$NAMESPACE" rollout latest dc/webhook-proxy >/dev/null 2>&1 || true + if [[ "$ROLLOUT_PAUSED_BY_SCRIPT" == true ]]; then + log "Restarting webhook-proxy in ${NAMESPACE}..." + "$OC_BIN" -n "$NAMESPACE" rollout resume dc/webhook-proxy >/dev/null 2>&1 || true + ROLLOUT_PAUSED_BY_SCRIPT=false + fi +} + +trap rollout EXIT + +SECRET_EXISTS=true + if ! "$OC_BIN" -n "$NAMESPACE" get secret webhook-proxy >/dev/null 2>&1; then - log "Skipping ${NAMESPACE}: secret/webhook-proxy not found" - log "Completed" - exit 0 + SECRET_EXISTS=false + log "secret/webhook-proxy does not exist in ${NAMESPACE}" fi if [[ "$APPLY" == true ]]; then - patch_payload="$(jq -cn --arg value "$HMAC_SECRET_B64" '{data:{"webhook-hmac-secret":$value}}')" - "$OC_BIN" -n "$NAMESPACE" patch secret webhook-proxy --type merge -p "$patch_payload" >/dev/null - log "Patched secret/webhook-proxy in ${NAMESPACE}" - "$OC_BIN" -n "$NAMESPACE" rollout pause dc/webhook-proxy + if [[ "$SECRET_EXISTS" == true ]]; then + patch_payload="$(jq -cn --arg value "$HMAC_SECRET_B64" '{data:{"webhook-hmac-secret":$value}}')" + "$OC_BIN" -n "$NAMESPACE" patch secret webhook-proxy --type merge -p "$patch_payload" >/dev/null + log "Patched secret/webhook-proxy in ${NAMESPACE}" + else + "$OC_BIN" -n "$NAMESPACE" create secret generic webhook-proxy \ + --from-literal=webhook-hmac-secret="$HMAC_SECRET_RAW" \ + >/dev/null + + log "Created secret/webhook-proxy in ${NAMESPACE}" + fi + + if [[ "$("$OC_BIN" -n "$NAMESPACE" get dc webhook-proxy -o jsonpath='{.spec.paused}' 2>/dev/null)" != "true" ]]; then + "$OC_BIN" -n "$NAMESPACE" rollout pause dc/webhook-proxy && ROLLOUT_PAUSED_BY_SCRIPT=true + fi "$OC_BIN" -n "$NAMESPACE" set env dc/webhook-proxy ALLOWED_WEBHOOK_IP_RANGES="${ALLOWED_IP_RANGES}" log "Added environment variable ALLOWED_WEBHOOK_IP_RANGES=${ALLOWED_IP_RANGES} to deployment config webhook-proxy in ${NAMESPACE}" "$OC_BIN" -n "$NAMESPACE" set env dc/webhook-proxy WEBHOOK_HMAC_SECRET- || true + patch_payload='[{ "op": "add", "path": "/spec/template/spec/containers/0/env/-", "value": { "name": "WEBHOOK_HMAC_SECRET", "valueFrom": { "secretKeyRef": { "name": "webhook-proxy", "key": "webhook-hmac-secret" }}}}]' + "$OC_BIN" -n "$NAMESPACE" patch dc/webhook-proxy --type json -p "$patch_payload" + log "Added environment variable WEBHOOK_HMAC_SECRET to dc/webhook-proxy in ${NAMESPACE}" - log "Restarting webhook-proxy in ${NAMESPACE}..." - "$OC_BIN" -n "$NAMESPACE" rollout resume dc/webhook-proxy + rollout + else - log "DRY-RUN would patch secret in ${NAMESPACE}" + + if [[ "$SECRET_EXISTS" == true ]]; then + log "DRY-RUN would patch secret/webhook-proxy in ${NAMESPACE}" + else + log "DRY-RUN would create secret/webhook-proxy in ${NAMESPACE}" + fi + log "DRY-RUN would add the environment variable ALLOWED_WEBHOOK_IP_RANGES=${ALLOWED_IP_RANGES} to deployment config webhook-proxy in ${NAMESPACE}" log "DRY-RUN would add the environment variable WEBHOOK_HMAC_SECRET to deployment config webhook-proxy in ${NAMESPACE}" - log "DRY-RUN Would restart the Webhook Proxy" + log "DRY-RUN would restart the Webhook Proxy" + fi log "Completed" diff --git a/scripts/webhook-proxy-migration/migrate-webhooks.sh b/scripts/webhook-proxy-migration/migrate-webhooks.sh index 69f8c948b..168ca6984 100755 --- a/scripts/webhook-proxy-migration/migrate-webhooks.sh +++ b/scripts/webhook-proxy-migration/migrate-webhooks.sh @@ -84,6 +84,78 @@ run_jira_migration() { "${jira_cmd[@]}" } +failed_projects=() +successful_projects=() +skipped_projects=() + +process_project() { + local project="$1" + + log "Processing project: ${project}" + + # Skip projects that do not have a webhook-proxy deployment config + if ! "$OC_BIN" -n "${project}-cd" get dc webhook-proxy >/dev/null 2>&1; then + log "Skipping project ${project}: deployment config 'webhook-proxy' not found" + return 2 + fi + + # Skip projects that do not have a webhook-proxy deployment config + if "$OC_BIN" -n "${project}-cd" get dc jira >/dev/null 2>&1; then + log "Skipping project ${project}: it has its own Jira instance" + return 2 + fi + + hmac_secret="$(openssl rand -base64 32 | tr '/' '_')" + log "Generated HMAC secret for project ${project}" + + bitbucket_cmd=( + "$BITBUCKET_SCRIPT" + --base-url "$BASE_URL" + --project "$project" + --webhook-secret "$hmac_secret" + --url-match "$URL_MATCH" + ) + + if [[ -n "$TOKEN" ]]; then + bitbucket_cmd+=(--token "$TOKEN") + else + bitbucket_cmd+=(--username "$USERNAME" --password "$PASSWORD") + fi + + [[ "$APPLY" == true ]] && bitbucket_cmd+=(--apply) + + log "Running Bitbucket webhook migration for ${project}" + "${bitbucket_cmd[@]}" || exit 1 + + proxy_cmd=( + "$PROXY_SCRIPT" + --oc-bin "$OC_BIN" + --project "$project" + --hmac-secret "$hmac_secret" + --allowed-ip-ranges "$ALLOWED_IP_RANGES" + ) + [[ "$APPLY" == true ]] && proxy_cmd+=(--apply) + + log "Running proxy secret migration for ${project}" + "${proxy_cmd[@]}" + + run_jira_migration "$project" "$hmac_secret" + + log "Running ProvApp project migration for ${project}" + if [[ "$APPLY" == true ]]; then + curl -fsS -X PUT "${PROVAPP_BASE_URL}/api/v2/project/${project}/webhookProxyHmacKey" \ + -d "$hmac_secret" \ + -H "Accept: application/json" \ + -H "Content-Type: text/plain" \ + -u "${PROVAPP_USER}:${PROVAPP_PASSWORD}" > /dev/null + + log "HMAC key for project ${project} has been successfully updated" + else + log "DRY-RUN Would update HMAC key for project ${project}" + log "DRY-RUN Would restart the Provisioning App" + fi +} + while [[ $# -gt 0 ]]; do case "$1" in --projects-file) @@ -214,60 +286,34 @@ log "Projects loaded: ${#PROJECTS[@]}" log "Mode: $( [[ "$APPLY" == true ]] && echo apply || echo dry-run )" for project in "${PROJECTS[@]}"; do - log "Processing project: ${project}" - - hmac_secret="$(openssl rand -base64 32)" - log "Generated HMAC secret for project ${project}" - - proxy_cmd=( - "$PROXY_SCRIPT" - --oc-bin "$OC_BIN" - --project "$project" - --hmac-secret "$hmac_secret" - --allowed-ip-ranges "$ALLOWED_IP_RANGES" - ) - if [[ "$APPLY" == true ]]; then - proxy_cmd+=(--apply) - fi - - log "Running proxy secret migration for ${project}" - "${proxy_cmd[@]}" - - bitbucket_cmd=( - "$BITBUCKET_SCRIPT" - --base-url "$BASE_URL" - --project "$project" - --webhook-secret "$hmac_secret" - --url-match "$URL_MATCH" - ) - if [[ -n "$TOKEN" ]]; then - bitbucket_cmd+=(--token "$TOKEN") + if ( + process_project "$project" + ); then + successful_projects+=("$project") + log "Project ${project} completed successfully" else - bitbucket_cmd+=(--username "$USERNAME" --password "$PASSWORD") - fi - if [[ "$APPLY" == true ]]; then - bitbucket_cmd+=(--apply) - fi - - log "Running Bitbucket webhook migration for ${project}" - "${bitbucket_cmd[@]}" + rc=$? - run_jira_migration "$project" "$hmac_secret" - - log "Running ProvApp project migration for ${project}" - if [[ "$APPLY" == true ]]; then - log "Updating HMAC key for project ${project}" - curl -fsS -X PUT "${PROVAPP_BASE_URL}/api/v2/project/${project}/webhookProxyHmacKey" -d "$hmac_secret" \ - -H "Accept: application/json" \ - -H "Content-Type: text/plain" \ - -u "${PROVAPP_USER}:${PROVAPP_PASSWORD}" > /dev/null - log "HMAC key for project ${project} has been successfully updated" - log "Restarting ods-provisioning-app" - oc -n "${project}-cd" rollout latest dc/ods-provisioning-app || true - else - log "DRY-RUN Would update HMAC key for project ${project}" - log "DRY-RUN Would restart the Provisioning App" + if [[ $rc -eq 2 ]]; then + skipped_projects+=("$project") + log "Project ${project} skipped" + else + log "ERROR: Project ${project} failed" + failed_projects+=("$project") + fi fi done log "All projects processed" + +log "Successful projects: ${#successful_projects[@]}" +printf '%s\n' "${successful_projects[@]}" + +log "Skipped projects: ${#skipped_projects[@]}" +printf '%s\n' "${skipped_projects[@]}" + +if [[ ${#failed_projects[@]} -gt 0 ]]; then + log "Failed projects: ${#failed_projects[@]}" + printf '%s\n' "${failed_projects[@]}" + exit 1 +fi