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
24 changes: 23 additions & 1 deletion bin/installAll.sh
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ STARTUP_WAVES=(
"mc-infra-connector mc-infra-manager mc-iam-manager mc-iam-manager-post-initial"
"mc-data-manager mc-web-console-api mc-web-console-front"
"mc-observability-manager mc-observability-front mc-observability-insight mc-observability-insight-scheduler mc-observability-mcp-grafana mc-observability-mcp-maria mc-observability-mcp-influx mc-observability-log-collector"
"mc-workflow-manager-jenkins"
"mc-application-manager mc-workflow-manager mc-cost-optimizer-fe"
)

Expand Down Expand Up @@ -190,6 +191,17 @@ check_post_initial() {
fi
}

register_jenkins_credentials() {
local bootstrap_script="$PROJECT_ROOT_ABS/tool/jenkins/register-credentials.sh"

echo ""
echo "Registering Jenkins credentials..."
if ! bash "$bootstrap_script"; then
echo "Error: Jenkins credential registration failed." >&2
return 1
fi
}

# =============================================================================

# Save current directory at script start
Expand Down Expand Up @@ -544,12 +556,19 @@ case $RUN_MODE in
wave_num=$((wave_num + 1))
echo ""
echo "---- Wave $wave_num/${#STARTUP_WAVES[@]}: $wave_services ----"
./mcc infra run -s "$wave_services"
if [ "$wave_num" -lt "${#STARTUP_WAVES[@]}" ]; then
./mcc infra run -d -s "$wave_services"
else
./mcc infra run -s "$wave_services"
fi
run_exit=$?
if [ $run_exit -ne 0 ]; then
report_run_failure "$run_exit" "Wave $wave_num ($wave_services)"
exit 1
fi
if [ "$wave_services" = "mc-workflow-manager-jenkins" ]; then
register_jenkins_credentials || exit 1
fi
done

check_post_initial
Expand Down Expand Up @@ -586,6 +605,9 @@ case $RUN_MODE in
report_run_failure "$run_exit" "Wave $wave_num ($wave_services)"
exit 1
fi
if [ "$wave_services" = "mc-workflow-manager-jenkins" ]; then
register_jenkins_credentials || exit 1
fi
done

