10

I am trying to migrate credentials from Jenkins to another credentials store.

I want to read the credentials from the Jenkins store, and have found this script (https://github.com/tkrzeminski/jenkins-groovy-scripts/blob/master/show-all-credentials.groovy

The script does the job OK for SystemCredentialsProvider credentials for the global domain at root level.

But my credentials are stored in a Folder, so the script does not work for me.

I am using the Jenkins script console to execute the script.

If I navigate to the Jenkins Credentials configuration page and hover over the icon for one of my credential entries, the tooltip says "Folder Credentials Provider".

====================================================

Question: How do I read all of the the credentials from a Folder in Jenkins?

====================================================

Please see script, below:

import jenkins.model.*
import com.cloudbees.plugins.credentials.*
import com.cloudbees.plugins.credentials.impl.*
import com.cloudbees.plugins.credentials.domains.*
import com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey
import com.cloudbees.jenkins.plugins.awscredentials.AWSCredentialsImpl
import org.jenkinsci.plugins.plaincredentials.StringCredentials
import org.jenkinsci.plugins.plaincredentials.impl.FileCredentialsImpl

def showRow = { credentialType, secretId, username = null, password = null, description = null ->
  println("${credentialType} : ".padLeft(20) + secretId?.padRight(38)+" | " +username?.padRight(20)+" | " +password?.padRight(40) + " | " +description)
}

// set Credentials domain name (null means is it global)
domainName = null

credentialsStore = Jenkins.instance.getExtensionList('com.cloudbees.plugins.credentials.SystemCredentialsProvider')[0]?.getStore()
domain = new Domain(domainName, null, Collections.<DomainSpecification>emptyList())

credentialsStore?.getCredentials(domain).each{
  if(it instanceof UsernamePasswordCredentialsImpl)
    showRow("user/password", it.id, it.username, it.password?.getPlainText(), it.description)
  else if(it instanceof BasicSSHUserPrivateKey)
    showRow("ssh priv key", it.id, it.passphrase?.getPlainText(), it.privateKeySource?.getPrivateKey(), it.description)
  else if(it instanceof AWSCredentialsImpl)
    showRow("aws", it.id, it.accessKey, it.secretKey?.getPlainText(), it.description)
  else if(it instanceof StringCredentials)
    showRow("secret text", it.id, it.secret?.getPlainText(), '', it.description)
  else if(it instanceof FileCredentialsImpl)
    showRow("secret file", it.id, it.content?.text, '', it.description)
  else
    showRow("something else", it.id, '', '', '')
}

return
Shaybc
  • 2,628
  • 29
  • 43
Banoona
  • 1,470
  • 3
  • 18
  • 32

5 Answers5

7

i simply created a Java class to retrieve the credentials from either global or folder scope:

import com.cloudbees.plugins.credentials.CredentialsProvider;
import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials;
import com.cloudbees.hudson.plugins.folder.Folder

class JenkinsCredentials
{
    /**
    * this method gets a 'StandardUsernamePasswordCredentials' for a userName, the 'StandardUsernamePasswordCredentials' object
    * have 2 fields - user and password, which can be used to login to other systems,
    * the username is searched for first in the global jenkins level, and if not found then it is searched at the folder level
     */
    public static StandardUsernamePasswordCredentials getCredentialsByUsername(String userName, String fromFolder) throws Exception
    {
        List credsList = CredentialsProvider.lookupCredentials(StandardUsernamePasswordCredentials.class, Jenkins.getInstance());
        if(credsList == null || credsList.size() == 0)
        {
            credsList = getFolderLevelCredentialsList(fromFolder);
        }
        return credsList.findResult { it.username == userName ? it : null };
    }

    /**
    * this method gets a 'StandardUsernamePasswordCredentials' for a userID, the 'StandardUsernamePasswordCredentials' object
    * have 2 fields - user and password, which can be used to login to other systems,
    * the userID is searched for first in the global jenkins level, and if not found then it is searched at the folder level
     */
    public static StandardUsernamePasswordCredentials getCredentialsByID(String userID, String fromFolder) throws Exception
    {
        List credsList = CredentialsProvider.lookupCredentials(StandardUsernamePasswordCredentials.class, Jenkins.getInstance());
        if(credsList == null || credsList.size() == 0)
        {
            credsList = getFolderLevelCredentialsList(fromFolder);
        }
        return credsList.findResult { it.id == userID ? it : null };
    }

    /**
    * this method gets a 'StandardUsernamePasswordCredentials' for a userName, the 'StandardUsernamePasswordCredentials' object
    * have 2 fields - user and password, which can be used to login to other systems,
    * the username is searched for at the folder level who's name we provided as 'fromFolder'
     */
    public static StandardUsernamePasswordCredentials getFolderCredentialsByUsername(String userName, String fromFolder) throws Exception
    {
        List credsList = getFolderLevelCredentialsList(fromFolder);
        return credsList.findResult { it.username == userName ? it : null };
    }

    /**
    * this method gets a 'StandardUsernamePasswordCredentials' for a userID, the 'StandardUsernamePasswordCredentials' object
    * have 2 fields - user and password, which can be used to login to other systems,
    * the userID is searched for at the folder level who's name we provided as 'fromFolder'
    */
    public static StandardUsernamePasswordCredentials getFolderCredentialsByID(String userID, String fromFolder) throws Exception
    {
        List credsList = getFolderLevelCredentialsList(fromFolder);
        return credsList.findResult { it.id == userID ? it : null };
    }

    /**
    * this method gets a list of credentials set at a folder level, the method receives the folder name to get the credentials from
     */
    public static List getFolderLevelCredentialsList(String folderName)
    {
        return CredentialsProvider.lookupCredentials(StandardUsernamePasswordCredentials.class, getFolderItem(folderName));
    }

    /**
    * this method fetch a 'Folder' item from jenkins instance by a folder name
    * it then can be used in the other methods to search for a user credentials at that folder level
     */
    public static Folder getFolderItem(String folderName)
    {
        def allJenkinsItems = Jenkins.getInstance().getItems();
        for (currentJenkinsItem in allJenkinsItems)
        {
            if(currentJenkinsItem instanceof Folder)
            {
                if(((Folder)currentJenkinsItem).getFullName().contains(folderName))
                {
                    return (Folder)currentJenkinsItem;
                }
            }
        }

    }
}

then you can use it like this:

get credentials for userName 'shay bc' from folder named 'MyJenkinsFolder':

// get the credentials
StandardUsernamePasswordCredentials shaybcCredentials = JenkinsCredentials.getFolderCredentialsByUsername("shay bc", "MyJenkinsFolder")

// login to some system using the credentials
sshSession.login(shaybcCredentials.getUsername(), shaybcCredentials.getPassword())

get credentials for userID 'sbc' from folder named 'MyJenkinsFolder':

// get the credentials
StandardUsernamePasswordCredentials sbcCredentials = JenkinsCredentials.getFolderCredentialsByID("sbc", "MyJenkinsFolder")

// login to some system using the credentials
sshSession.login(sbcCredentials.getUsername(), sbcCredentials.getPassword())
Shaybc
  • 2,628
  • 29
  • 43
  • 2
    Care to add more details to your answer? It would be nice to see complete solution instead of a plane class, thank you! – oglop Jan 10 '20 at 12:35
  • This class saved me a lot of time. I will add a response in a separate answer. – Kingdon May 19 '20 at 16:26
  • 1
    @oglop i improved the example so it would be more clear for future users on how to use this code, hope this is more helpful – Shaybc Sep 12 '21 at 13:36
5

The class shared by @Shaybc was most of the solution, and although a commenter suggested it was not a complete solution by itself, I was able to guess how to implement it correctly into such a complete solution in my own Groovy script.

The Folder returned by JenkinsCredentials.getFolderItem('foldername') is taking the same string slug as your folder is addressed by in Jenkins. So if you have a folder with a descriptive name, and a slug, like "Folder Name" and "foldername", the right string to use to retrieve the folder itself is "foldername".

The example provided here shows how to retrieve credentials from the global store, this is Jenkins.instance – for complete documentation the script they provide is copied here:

import jenkins.*
import jenkins.model.* 
import hudson.*
import hudson.model.*
def jenkinsCredentials = com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials(
        com.cloudbees.plugins.credentials.Credentials.class,
        Jenkins.instance,
        null,
        null
);
for (creds in jenkinsCredentials) {
    println(jenkinsCredentials.id)
    }

So, start your script by defining class JenkinsCredentials as @Shaybc described, then instead of calling CredentialsProvider.lookupCredentials with Jenkins.instance, retrieve the folder and pass it in there instead.

My folder was called "ft" and (skipping over the import/boilerplate at the top and the definition of JenkinsCredentials helper class,) the remainder of my invocation in the Groovy script looked like:

def folder = JenkinsCredentials.getFolderItem('ft');

def creds = com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials(
        com.cloudbees.plugins.credentials.Credentials.class,
        folder,
        null,
        null
);
for (c in creds) {
    println(c.id + ": " + c.description)
}

Hope this helped someone! and by the way, if your credentials are stored in a file instead of a string, you have one more step... the file will not have any useful content in the "description" attribute, you need the byte stream stored at c.getContent()

So, that final loop with some metaprogramming to dynamically check for the availability of a getContent() method to call:

for (c in creds) {
 if (c.metaClass.respondsTo(c, 'getContent')) {
  println(
   IOUtils.toString(c.getContent(), StandardCharsets.UTF_8)
  )
 }
 println(c.id + ": " + c.description)
}

That last part was borrowed from this answer, which shows as well how you can import StandardCharsets and IOUtils: https://stackoverflow.com/a/42360792/661659

Kingdon
  • 308
  • 4
  • 12
4
import jenkins.model.*
import hudson.model.ModelObject
import com.cloudbees.plugins.credentials.*
import com.cloudbees.plugins.credentials.impl.*
import com.cloudbees.plugins.credentials.domains.*
import com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey
import com.cloudbees.jenkins.plugins.awscredentials.AWSCredentialsImpl
import org.jenkinsci.plugins.plaincredentials.StringCredentials
import org.jenkinsci.plugins.plaincredentials.impl.FileCredentialsImpl
import com.cloudbees.hudson.plugins.folder.Folder


class DeepCredentialsPrinter {

  private static final boolean DEBUG = false;

  private final out;
  private final Set<CredentialsStore> visitedStores = new HashSet<>();

  DeepCredentialsPrinter(out) {
      this.out = out;
  }

  private void start() {
    process(Jenkins.getInstance())
  }

  private void process(ItemGroup group) {
    printCreds(group);

    List<ItemGroup> items = group.getItems();
    if (items == null || items.isEmpty()) {
      return;
    }

    for (item in items) {
      if (item instanceof ItemGroup) {
        process(item);
      } else if (item instanceof Item) {
        printCreds(item)
      } else {
        if (DEBUG) {
          out.println("[DEBUG] unsupported item type: " + item.getClass().getCanonicalName());
        }
      }
    }
  }

  private void printCreds(ModelObject model) {
    for (store in CredentialsProvider.lookupStores(model)) {
      if (visitedStores.add(store)) { // only visit new stores
        print(model.getFullName(), store.getCredentials(Domain.global()));
      }
    }
  }

  private void print(String fullname, List<Credentials> creds) {
    if (creds.isEmpty()) {
      if (DEBUG) {
        out.println("[DEBUG] No credentials in /" + fullname);
      }
    } else {
      for (c in creds) {
        out.println("Folder: /" + fullname)
        out.println("   id: " + c.id)
        if (c.properties.description) {
            out.println("   description: " + c.description)
        }
        if (c.properties.username) {
            out.println("   username: " + c.username)
        }
        if (c.properties.password) {
            out.println("   password: " + c.password)
        }
        if (c.properties.passphrase) {
            out.println("   passphrase: " + c.passphrase)
        }
        if (c.properties.secret) {
            out.println("   secret: " + c.secret)
        }
        if (c.properties.secretBytes) {
            out.println("    secretBytes: ")
            out.println("\n" + new String(c.secretBytes.getPlainData()))
        }
        if (c.properties.privateKeySource) {
            out.println("   privateKey: " + c.getPrivateKey())
        }
        if (c.properties.apiToken) {
            out.println("   apiToken: " + c.apiToken)
        }
        if (c.properties.token) {
            out.println("   token: " + c.token)
        }
        out.println("")
        }
    }
  }
}

new DeepCredentialsPrinter(getBinding().out).start();

From all the above comments, Here is the code to list all the folder level credentials which prints all properties based on type of credential

Ravin
  • 41
  • 2
2

Just to add to this really awesome class from @Shaybc... If you're like me and have nested folders, you'll want to change up the Class method getFolderItem to recurse into all of the "sub-folders"... Otherwise you'll only ever get a return/hit on the top-level (root) Jenkins items.

Here's what that looks like, replacing the getFolderItem method and adding a getFolderItemRecursively method to do the work.

private static Folder getFolderItemRecursively(Folder folder, String folderName) {
    for (nestedItem in folder.getItems()) {
        if (nestedItem instanceof Folder) {
            if(nestedItem.getFullName().contains(folderName)) {
                return nestedItem;
            } else {
                def recurse = getFolderItemRecursively(nestedItem, folderName);
                if (recurse instanceof Folder) {
                    return recurse;
                }
            }
        }
    }
}
public static Folder getFolderItem(String folderName)
{
    for (item in Jenkins.getInstance().getItems())
    {
        if(item instanceof Folder)
        {
            if(item.getFullName().contains(folderName))
            {
                return item;
            } else {
                def recurse = getFolderItemRecursively(item, folderName);
                if (recurse instanceof Folder) {
                    return recurse;
                }
            }
        }
    }
}
Tim Welch
  • 21
  • 1
1

Somewhat related to the original ask, I needed to do the inventory of all credentials stored in Jenkins. None of the answers so far have provided a solution that works out-of-the-box to dump all Jenkins credentials... Based on the elements of answers provided by @TimWelch and @Kingdon, I wrote the following groovy (admin) script, which prints all credentials IDs and their type starting from the root folder to the deepest folders (recursive), while also avoiding printing duplicates:

import jenkins.model.*
import hudson.model.ModelObject
import com.cloudbees.plugins.credentials.*
import com.cloudbees.plugins.credentials.impl.*
import com.cloudbees.plugins.credentials.domains.*
import com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey
import com.cloudbees.jenkins.plugins.awscredentials.AWSCredentialsImpl
import org.jenkinsci.plugins.plaincredentials.StringCredentials
import org.jenkinsci.plugins.plaincredentials.impl.FileCredentialsImpl
import com.cloudbees.hudson.plugins.folder.Folder


class DeepCredentialsPrinter {

  private static final boolean DEBUG = false;

  private final out;
  private final Set<CredentialsStore> visitedStores = new HashSet<>();

  DeepCredentialsPrinter(out) {
      this.out = out;
  }

  private void start() {
    out.println("Folder,Credentials Type,Credentials ID") // header
    process(Jenkins.getInstance())
  }

  private void process(ItemGroup group) {
    printCreds(group);

    List<ItemGroup> items = group.getItems();
    if (items == null || items.isEmpty()) {
      return;
    }

    for (item in items) {
      if (item instanceof ItemGroup) {
        process(item);
      } else if (item instanceof Item) {
        printCreds(item)
      } else {
        if (DEBUG) {
          out.println("[DEBUG] unsupported item type: " + item.getClass().getCanonicalName());
        }
      }
    }
  }

  private void printCreds(ModelObject model) {
    for (store in CredentialsProvider.lookupStores(model)) {
      if (visitedStores.add(store)) { // only visit new stores
        print(model.getFullName(), store.getCredentials(Domain.global()));
      }
    }
  }

  private void print(String fullname, List<Credentials> creds) {
    if (creds.isEmpty()) {
      if (DEBUG) {
        out.println("[DEBUG] No credentials in /" + fullname);
      }
    } else {
      for (c in creds) {
        out.printf("/%s,%s,%s\n", fullname, c.getClass().getSimpleName(), c.id)
      }
    }
  }
}

new DeepCredentialsPrinter(getBinding().out).start();

gist: https://gist.github.com/fabienrenaud/349764873855533abc71b7fadafdf29e

The printCreds method can easily be extended to print more data for each secret type.

fabien
  • 1,529
  • 1
  • 15
  • 28