Chapter 15Lesson 02~150 minutes

Credentials Store, Secret Text, Files, SSH Keys, Username/Password, Binding, and Secret Hygiene: Guided Hands-On Workflow and Core Operations

Create only fake credentials in a disposable folder, bind them for the shortest practical lifetime on a trusted lab agent, and prove safe use without revealing values.

Hands-onwithCredentialsSecret filesSSH keysFolder scopeEvidence

Learning objectives

  • Create disposable secret text, username/password, SSH private-key, and secret-file credentials without placing values in SCM.
  • Store the credentials in a lab folder so descendant jobs can use them while unrelated jobs cannot.
  • Bind credentials with withCredentials, prove non-secret metadata and temporary-file behavior, and avoid console disclosure.
  • Compare safe secret-file placement with a risky nested-workspace pattern before using it.
  • Capture an evidence packet that records identity, scope, build, agent, and cleanup state while excluding all secret values.

1. Lab boundary and preflight

This lab is deliberately local/disposable. Use Jenkins 2.568.3 LTS with Java 21, one trusted disposable agent labeled trusted-cred-lab, and the plugin versions listed in the references. Do not use production credentials, employer repositories, a production controller, or a multi-tenant agent.

Guard: the only credential values allowed in this lab are synthetic. If you already have a credential with one of the IDs below, stop and choose different lab IDs rather than editing it.
# Agent-side, read-only preflight
java -version
git --version
id
pwd
umask

On the controller, record the core/Java/plugin baseline. Confirm that routine builds do not execute on the built-in controller node. The lab should use a dedicated disposable folder named cred-lab.

2. Create four fake credentials in the folder store

Create cred-lab, open Credentials → Folder, and add these lab-only values. The descriptions should say DISPOSABLE TRAINING ONLY.

ID Kind Fake input Purpose
lab-secret-text Secret text LAB_ONLY_TOKEN_2026 Opaque token binding
lab-basic-auth Username/password lab-reader / LAB_ONLY_PASSWORD_42 Paired variables
lab-secret-file Secret file A temporary file containing only profile=lab-only File-binding lifecycle
lab-ssh-key SSH username with private key A newly generated disposable Ed25519 key Private-key file binding

Generate the disposable SSH key outside SCM and upload it to the folder credential. Never paste the private key into chat, logs, lesson evidence, or source control.

mkdir -p ./cred-lab-input && chmod 700 ./cred-lab-input
printf '%s\n' 'profile=lab-only' > ./cred-lab-input/client.conf
chmod 600 ./cred-lab-input/client.conf
ssh-keygen -t ed25519 -f ./cred-lab-input/id_ed25519 -N '' -C 'jenkins-cred-lab'
ssh-keygen -lf ./cred-lab-input/id_ed25519.pub

3. Bind values narrowly without printing them

Create cred-lab/binding-demo as a Pipeline job. The Pipeline deliberately does not contact any external service. It proves only that Jenkins can resolve the IDs and that the agent receives the correct binding forms.

pipeline {
  agent { label 'trusted-cred-lab' }
  options { skipDefaultCheckout(true); timestamps() }
  stages {
    stage('Identity') {
      steps {
        sh '''
          set -eu
          printf 'job=%s build=%s node=%s workspace=%s\\n' \
            "$JOB_NAME" "$BUILD_NUMBER" "$NODE_NAME" "$WORKSPACE"
        '''
      }
    }
    stage('Bounded credential use') {
      steps {
        withCredentials([
          string(credentialsId: 'lab-secret-text', variable: 'LAB_TOKEN'),
          usernamePassword(credentialsId: 'lab-basic-auth',
                           usernameVariable: 'LAB_USER', passwordVariable: 'LAB_PASS'),
          file(credentialsId: 'lab-secret-file', variable: 'LAB_CONFIG'),
          sshUserPrivateKey(credentialsId: 'lab-ssh-key',
                            keyFileVariable: 'LAB_SSH_KEY', usernameVariable: 'LAB_SSH_USER')
        ]) {
          sh '''
            set -eu
            set +x
            test -n "$LAB_TOKEN"
            test -n "$LAB_USER"
            test -n "$LAB_PASS"
            test -f "$LAB_CONFIG"
            test -f "$LAB_SSH_KEY"
            printf 'credential bindings are present; values intentionally not printed\\n'
            printf 'secret-file-parent=%s\\n' "$(dirname "$LAB_CONFIG")"
            printf 'ssh-key-parent=%s\\n' "$(dirname "$LAB_SSH_KEY")"
            stat -c 'secret-file mode=%a owner=%U size=%s' "$LAB_CONFIG"
            stat -c 'ssh-key mode=%a owner=%U size=%s' "$LAB_SSH_KEY"
            ssh-keygen -lf "$LAB_SSH_KEY"
          '''
        }
      }
    }
  }
}
Evidence discipline: the log records that bindings exist, their temporary parent directories, file metadata, and the disposable SSH public fingerprint. It never prints token, password, private key, or secret-file contents.

