Question: How to Configure GitHub App Authentication in Jenkins Pipeline for Secure Repository Access
I’m trying to configure GitHub App authentication in a Jenkins pipeline to securely access a GitHub repository at the organization level. I’ve set up the GitHub App with the following permissions:
- Administration: Read-only
- Checks: Read & write
- Contents: Read-only
- Metadata: Read-only
- Pull requests: Read-only
In my pipeline, I’m generating a JWT, exchanging it for an access token, and using the token to clone the repository. However, I keep encountering a 401
error with the message: “A JSON web token could not be decoded.”
Could someone guide me on correctly setting up the GitHub App credentials in Jenkins and troubleshooting this JWT decoding error?
Jenkins Setup:
stage('Generate JWT and Clone Repository') {
environment {
// Hardcoded Credentials and Repository Information
APP_ID = "asdfadsfasdf" // Replace with your GitHub App ID
INSTALLATION_ID = "1234567" // Replace with your GitHub App Installation ID
PEM_CONTENT = '''
-----BEGIN RSA PRIVATE KEY-----
-----END RSA PRIVATE KEY-----
''' // Replace with the full contents of your PEM file
REPO_URL = "github.com/org_name/repo_name.git" // Replace with your repo URL
}
steps {
script {
// Step 1: Write PEM content to a temporary file with appropriate permissions
// def pemFile = 'github_app_private_key.pem'
// writeFile file: pemFile, text: PEM_CONTENT
// sh "chmod 600 ${pemFile}"
// Step 2: Generate JWT using the PEM file and add debugging for each component
echo "Generating JWT..."
env.JWT = sh(
script: """#!/bin/bash
# Generate JWT Header
HEADER=\$(echo -n '{"alg":"RS256","typ":"JWT"}' | openssl base64 | tr -d '=' | tr '/+' '_-' | tr -d '\\n')
# Generate JWT Payload
PAYLOAD=\$(echo -n "{\\"iss\\":${APP_ID},\\"iat\\":\$(date +%s),\\"exp\\":\$(( \$(date +%s) + 600 ))}" | openssl base64 | tr -d '=' | tr '/+' '_-' | tr -d '\\n')
# Generate JWT Signature
HEADER_PAYLOAD="\${HEADER}.\${PAYLOAD}"
SIGNATURE=\$(echo -n "\${HEADER_PAYLOAD}" | openssl dgst -sha256 -sign ${PEM_CONTENT} | openssl base64 | tr -d '=' | tr '/+' '_-' | tr -d '\\n')
# Combine to form JWT
echo "\${HEADER}.\${PAYLOAD}.\${SIGNATURE}"
""",
returnStdout: true
).trim()
if (!env.JWT) {
error("JWT generation failed.")
} else {
echo "JWT generated successfully and stored as a global environment variable."
}
// Step 3: Exchange the JWT for an installation access token and store the response as a global environment variable
echo "Requesting installation access token..."
env.RESPONSE = sh(
script: """#!/bin/bash
curl -s -X POST \
-H "Authorization: Bearer ${env.JWT}" \
-H "Accept: application/vnd.github+json" \
https://api.github.com/app/installations/${INSTALLATION_ID}/access_tokens
""",
returnStdout: true
).trim()
// Debugging: Output the API response
echo "GitHub API response: ${env.RESPONSE}"
// Parse the token from the RESPONSE
def jsonResponse = readJSON(text: env.RESPONSE)
def accessToken = jsonResponse.token
if (!accessToken) {
error("Failed to obtain access token. Response: ${env.RESPONSE}")
} else {
echo "Access token obtained successfully."
}
// Step 4: Clone the repository using the access token
echo "Cloning repository..."
sh "git clone https://x-access-token:${accessToken}@${REPO_URL}"
echo "Repository cloned successfully."
// Step 5: Clean up PEM file
sh "rm -f ${PEM_CONTENT}"
}
}
}