echo ""
Expand Down
1 change: 1 addition & 0 deletions conf/docker/docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1014,6 +1014,7 @@ services:
- /usr/bin/docker:/usr/bin/docker
- ./container-volume/mc-workflow-manager/jenkins/:/var/jenkins_home:rw
- ./tool/init.groovy.d:/usr/share/jenkins/ref/init.groovy.d
- ./tool/jenkins:/opt/mcmp/jenkins-credentials:ro
environment:
JENKINS_USERNAME: ${MC_WORKFLOW_MANAGER_JENKINS_USERNAME:-admin}
JENKINS_PASSWORD: ${MC_WORKFLOW_MANAGER_JENKINS_PASSWORD:-123456}
Expand Down
2 changes: 2 additions & 0 deletions conf/docker/tool/init.groovy.d/basic-security.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import hudson.security.*
// 설치할 플러그인 목록
def pluginParameter = """
workflow-api
credentials
snakeyaml-api
swarm
authorize-project
antisamy-markup-formatter
Expand Down
78 changes: 78 additions & 0 deletions conf/docker/tool/jenkins/jenkins-credentials.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
if (!binding.hasVariable("encodedCredentialsYaml")) {
throw new IllegalStateException("encodedCredentialsYaml must be provided by the external bootstrap script")
}

byte[] decryptedBytes = null
String yamlText = null
try {
decryptedBytes = Base64.decoder.decode(encodedCredentialsYaml.toString())
if (decryptedBytes.length == 0) {
throw new IllegalStateException("CSP credentials are empty")
}
yamlText = new String(decryptedBytes, java.nio.charset.StandardCharsets.UTF_8)
} finally {
if (decryptedBytes != null) {
java.util.Arrays.fill(decryptedBytes, (byte) 0)
}
}

def yamlRoot = new org.yaml.snakeyaml.Yaml().load(yamlText)
def adminCredentials = yamlRoot?.credentialholder?.admin

if (!(adminCredentials instanceof Map)) {
throw new IllegalStateException("credentialholder.admin is required in credentials.yaml.enc")
}

def objectStorageKeys = [
aws: ["aws_access_key_id", "aws_secret_access_key"],
gcp: ["S3AccessKey", "S3SecretKey"],
alibaba: ["AccessKeyId", "AccessKeySecret"],
tencent: ["SecretId", "SecretKey"],
ibm: ["S3AccessKey", "S3SecretKey"],
ncp: ["ncloud_access_key", "ncloud_secret_key"],
nhn: ["S3AccessKey", "S3SecretKey"]
]

def store = com.cloudbees.plugins.credentials.SystemCredentialsProvider.instance.store
def domain = com.cloudbees.plugins.credentials.domains.Domain.global()
def registered = []
def skipped = []

objectStorageKeys.each { provider, keys ->
def providerCredentials = adminCredentials[provider]
def accessKey = providerCredentials instanceof Map ? providerCredentials[keys[0]]?.toString()?.trim() : ""
def secretKey = providerCredentials instanceof Map ? providerCredentials[keys[1]]?.toString()?.trim() : ""

if (!accessKey || !secretKey) {
skipped << provider
return
}
if (accessKey.contains("\n") || accessKey.contains("\r") || secretKey.contains("\n") || secretKey.contains("\r")) {
throw new IllegalStateException("Object Storage credential for ${provider} must not contain line breaks")
}

def credentialId = "object-storage-credential-${provider}"
def credential = new com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl(
com.cloudbees.plugins.credentials.CredentialsScope.GLOBAL,
credentialId,
"M-CMP Object Storage credential for ${provider.toUpperCase()}",
accessKey,
secretKey
)
def existing = store.getCredentials(domain).find { it.id == credentialId }
def saved = existing ? store.updateCredentials(domain, existing, credential) : store.addCredentials(domain, credential)

if (!saved) {
throw new IllegalStateException("Failed to save Jenkins credential ${credentialId}")
}
registered << credentialId
}

registered.each { println "MCMP_OBJECT_STORAGE_CREDENTIAL_REGISTERED ${it}" }
skipped.each { println "MCMP_OBJECT_STORAGE_CREDENTIAL_SKIPPED ${it}" }
new File(jenkins.model.Jenkins.get().rootDir, ".mcmp-object-storage-credentials-initialized").text = "ready\n"
println "MCMP_OBJECT_STORAGE_CREDENTIALS_COMPLETE"

yamlText = null
yamlRoot = null
adminCredentials = null
209 changes: 209 additions & 0 deletions conf/docker/tool/jenkins/register-credentials.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
#!/usr/bin/env bash

set -euo pipefail
umask 077

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly SCRIPT_DIR
readonly JENKINS_CONTAINER="${MCMP_JENKINS_CONTAINER_NAME:-mc-workflow-manager-jenkins}"
readonly ENCRYPTED_CREDENTIALS_FILE="${MCMP_CREDENTIALS_FILE:-$HOME/.cloud-barista/credentials.yaml.enc}"
readonly DECRYPT_KEY_FILE="${MCMP_CREDENTIALS_KEY_FILE:-$HOME/.cloud-barista/.tmp_enc_key}"
readonly GROOVY_SCRIPT="$SCRIPT_DIR/jenkins-credentials.groovy"
readonly REGISTRATION_MARKER="/var/jenkins_home/.mcmp-object-storage-credentials-initialized"
readonly MAX_PASSWORD_ATTEMPTS=3

MCMP_JENKINS_COOKIE_FILE=""

cleanup_jenkins_cookie() {
if [ -n "$MCMP_JENKINS_COOKIE_FILE" ]; then
rm -f -- "$MCMP_JENKINS_COOKIE_FILE"
fi
}

run_inside_container() {
: "${JENKINS_USERNAME:?JENKINS_USERNAME must not be empty}"
: "${JENKINS_PASSWORD:?JENKINS_PASSWORD must not be empty}"

local jenkins_url="http://localhost:8080"
local crumb_header

MCMP_JENKINS_COOKIE_FILE=$(mktemp /tmp/mcmp-jenkins-cookie.XXXXXX)
trap cleanup_jenkins_cookie EXIT

crumb_header=$(curl -fsS \
--user "$JENKINS_USERNAME:$JENKINS_PASSWORD" \
--cookie-jar "$MCMP_JENKINS_COOKIE_FILE" \
"$jenkins_url/crumbIssuer/api/xml?xpath=concat(//crumbRequestField,%22:%22,//crumb)")

if [ -z "$crumb_header" ]; then
echo "Error: Jenkins returned an empty CSRF crumb." >&2
return 1
fi

curl -fsS \
--user "$JENKINS_USERNAME:$JENKINS_PASSWORD" \
--cookie "$MCMP_JENKINS_COOKIE_FILE" \
--header "$crumb_header" \
--data-urlencode "script@-" \
"$jenkins_url/scriptText"
}

if [ "${1:-}" = "--inside-container" ]; then
if [ "$#" -ne 1 ]; then
echo "Error: --inside-container does not accept additional arguments." >&2
exit 1
fi
run_inside_container
exit $?
fi

force_registration=false
case "${1:-}" in
"")
;;
--force)
force_registration=true
;;
*)
echo "Usage: $0 [--force]" >&2
exit 1
;;
esac

for command_name in docker openssl; do
if ! command -v "$command_name" >/dev/null 2>&1; then
echo "Error: $command_name is required to register Jenkins credentials." >&2
exit 1
fi
done

if [ ! -f "$GROOVY_SCRIPT" ]; then
echo "Error: Jenkins credential script not found: $GROOVY_SCRIPT" >&2
exit 1
fi

container_running=$(docker inspect --format '{{.State.Running}}' "$JENKINS_CONTAINER" 2>/dev/null || true)
if [ "$container_running" != "true" ]; then
echo "Error: Jenkins container is not running: $JENKINS_CONTAINER" >&2
exit 1
fi

echo "Waiting for Jenkins initialization to complete..."
jenkins_ready=false
for ((attempt = 1; attempt <= 120; attempt++)); do
health_status=$(docker inspect \
--format '{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' \
"$JENKINS_CONTAINER" 2>/dev/null || true)
if [ "$health_status" = "healthy" ]; then
jenkins_ready=true
break
fi
sleep 5
done

if [ "$jenkins_ready" != "true" ]; then
echo "Error: Jenkins did not become healthy within 10 minutes." >&2
exit 1
fi

if [ "$force_registration" != "true" ] && \
docker exec "$JENKINS_CONTAINER" test -f "$REGISTRATION_MARKER"; then
echo "Jenkins credentials are already initialized; skipping registration."
exit 0
fi

if [ ! -f "$ENCRYPTED_CREDENTIALS_FILE" ] || [ ! -s "$ENCRYPTED_CREDENTIALS_FILE" ]; then
echo "Encrypted CSP credentials not found or empty; skipping Jenkins credential registration:"
echo " $ENCRYPTED_CREDENTIALS_FILE"
echo "Register the credentials manually in the Jenkins UI."
exit 0
fi

encoded_credentials_yaml=""
credentials_decrypted=false
if [ -s "$DECRYPT_KEY_FILE" ]; then
echo "Using the host key file for this one-time registration: $DECRYPT_KEY_FILE"
if encoded_credentials_yaml=$(
openssl enc -aes-256-cbc -d -pbkdf2 \
-in "$ENCRYPTED_CREDENTIALS_FILE" \
-pass "file:$DECRYPT_KEY_FILE" 2>/dev/null |
openssl base64 -A
); then
credentials_decrypted=true
else
encoded_credentials_yaml=""
echo "Warning: failed to decrypt credentials.yaml.enc with .tmp_enc_key." >&2
fi
fi

if [ "$credentials_decrypted" != "true" ] && [ -t 0 ]; then
for ((attempt = 1; attempt <= MAX_PASSWORD_ATTEMPTS; attempt++)); do
credential_password=""
IFS= read -r -s -p "Enter the credentials.yaml.enc password ($attempt/$MAX_PASSWORD_ATTEMPTS): " credential_password || credential_password=""
printf '\n'

if [ -z "$credential_password" ]; then
echo "Warning: a decryption password is required ($attempt/$MAX_PASSWORD_ATTEMPTS)." >&2
continue
fi

if encoded_credentials_yaml=$(
printf '%s\n' "$credential_password" |
openssl enc -aes-256-cbc -d -pbkdf2 \
-in "$ENCRYPTED_CREDENTIALS_FILE" \
-pass stdin 2>/dev/null |
openssl base64 -A
); then
credentials_decrypted=true
unset credential_password
break
fi

encoded_credentials_yaml=""
unset credential_password
echo "Warning: failed to decrypt credentials.yaml.enc with the entered password ($attempt/$MAX_PASSWORD_ATTEMPTS)." >&2
done
fi

if [ "$credentials_decrypted" != "true" ]; then
echo "Unable to decrypt credentials.yaml.enc; skipping Jenkins credential registration."
echo "Register the credentials manually in the Jenkins UI."
exit 0
fi

if [ -z "$encoded_credentials_yaml" ]; then
echo "Error: decrypted CSP credentials are empty." >&2
exit 1
fi

if ! registration_output=$(
{
printf 'encodedCredentialsYaml = "%s"\n' "$encoded_credentials_yaml"
sed -n '1,$p' "$GROOVY_SCRIPT"
} | docker exec -i "$JENKINS_CONTAINER" \
/opt/mcmp/jenkins-credentials/register-credentials.sh --inside-container
); then
unset encoded_credentials_yaml
echo "Error: failed to send credentials to Jenkins." >&2
exit 1
fi
unset encoded_credentials_yaml

registration_complete=false
while IFS= read -r output_line; do
case "$output_line" in
MCMP_OBJECT_STORAGE_CREDENTIAL_REGISTERED\ *|MCMP_OBJECT_STORAGE_CREDENTIAL_SKIPPED\ *)
printf '%s\n' "$output_line"
;;
MCMP_OBJECT_STORAGE_CREDENTIALS_COMPLETE)
registration_complete=true
;;
esac
done <<< "$registration_output"

if [ "$registration_complete" != "true" ]; then
echo "Error: Jenkins did not confirm credential registration." >&2
exit 1
fi

echo "Jenkins credential registration is complete."