4. Understand secret-file placement before changing directories

The Credentials Binding documentation warns about binding a file from inside a nested dir. The following pattern can place the temporary file under the browsable workspace subtree:

// RISKY pattern — use only with fake lab data for analysis, not real secrets
dir('subdir') {
  withCredentials([file(credentialsId: 'lab-secret-file', variable: 'FILE')]) {
    sh 'set +x; test -f "$FILE"'
  }
}

Prefer binding outside the nested directory so the secret-file temporary directory is outside that subdirectory, or use an isolated workspace strategy designed for secret-bearing work:

withCredentials([file(credentialsId: 'lab-secret-file', variable: 'FILE')]) {
  dir('subdir') {
    sh 'set +x; test -f "$FILE"; ./consume-lab-config "$FILE"'
  }
}

Do not archive or stash the workspace while the binding is active. A secret copied into another file is no longer protected by the binding cleanup.

5. Prove folder context without broadening it

Create a second disposable Pipeline job credential-outsider at the Jenkins root or in a different folder. Attempt to resolve only the ID lab-secret-text. The expected result is that Jenkins cannot find an accessible credential with that ID in the outsider context. Preserve the resulting error as scope evidence.

pipeline {
  agent { label 'trusted-cred-lab' }
  stages {
    stage('Expected denial') {
      steps {
        withCredentials([string(credentialsId: 'lab-secret-text', variable: 'X')]) {
          sh 'set +x; test -n "$X"'
        }
      }
    }
  }
}
Do not “fix” the denial by moving the credential to root-global scope. The denial is the expected result of the lab's least-privilege design.

6. Expected observations and evidence

  • cred-lab/binding-demo resolves all four IDs and runs only on trusted-cred-lab.
  • The console shows no credential values; it shows only non-secret metadata.
  • Secret-file and SSH-key paths exist only while their binding block executes.
  • The outsider job fails before it can use the folder credential.
  • No workspace artifact, stash, report, or archive contains credential values or temporary credential files.

7. Small challenge: choose the correct layer

A job inside cred-lab can resolve lab-secret-text, but an SSH command fails host-key verification. Which layer should you change?

Answer after investigation: not the credential scope. Credential resolution already succeeded. Diagnose the SSH client/known-host trust configuration on the agent. Never disable host-key verification to make the command pass.

8. Cleanup and rollback

  1. Delete the outsider job and cred-lab folder after recording non-secret evidence.
  2. Confirm the four lab credential IDs no longer appear in the folder store.
  3. Delete ./cred-lab-input from the machine where the fake input files were created.
  4. Stop/remove only the disposable agent/controller resources created for this course lab.
  5. Never delete unrelated credentials, folders, nodes, or build history.
Next lesson

Configuration, Design Choices, and Tradeoffs

Decide when credentials belong in root System/Global scope, a folder store, an external provider, a file/key type, or a tool-native helper—and identify which mechanism actually enforces access.

Knowledge check

Why does the lab put credentials in the disposable folder instead of the root global store?

Why does the safe example use set +x before commands that reference secret variables?

What evidence can you record about a secret-file binding without recording the secret?

Why is printing the SSH public fingerprint acceptable while printing the private key is not?

A job in another folder cannot see lab-secret-text. Is that a build failure or evidence?

Official references and version notes

Assumption timestamp: 2026-09-17. Recheck core/plugin security advisories and minimum-core requirements before reproducing this lab later.

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.