Search code examples
powershelljenkinsjenkins-cli

Jenkins: dynamically connect slave to master without knowing node secret


I struggle to (dynamically) start the Jenkins slave agent from my dedicated slave machine (Windows 2012 R2 server). The Jenkins master (ver. 1.617 - which I can upgrade if necessary, but not downgrade [before ver. 1.498 no credentials were required]) is on a Windows 2012 R2 server. Security is enabled and configured via the Active Directory plugin and Project-based Matrix Authorization Strategy.

Because of the Active Directory involved, I cannot simply add a system user to authenticate with (via -jnlpCredentials username:password or -jnlpCredentials username:apitoken). As a workaround I am using my Jenkins service user for that, but I don't like it's API-Token lying around hard-coded in the script.
I am trying to use the alternative -secret secretKey, but that secretKey is randomly created when a slave node is registered on the master.

Since I am using the Azure Slave Plugin, the slave nodes and the associated virtual machines are created for me. The virtual machines are created from a pre-defined image, that I can change in whatever way necessary.
In this pre-defined image I have a PowerShell script executed on start-up. It is derived from the sample given here. It doesn't have to be PowerShell, any other way would be okay as well.

Set-ExecutionPolicy Unrestricted

# base url to Jenkins master
$jenkinsserverurl = "https://jenkins.mycompany.com/"
# the azure-slave-plugin is creating VMs with names like 'Azure0807150842'
$vmname = (Get-Culture).TextInfo.ToTitleCase($env:computername.tolower())
# authenticate with Jenkins service user + API-token - since we don't know the '-secret'
$apiToken="jenkins_user:1234abcdefab56c7d890de1f2a345b67"

Write-Output "Downloading jenkins slave jar "
# in order to avoid updating it manually for Jenkins master updates
$slaveJarSource = $jenkinsserverurl + "jnlpJars/slave.jar"
$slaveJarLocal = "C:\jenkins_home\slave.jar"
$wc = New-Object System.Net.WebClient
$wc.DownloadFile($slaveJarSource, $slaveJarLocal)

Write-Output "Executing slave process "
$jnlpSource = $jenkinsserverurl+"computer/" + $vmname + "/slave-agent.jnlp"
# expect java.exe in the PATH, and use -noCertificateCheck to skip SSL validation
& java -jar $slaveJarLocal -jnlpCredentials $apiToken -jnlpUrl $jnlpSource -noCertificateCheck

Downloading the JNLP file and reading the contained secret is no option, since I need proper HTTP authentication at the Jenkins master for that as well.

Write-Output "Downloading jenkins slave jnlp "
$jnlpSource = $jenkinsserverurl+"computer/" + $vmname + "/slave-agent.jnlp"
$jnlpLocal = "C:\jenkins_home\slave-agent.jnlp"
$wc = New-Object System.Net.WebClient
$wc.DownloadFile($jnlpSource, $jnlpLocal)

Write-Output "Extracting secret from jenkins slave jnlp "
[xml]$jnlpFile = Get-Content $jnlpLocal
# the first argument in the generated JNLP contains the secret
$secret = Select-Xml "//jnlp/application-desc/argument[1]/text()" $jnlpFile
  1. How can I get my hands on the generated secret (without disabling the security), or
  2. What kind of credentials can I use instead (without using an actual user - such as my own or the Jenkins service user)?

Solution

  • In an ideal world, the plugin creating the slave node and its VM, would login to the created VM and execute a script similar to the one in my question - with the addition of the injected Jenkins server url, VM name, and generated secret. Since that is not the case for the current Azure Slave Plugin version, I am stuck with my workaround script - using my existing Jenkins service user.


    I use this to let the plugin create a bigger/faster VM on the fly, which is only used for a daily test run and is shutdown automatically the rest of the time (and therefore causes no costs when unused). If someone is interested, that is the setup I ended up with:

    1. Generalized Azure VM Image (Windows 2012 R2, with JDK, Maven, Git installed). Via NSSM, I installed the PowerShell script (which starts the slave agent) as a Windows Service, to be executed automatically at machine bootup (same as in the question):

      Set-ExecutionPolicy Unrestricted
      
      # base url to Jenkins master
      $jenkinsserverurl = "https://jenkins.mycompany.com/"
      # the azure-slave-plugin is creating VMs with names like 'Azure0807150842'
      $vmname = (Get-Culture).TextInfo.ToTitleCase($env:computername.tolower())
      # authenticate with Jenkins service user + API-token - since we don't know the '-secret'
      $apiToken="jenkins_user:1234abcdefab56c7d890de1f2a345b67"
      
      Write-Output "Downloading jenkins slave jar "
      # in order to avoid updating it manually for Jenkins master updates
      $slaveJarSource = $jenkinsserverurl + "jnlpJars/slave.jar"
      $slaveJarLocal = "C:\jenkins_home\slave.jar"
      $wc = New-Object System.Net.WebClient
      $wc.DownloadFile($slaveJarSource, $slaveJarLocal)
      
      Write-Output "Executing slave process "
      $jnlpSource = $jenkinsserverurl+"computer/" + $vmname + "/slave-agent.jnlp"
      # expect java.exe in the PATH, and use -noCertificateCheck to skip SSL validation
      & java -jar $slaveJarLocal -jnlpCredentials $apiToken -jnlpUrl $jnlpSource -noCertificateCheck
      
    2. Jenkins master with Azure Slave Plugin installed and configured to use this VM image, with shutdown-on-idle after five minutes.

    3. Jenkins Maven Project (Job) that is configured to only run on a Azure slave node, checks out my test project from Git, and executes the jUnit Selenium tests from there.