Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package kubelogin for openSUSE:Factory 
checked in at 2026-09-24 22:59:29
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/kubelogin (Old)
 and      /work/SRC/openSUSE:Factory/.kubelogin.new.383539 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "kubelogin"

Thu Sep 24 22:59:29 2026 rev:30 rq:1380127 version:0.2.20

Changes:
--------
--- /work/SRC/openSUSE:Factory/kubelogin/kubelogin.changes      2026-06-25 
10:57:55.546865397 +0200
+++ /work/SRC/openSUSE:Factory/.kubelogin.new.383539/kubelogin.changes  
2026-09-24 23:01:37.159665078 +0200
@@ -1,0 +2,24 @@
+Thu Sep 24 04:55:13 UTC 2026 - Johannes Kastl 
<[email protected]>
+
+- Update to version 0.2.20:
+  * Bug Fixes
+    - Fix release asset publishing by replacing
+      skx/github-action-publish-binaries with GitHub CLI in #815
+  * Maintenance
+    - Pin GitHub Actions to full-length commit SHAsin #799
+    - Add test coverage for NeedAuthenticate across all credential
+      typesin #794
+    - Add unit tests for client certificate PEM/PKCS helpersin #793
+    - Bump golang.org/x/sys from 0.45.0 to 0.48.0in #808
+    - Bump golang.org/x/crypto to v0.56.0in #806
+    - Bump gopkg.in/dnaeon/go-vcr.v4 from 4.0.2 to 4.0.7in #804
+    - Bump docker/setup-buildx-action from 4.1.0 to 4.3.0in #800
+    - Bump actions/checkout from 7.0.0 to 7.0.1in #798
+    - Bump docker/login-action from 4.2.0 to 4.6.0in #797
+    - Bump docker/build-push-action from 7.2.0 to 7.3.0in #790
+    - Bump golangci/golangci-lint-action from 9.2.1 to 9.3.0in #789
+    - Bump actions/setup-go from 6.4.0 to 6.5.0in #788
+  * Doc Update
+    - Explain how to switch device code login to interactivein #795
+
+-------------------------------------------------------------------

Old:
----
  kubelogin-0.2.19.obscpio

New:
----
  kubelogin-0.2.20.obscpio

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ kubelogin.spec ++++++
--- /var/tmp/diff_new_pack.M77kHC/_old  2026-09-24 23:01:39.311755064 +0200
+++ /var/tmp/diff_new_pack.M77kHC/_new  2026-09-24 23:01:39.312755106 +0200
@@ -17,7 +17,7 @@
 
 
 Name:           kubelogin
-Version:        0.2.19
+Version:        0.2.20
 Release:        0
 Summary:        Kubernetes client credential plugin implementing Azure 
authentication
 License:        MIT

++++++ _service ++++++
--- /var/tmp/diff_new_pack.M77kHC/_old  2026-09-24 23:01:39.341756319 +0200
+++ /var/tmp/diff_new_pack.M77kHC/_new  2026-09-24 23:01:39.343756403 +0200
@@ -2,7 +2,7 @@
   <service name="obs_scm" mode="manual">
     <param name="url">https://github.com/Azure/kubelogin.git</param>
     <param name="scm">git</param>
-    <param name="revision">v0.2.19</param>
+    <param name="revision">v0.2.20</param>
     <param name="versionformat">@PARENT_TAG@</param>
     <param name="versionrewrite-pattern">v(.*)</param>
     <param name="changesgenerate">enable</param>

++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.M77kHC/_old  2026-09-24 23:01:39.362757197 +0200
+++ /var/tmp/diff_new_pack.M77kHC/_new  2026-09-24 23:01:39.364757281 +0200
@@ -1,6 +1,6 @@
 <servicedata>
 <service name="tar_scm">
                 <param 
name="url">https://github.com/Azure/kubelogin.git</param>
-              <param 
name="changesrevision">a9b10fbf8422f0c5b687eb58f26d7995f2fe206d</param></service></servicedata>
+              <param 
name="changesrevision">51be4471f0367039d4f79029f2ead5408199076d</param></service></servicedata>
 (No newline at EOF)
 

++++++ kubelogin-0.2.19.obscpio -> kubelogin-0.2.20.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kubelogin-0.2.19/.github/dependabot.yml 
new/kubelogin-0.2.20/.github/dependabot.yml
--- old/kubelogin-0.2.19/.github/dependabot.yml 2026-06-23 19:26:47.000000000 
+0200
+++ new/kubelogin-0.2.20/.github/dependabot.yml 2026-09-23 19:41:35.000000000 
+0200
@@ -14,3 +14,5 @@
   directory: /
   schedule:
     interval: daily
+  cooldown:
+    default-days: 7
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kubelogin-0.2.19/.github/workflows/build.yml 
new/kubelogin-0.2.20/.github/workflows/build.yml
--- old/kubelogin-0.2.19/.github/workflows/build.yml    2026-06-23 
19:26:47.000000000 +0200
+++ new/kubelogin-0.2.20/.github/workflows/build.yml    2026-09-23 
19:41:35.000000000 +0200
@@ -27,10 +27,10 @@
       GO111MODULE: on
     steps:
       - name: Check out code
-        uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # 
v7.0.0
+        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # 
v7.0.1
 
       - name: Set up Go
-        uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # 
v6.4.0
+        uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # 
v6.5.0
         with:
           go-version-file: "go.mod"
           cache: false
@@ -47,10 +47,10 @@
     needs: test
     steps:
       - name: Check out code
-        uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # 
v7.0.0
+        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # 
v7.0.1
 
       - name: Set up Go
-        uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # 
v6.4.0
+        uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # 
v6.5.0
         with:
           go-version-file: "go.mod"
           cache: false
@@ -97,10 +97,10 @@
     needs: test
     steps:
       - name: Check out code
-        uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # 
v7.0.0
+        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # 
v7.0.1
 
       - name: Set up Go
-        uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # 
v6.4.0
+        uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # 
v6.5.0
         with:
           go-version-file: "go.mod"
           cache: false
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/kubelogin-0.2.19/.github/workflows/dependency-review.yml 
new/kubelogin-0.2.20/.github/workflows/dependency-review.yml
--- old/kubelogin-0.2.19/.github/workflows/dependency-review.yml        
2026-06-23 19:26:47.000000000 +0200
+++ new/kubelogin-0.2.20/.github/workflows/dependency-review.yml        
2026-09-23 19:41:35.000000000 +0200
@@ -17,6 +17,6 @@
     runs-on: ubuntu-latest
     steps:
       - name: 'Checkout Repository'
-        uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # 
v7.0.0
+        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # 
v7.0.1
       - name: 'Dependency Review'
         uses: 
actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # 
v5.0.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/kubelogin-0.2.19/.github/workflows/docker-publish.yml 
new/kubelogin-0.2.20/.github/workflows/docker-publish.yml
--- old/kubelogin-0.2.19/.github/workflows/docker-publish.yml   2026-06-23 
19:26:47.000000000 +0200
+++ new/kubelogin-0.2.20/.github/workflows/docker-publish.yml   2026-09-23 
19:41:35.000000000 +0200
@@ -26,19 +26,19 @@
         run: echo "IMAGE_NAME=$(echo '${{ github.repository }}' | tr 
'[:upper:]' '[:lower:]')" >> $GITHUB_ENV
 
       - name: Check out code
-        uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # 
v7.0.0
+        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # 
v7.0.1
 
       - name: Set up Go
-        uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # 
v6.4.0
+        uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # 
v6.5.0
         with:
           go-version-file: "go.mod"
           cache: false
 
       - name: Set up Docker Buildx
-        uses: 
docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
+        uses: 
docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0
 
       - name: Log in to GitHub Container Registry
-        uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # 
v4.2.0
+        uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # 
v4.6.0
         with:
           registry: ${{ env.REGISTRY }}
           username: ${{ github.actor }}
@@ -70,7 +70,7 @@
           file bin/linux_arm64/kubelogin
 
       - name: Build and push Docker image
-        uses: 
docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
+        uses: 
docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
         with:
           context: .
           platforms: linux/amd64,linux/arm64
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kubelogin-0.2.19/.github/workflows/golangci-lint.yml 
new/kubelogin-0.2.20/.github/workflows/golangci-lint.yml
--- old/kubelogin-0.2.19/.github/workflows/golangci-lint.yml    2026-06-23 
19:26:47.000000000 +0200
+++ new/kubelogin-0.2.20/.github/workflows/golangci-lint.yml    2026-09-23 
19:41:35.000000000 +0200
@@ -26,12 +26,12 @@
       deployments: read
       packages: none
     steps:
-      - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # 
v7.0.0
-      - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # 
v6.4.0
+      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # 
v7.0.1
+      - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # 
v6.5.0
         with:
           go-version-file: "go.mod"
           cache: false
       - name: golangci-lint
-        uses: 
golangci/golangci-lint-action@82606bf257cbaff209d206a39f5134f0cfbfd2ee # v9.2.1
+        uses: 
golangci/golangci-lint-action@ba0d7d2ec06a0ea1cb5fa41b2e4a3ab91d21278a # v9.3.0
         with:
           version: v2.12.2
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kubelogin-0.2.19/.github/workflows/release.yml 
new/kubelogin-0.2.20/.github/workflows/release.yml
--- old/kubelogin-0.2.19/.github/workflows/release.yml  2026-06-23 
19:26:47.000000000 +0200
+++ new/kubelogin-0.2.20/.github/workflows/release.yml  2026-09-23 
19:41:35.000000000 +0200
@@ -19,7 +19,7 @@
       tag_version: "v${{ steps.changelog_reader.outputs.version }}"
     steps:
       - name: Check out code into the Go module directory
-        uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # 
v7.0.0
+        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # 
v7.0.1
         
       # Read changelog and read versions etc.
       - name: Check version is mentioned in Changelog.md
@@ -58,13 +58,13 @@
     if: ${{ needs.create-release.outputs.release_id != '' }}
     steps:
       - name: Check out code
-        uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # 
v7.0.0
+        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # 
v7.0.1
 
       - name: Get tags
         run: git fetch --tags
 
       - name: Set up Go
-        uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # 
v6.4.0
+        uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # 
v6.5.0
         with:
           go-version-file: "go.mod"
           cache: false
@@ -119,13 +119,13 @@
           tar -czf windows-kubelogin.tar.gz bin/windows_*
 
       - name: Upload Linux artifacts
-        uses: actions/upload-artifact@v7
+        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a 
# v7.0.1
         with:
           name: linux-binaries
           path: linux-kubelogin.tar.gz
       
       - name: Upload Windows artifacts
-        uses: actions/upload-artifact@v7
+        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a 
# v7.0.1
         with:
           name: windows-binaries
           path: windows-kubelogin.tar.gz
@@ -137,13 +137,13 @@
     if: ${{ needs.create-release.outputs.release_id != '' }}
     steps:
       - name: Check out code
-        uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # 
v7.0.0
+        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # 
v7.0.1
 
       - name: Get tags
         run: git fetch --tags
 
       - name: Set up Go
-        uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # 
v6.4.0
+        uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # 
v6.5.0
         with:
           go-version-file: "go.mod"
           cache: false
@@ -169,7 +169,7 @@
           tar -czf macos-kubelogin.tar.gz bin/darwin_*
 
       - name: Upload macOS artifacts
-        uses: actions/upload-artifact@v7
+        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a 
# v7.0.1
         with:
           name: macos-binaries
           path: macos-kubelogin.tar.gz
@@ -183,7 +183,7 @@
     if: ${{ needs.create-release.outputs.release_id != '' }}
     steps:
       - name: Download all artifacts
-        uses: actions/download-artifact@v8
+        uses: 
actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
         with:
           path: bin
 
@@ -255,9 +255,27 @@
         run: echo -n "${{ needs.create-release.outputs.tag_version }}" > 
kubelogin-version.txt
 
       - name: Publish
-        uses: 
skx/github-action-publish-binaries@44887b225ceca96efd8a912d39c09ad70312af31 # 
master
         env:
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-        with:
-          args: kubelogin.zip kubelogin-win-amd64.zip kubelogin-win-arm64.zip 
kubelogin-darwin-amd64.zip kubelogin-darwin-arm64.zip kubelogin-linux-amd64.zip 
kubelogin-linux-arm64.zip kubelogin-linux-armv7.zip kubelogin.zip.sha256 
kubelogin-win-amd64.zip.sha256 kubelogin-win-arm64.zip.sha256 
kubelogin-darwin-amd64.zip.sha256 kubelogin-darwin-arm64.zip.sha256 
kubelogin-linux-amd64.zip.sha256 kubelogin-linux-arm64.zip.sha256 
kubelogin-linux-armv7.zip.sha256 kubelogin-version.txt
-          releaseId: ${{ needs.create-release.outputs.release_id }}
+          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          GH_REPO: ${{ github.repository }}
+          RELEASE_TAG: ${{ needs.create-release.outputs.tag_version }}
+        run: |
+          gh release upload "$RELEASE_TAG" \
+            kubelogin.zip \
+            kubelogin.zip.sha256 \
+            kubelogin-win-amd64.zip \
+            kubelogin-win-amd64.zip.sha256 \
+            kubelogin-win-arm64.zip \
+            kubelogin-win-arm64.zip.sha256 \
+            kubelogin-darwin-amd64.zip \
+            kubelogin-darwin-amd64.zip.sha256 \
+            kubelogin-darwin-arm64.zip \
+            kubelogin-darwin-arm64.zip.sha256 \
+            kubelogin-linux-amd64.zip \
+            kubelogin-linux-amd64.zip.sha256 \
+            kubelogin-linux-arm64.zip \
+            kubelogin-linux-arm64.zip.sha256 \
+            kubelogin-linux-armv7.zip \
+            kubelogin-linux-armv7.zip.sha256 \
+            kubelogin-version.txt \
+            --clobber
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/kubelogin-0.2.19/.github/workflows/update-changelog.yml 
new/kubelogin-0.2.20/.github/workflows/update-changelog.yml
--- old/kubelogin-0.2.19/.github/workflows/update-changelog.yml 2026-06-23 
19:26:47.000000000 +0200
+++ new/kubelogin-0.2.20/.github/workflows/update-changelog.yml 2026-09-23 
19:41:35.000000000 +0200
@@ -25,13 +25,13 @@
     steps:
       - name: Checkout repository
         # v4.1.1
-        uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
+        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # 
v7.0.1
         with:
           fetch-depth: 0  # Fetch all history
 
       - name: Set up Go
         # v4.1.0
-        uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c
+        uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # 
v6.5.0
         with:
           go-version-file: "go.mod"
           cache: true
@@ -60,7 +60,7 @@
           rm changelog-entry.md
 
       - name: Create Pull Request
-        uses: peter-evans/create-pull-request@v8
+        uses: 
peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # 
v8.1.1
         with:
           token: ${{ secrets.GITHUB_TOKEN }}
           commit-message: >-
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kubelogin-0.2.19/.github/workflows/website.yaml 
new/kubelogin-0.2.20/.github/workflows/website.yaml
--- old/kubelogin-0.2.19/.github/workflows/website.yaml 2026-06-23 
19:26:47.000000000 +0200
+++ new/kubelogin-0.2.20/.github/workflows/website.yaml 2026-09-23 
19:41:35.000000000 +0200
@@ -16,7 +16,7 @@
       
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # 
v7.0.0
+      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # 
v7.0.1
         with:
           submodules: true
           fetch-depth: 0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kubelogin-0.2.19/CHANGELOG.md 
new/kubelogin-0.2.20/CHANGELOG.md
--- old/kubelogin-0.2.19/CHANGELOG.md   2026-06-23 19:26:47.000000000 +0200
+++ new/kubelogin-0.2.20/CHANGELOG.md   2026-09-23 19:41:35.000000000 +0200
@@ -1,5 +1,39 @@
 # Change Log
 
+## [0.2.20]
+
+### Bug Fixes
+
+* Fix release asset publishing by replacing skx/github-action-publish-binaries 
with GitHub CLI by @Copilot in https://github.com/Azure/kubelogin/pull/815
+
+### Maintenance
+
+* Pin GitHub Actions to full-length commit SHAs by @danfiedler-msft in 
https://github.com/Azure/kubelogin/pull/799
+* Add test coverage for NeedAuthenticate across all credential types by 
@NaeemH in https://github.com/Azure/kubelogin/pull/794
+* Add unit tests for client certificate PEM/PKCS helpers by @NaeemH in 
https://github.com/Azure/kubelogin/pull/793
+* Bump golang.org/x/sys from 0.45.0 to 0.48.0 by @dependabot[bot] in 
https://github.com/Azure/kubelogin/pull/808
+* Bump golang.org/x/crypto to v0.56.0 by @harsh-im in 
https://github.com/Azure/kubelogin/pull/806
+* Bump gopkg.in/dnaeon/go-vcr.v4 from 4.0.2 to 4.0.7 by @dependabot[bot] in 
https://github.com/Azure/kubelogin/pull/804
+* Bump docker/setup-buildx-action from 4.1.0 to 4.3.0 by @dependabot[bot] in 
https://github.com/Azure/kubelogin/pull/800
+* Bump actions/checkout from 7.0.0 to 7.0.1 by @dependabot[bot] in 
https://github.com/Azure/kubelogin/pull/798
+* Bump docker/login-action from 4.2.0 to 4.6.0 by @dependabot[bot] in 
https://github.com/Azure/kubelogin/pull/797
+* Bump docker/build-push-action from 7.2.0 to 7.3.0 by @dependabot[bot] in 
https://github.com/Azure/kubelogin/pull/790
+* Bump golangci/golangci-lint-action from 9.2.1 to 9.3.0 by @dependabot[bot] 
in https://github.com/Azure/kubelogin/pull/789
+* Bump actions/setup-go from 6.4.0 to 6.5.0 by @dependabot[bot] in 
https://github.com/Azure/kubelogin/pull/788
+
+### Doc Update
+
+* Explain how to switch device code login to interactive by @1fanwang in 
https://github.com/Azure/kubelogin/pull/795
+
+### New Contributors
+
+* @1fanwang made their first contribution in 
https://github.com/Azure/kubelogin/pull/795
+* @NaeemH made their first contribution in 
https://github.com/Azure/kubelogin/pull/794
+* @danfiedler-msft made their first contribution in 
https://github.com/Azure/kubelogin/pull/799
+* @harsh-im made their first contribution in 
https://github.com/Azure/kubelogin/pull/806
+
+**Full Changelog**: 
https://github.com/Azure/kubelogin/compare/v0.2.19...v0.2.20
+
 ## [0.2.19]
 
 ### What's Changed
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/kubelogin-0.2.19/docs/book/src/concepts/login-modes/devicecode.md 
new/kubelogin-0.2.20/docs/book/src/concepts/login-modes/devicecode.md
--- old/kubelogin-0.2.19/docs/book/src/concepts/login-modes/devicecode.md       
2026-06-23 19:26:47.000000000 +0200
+++ new/kubelogin-0.2.20/docs/book/src/concepts/login-modes/devicecode.md       
2026-09-23 19:41:35.000000000 +0200
@@ -21,6 +21,25 @@
 
 ```
 
+## Using Interactive Mode Instead
+
+Device code login asks the user to open a URL and type a code by hand. Entra 
ID does not return the
+optional `verification_uri_complete` field from [RFC 
8628](https://datatracker.ietf.org/doc/html/rfc8628#section-3.3.1),
+so the URL and the code cannot be combined into a single link. When the 
machine running `kubectl`
+has a browser, [web browser interactive mode](./interactive.md) avoids the 
copying altogether.
+
+If the kubeconfig was provisioned for you and already pins 
`--login=devicecode` in its exec args,
+you don't have to edit it. Environment variables are applied after the flags 
are parsed, so
+`AAD_LOGIN_METHOD` takes precedence over the login mode stored in the 
kubeconfig:
+
+```sh
+export AAD_LOGIN_METHOD=interactive
+
+kubectl get nodes
+```
+
+Passing `--disable-environment-override` turns this off and keeps the login 
mode from the kubeconfig.
+
 ## Restrictions
 
 - Device code login mode doesn't work when Conditional Access policy is 
configured on AAD tenant. Use [web browser interactive mode](./interactive.md) 
instead.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kubelogin-0.2.19/go.mod new/kubelogin-0.2.20/go.mod
--- old/kubelogin-0.2.19/go.mod 2026-06-23 19:26:47.000000000 +0200
+++ new/kubelogin-0.2.20/go.mod 2026-09-23 19:41:35.000000000 +0200
@@ -18,9 +18,9 @@
        github.com/spf13/pflag v1.0.5
        github.com/stretchr/testify v1.11.1
        go.uber.org/mock v0.5.0
-       golang.org/x/crypto v0.52.0
-       golang.org/x/sys v0.45.0
-       gopkg.in/dnaeon/go-vcr.v4 v4.0.2
+       golang.org/x/crypto v0.56.0
+       golang.org/x/sys v0.48.0
+       gopkg.in/dnaeon/go-vcr.v4 v4.0.7
        k8s.io/apimachinery v0.29.3
        k8s.io/cli-runtime v0.29.3
        k8s.io/client-go v0.29.3
@@ -69,11 +69,12 @@
        github.com/pmezard/go-difflib v1.0.0 // indirect
        github.com/xlab/treeprint v1.2.0 // indirect
        go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect
-       golang.org/x/net v0.55.0 // indirect
+       go.yaml.in/yaml/v4 v4.0.0-rc.6 // indirect
+       golang.org/x/net v0.57.0 // indirect
        golang.org/x/oauth2 v0.30.0 // indirect
-       golang.org/x/sync v0.20.0 // indirect
-       golang.org/x/term v0.43.0 // indirect
-       golang.org/x/text v0.37.0 // indirect
+       golang.org/x/sync v0.22.0 // indirect
+       golang.org/x/term v0.45.0 // indirect
+       golang.org/x/text v0.41.0 // indirect
        golang.org/x/time v0.3.0 // indirect
        google.golang.org/protobuf v1.33.0 // indirect
        gopkg.in/inf.v0 v0.9.1 // indirect
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kubelogin-0.2.19/go.sum new/kubelogin-0.2.20/go.sum
--- old/kubelogin-0.2.19/go.sum 2026-06-23 19:26:47.000000000 +0200
+++ new/kubelogin-0.2.20/go.sum 2026-09-23 19:41:35.000000000 +0200
@@ -184,14 +184,16 @@
 go.starlark.net v0.0.0-20230525235612-a134d8f9ddca/go.mod 
h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds=
 go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
 go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
+go.yaml.in/yaml/v4 v4.0.0-rc.6 h1:1h7H1ohdUh93/FyE4YaDa1Zh64K6VVbjF4K6WUxMtH4=
+go.yaml.in/yaml/v4 v4.0.0-rc.6/go.mod 
h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod 
h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod 
h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod 
h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod 
h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod 
h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.6.0/go.mod 
h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
-golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
-golang.org/x/crypto v0.52.0/go.mod 
h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc=
+golang.org/x/crypto v0.56.0 h1:GUh5Ii4J5jtcseSMiRqr1jXCNHoxjeV9Fmekc2oLy6Y=
+golang.org/x/crypto v0.56.0/go.mod 
h1:OMW5y6CY9l38uPLmxU6l6pwcXp1obtLo3e6gT7gQR2I=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod 
h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod 
h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod 
h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
@@ -211,8 +213,8 @@
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod 
h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod 
h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
-golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
-golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
+golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE=
+golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod 
h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
 golang.org/x/oauth2 v0.30.0/go.mod 
h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
@@ -222,8 +224,8 @@
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod 
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod 
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod 
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
-golang.org/x/sync v0.20.0/go.mod 
h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
+golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
+golang.org/x/sync v0.22.0/go.mod 
h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod 
h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod 
h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod 
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -237,21 +239,21 @@
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod 
h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
-golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
+golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo=
+golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod 
h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod 
h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod 
h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
-golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
-golang.org/x/term v0.43.0/go.mod 
h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
+golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0=
+golang.org/x/term v0.45.0/go.mod 
h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
-golang.org/x/text v0.37.0/go.mod 
h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
+golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=
+golang.org/x/text v0.41.0/go.mod 
h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M=
 golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
 golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod 
h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -263,8 +265,8 @@
 golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod 
h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod 
h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.1.12/go.mod 
h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
-golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
-golang.org/x/tools v0.44.0/go.mod 
h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
+golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE=
+golang.org/x/tools v0.48.0/go.mod 
h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod 
h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod 
h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod 
h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -290,8 +292,8 @@
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod 
h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c 
h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod 
h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
-gopkg.in/dnaeon/go-vcr.v4 v4.0.2 
h1:7T5VYf2ifyK01ETHbJPl5A6XTpUljD4Trw3GEDcdedk=
-gopkg.in/dnaeon/go-vcr.v4 v4.0.2/go.mod 
h1:65yxh9goQVrudqofKtHA4JNFWd6XZRkWfKN4YpMx7KI=
+gopkg.in/dnaeon/go-vcr.v4 v4.0.7 
h1:Mq/RF+mq3QwtEunJSsoTbYPt3elSAmdJhAxrEaqr88I=
+gopkg.in/dnaeon/go-vcr.v4 v4.0.7/go.mod 
h1:cRwV/njsN/D8qNJu4NAXWswz6b4OUh3rMIu4SObbLBg=
 gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
 gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
 gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/kubelogin-0.2.19/pkg/internal/token/clientcertcredential_helpers_test.go 
new/kubelogin-0.2.20/pkg/internal/token/clientcertcredential_helpers_test.go
--- 
old/kubelogin-0.2.19/pkg/internal/token/clientcertcredential_helpers_test.go    
    1970-01-01 01:00:00.000000000 +0100
+++ 
new/kubelogin-0.2.20/pkg/internal/token/clientcertcredential_helpers_test.go    
    2026-09-23 19:41:35.000000000 +0200
@@ -0,0 +1,204 @@
+package token
+
+import (
+       "crypto/ecdsa"
+       "crypto/elliptic"
+       "crypto/rand"
+       "crypto/rsa"
+       "crypto/x509"
+       "crypto/x509/pkix"
+       "encoding/pem"
+       "math/big"
+       "os"
+       "path/filepath"
+       "testing"
+       "time"
+
+       "github.com/stretchr/testify/assert"
+       "github.com/stretchr/testify/require"
+)
+
+// certTestKeyPair generates an RSA key and a matching self-signed certificate
+// (returned as DER) for exercising the PEM/PKCS certificate helpers.
+func certTestKeyPair(t *testing.T) (*rsa.PrivateKey, []byte) {
+       t.Helper()
+       key, err := rsa.GenerateKey(rand.Reader, 2048)
+       require.NoError(t, err)
+
+       tmpl := &x509.Certificate{
+               SerialNumber: big.NewInt(1),
+               Subject:      pkix.Name{CommonName: "kubelogin-test"},
+               NotBefore:    time.Now().Add(-time.Hour),
+               NotAfter:     time.Now().Add(time.Hour),
+       }
+       der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, 
&key.PublicKey, key)
+       require.NoError(t, err)
+       return key, der
+}
+
+func certTestCertPEM(der []byte) []byte {
+       return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der})
+}
+
+func certTestPKCS8PEM(t *testing.T, key *rsa.PrivateKey) []byte {
+       t.Helper()
+       der, err := x509.MarshalPKCS8PrivateKey(key)
+       require.NoError(t, err)
+       return pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: der})
+}
+
+func TestIsPublicKeyEqual(t *testing.T) {
+       key1, _ := certTestKeyPair(t)
+       key2, _ := certTestKeyPair(t)
+
+       assert.True(t, isPublicKeyEqual(&key1.PublicKey, &key1.PublicKey), 
"identical keys should be equal")
+       assert.False(t, isPublicKeyEqual(&key1.PublicKey, &key2.PublicKey), 
"different keys should not be equal")
+       assert.False(t, isPublicKeyEqual(&rsa.PublicKey{}, &key1.PublicKey), 
"nil modulus (left) should not be equal")
+       assert.False(t, isPublicKeyEqual(&key1.PublicKey, &rsa.PublicKey{}), 
"nil modulus (right) should not be equal")
+}
+
+func TestParseRsaPrivateKey(t *testing.T) {
+       key, _ := certTestKeyPair(t)
+
+       pkcs1PEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", 
Bytes: x509.MarshalPKCS1PrivateKey(key)})
+
+       pkcs8DER, err := x509.MarshalPKCS8PrivateKey(key)
+       require.NoError(t, err)
+       pkcs8PEM := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: 
pkcs8DER})
+
+       ecKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+       require.NoError(t, err)
+       ecDER, err := x509.MarshalPKCS8PrivateKey(ecKey)
+       require.NoError(t, err)
+       ecPEM := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: 
ecDER})
+
+       testCases := []struct {
+               name         string
+               input        []byte
+               expectErr    bool
+               expectErrMsg string
+       }{
+               {name: "pkcs1 rsa key", input: pkcs1PEM},
+               {name: "pkcs8 rsa key", input: pkcs8PEM},
+               {name: "pkcs8 non-rsa key", input: ecPEM, expectErr: true, 
expectErrMsg: "non-RSA"},
+               {name: "not a pem block", input: []byte("not a pem block"), 
expectErr: true, expectErrMsg: "failed to decode"},
+               {name: "empty input", input: nil, expectErr: true, 
expectErrMsg: "failed to decode"},
+       }
+
+       for _, tc := range testCases {
+               t.Run(tc.name, func(t *testing.T) {
+                       got, err := parseRsaPrivateKey(tc.input)
+                       if tc.expectErr {
+                               require.Error(t, err)
+                               assert.Contains(t, err.Error(), tc.expectErrMsg)
+                               assert.Nil(t, got)
+                               return
+                       }
+                       require.NoError(t, err)
+                       require.NotNil(t, got)
+                       assert.Equal(t, 0, 
key.PublicKey.N.Cmp(got.PublicKey.N), "parsed key should match the original")
+               })
+       }
+}
+
+func TestSplitPEMBlock(t *testing.T) {
+       key, der := certTestKeyPair(t)
+       certPEM := certTestCertPEM(der)
+       keyPEM := certTestPKCS8PEM(t, key)
+       unknownPEM := pem.EncodeToMemory(&pem.Block{Type: "SOMETHING ELSE", 
Bytes: []byte("ignored")})
+
+       t.Run("separates cert and key and ignores unknown blocks", func(t 
*testing.T) {
+               combined := bytesJoin(certPEM, keyPEM, unknownPEM)
+               gotCert, gotKey := splitPEMBlock(combined)
+
+               assert.NotEmpty(t, gotCert, "expected a certificate block")
+               assert.NotEmpty(t, gotKey, "expected a private key block")
+               assert.NotContains(t, string(gotCert), "SOMETHING ELSE")
+               assert.NotContains(t, string(gotKey), "SOMETHING ELSE")
+
+               parsedKey, err := parseRsaPrivateKey(gotKey)
+               require.NoError(t, err)
+               assert.Equal(t, 0, key.PublicKey.N.Cmp(parsedKey.PublicKey.N))
+       })
+
+       t.Run("returns nil for input without pem blocks", func(t *testing.T) {
+               gotCert, gotKey := splitPEMBlock([]byte("no pem here"))
+               assert.Nil(t, gotCert)
+               assert.Nil(t, gotKey)
+       })
+}
+
+func TestParseKeyPairFromPEMBlock(t *testing.T) {
+       key, der := certTestKeyPair(t)
+       certPEM := certTestCertPEM(der)
+       keyPEM := certTestPKCS8PEM(t, key)
+
+       _, otherDER := certTestKeyPair(t)
+       otherCertPEM := certTestCertPEM(otherDER)
+
+       t.Run("matching cert and key", func(t *testing.T) {
+               cert, priv, err := parseKeyPairFromPEMBlock(bytesJoin(certPEM, 
keyPEM))
+               require.NoError(t, err)
+               require.NotNil(t, cert)
+               require.NotNil(t, priv)
+               assert.Equal(t, 0, key.PublicKey.N.Cmp(priv.PublicKey.N))
+       })
+
+       t.Run("cert does not match key", func(t *testing.T) {
+               _, _, err := parseKeyPairFromPEMBlock(bytesJoin(otherCertPEM, 
keyPEM))
+               require.Error(t, err)
+               assert.Contains(t, err.Error(), "unable to find a matching 
public certificate")
+       })
+
+       t.Run("missing private key", func(t *testing.T) {
+               _, _, err := parseKeyPairFromPEMBlock(certPEM)
+               require.Error(t, err)
+               assert.Contains(t, err.Error(), "failed to decode")
+       })
+}
+
+func TestDecodePkcs12(t *testing.T) {
+       t.Run("invalid pkcs12 data returns error", func(t *testing.T) {
+               cert, key, err := decodePkcs12([]byte("not a valid pkcs12 
blob"), "")
+               require.Error(t, err)
+               assert.Nil(t, cert)
+               assert.Nil(t, key)
+       })
+}
+
+func TestReadCertificate(t *testing.T) {
+       key, der := certTestKeyPair(t)
+       combined := bytesJoin(certTestCertPEM(der), certTestPKCS8PEM(t, key))
+       dir := t.TempDir()
+
+       t.Run("valid pem file", func(t *testing.T) {
+               p := filepath.Join(dir, "cert.pem")
+               require.NoError(t, os.WriteFile(p, combined, 0o600))
+
+               cert, priv, err := readCertificate(p, "")
+               require.NoError(t, err)
+               require.NotNil(t, cert)
+               assert.Equal(t, 0, key.PublicKey.N.Cmp(priv.PublicKey.N))
+       })
+
+       t.Run("missing pem file", func(t *testing.T) {
+               _, _, err := readCertificate(filepath.Join(dir, 
"does-not-exist.pem"), "")
+               require.Error(t, err)
+               assert.Contains(t, err.Error(), "failed to read the certificate 
file")
+       })
+
+       t.Run("missing pfx file", func(t *testing.T) {
+               _, _, err := readCertificate(filepath.Join(dir, 
"does-not-exist.pfx"), "")
+               require.Error(t, err)
+               assert.Contains(t, err.Error(), "failed to read the certificate 
file")
+       })
+}
+
+// bytesJoin concatenates byte slices without a separator.
+func bytesJoin(parts ...[]byte) []byte {
+       var out []byte
+       for _, p := range parts {
+               out = append(out, p...)
+       }
+       return out
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/kubelogin-0.2.19/pkg/internal/token/clientcertcredential_pfx_test.go 
new/kubelogin-0.2.20/pkg/internal/token/clientcertcredential_pfx_test.go
--- old/kubelogin-0.2.19/pkg/internal/token/clientcertcredential_pfx_test.go    
1970-01-01 01:00:00.000000000 +0100
+++ new/kubelogin-0.2.20/pkg/internal/token/clientcertcredential_pfx_test.go    
2026-09-23 19:41:35.000000000 +0200
@@ -0,0 +1,78 @@
+package token
+
+import (
+       "crypto/rsa"
+       "encoding/base64"
+       "os"
+       "path/filepath"
+       "testing"
+
+       "github.com/stretchr/testify/assert"
+       "github.com/stretchr/testify/require"
+)
+
+// testPFXBase64 is a throwaway, self-signed RSA certificate packaged as a
+// password-protected PKCS#12 (PFX) file, base64-encoded. The password is
+// testPFXPassword. It intentionally uses legacy SHA1/3DES PBE so it can be
+// decoded by golang.org/x/crypto/pkcs12, which does not support the AES-based
+// algorithms OpenSSL 3 emits by default.
+//
+// Regenerate with:
+//
+//     openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem \
+//         -days 3650 -nodes -subj "/CN=kubelogin-test"
+//     openssl pkcs12 -export -inkey key.pem -in cert.pem -out test.pfx \
+//         -passout pass:kubelogin-test -legacy \
+//         -certpbe PBE-SHA1-3DES -keypbe PBE-SHA1-3DES -macalg sha1
+//     base64 -w0 test.pfx
+const testPFXBase64 = 
"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"
+
+const testPFXPassword = "kubelogin-test"
+
+func testPFXBytes(t *testing.T) []byte {
+       t.Helper()
+       data, err := base64.StdEncoding.DecodeString(testPFXBase64)
+       require.NoError(t, err)
+       return data
+}
+
+func TestDecodePkcs12WithPassword(t *testing.T) {
+       pfx := testPFXBytes(t)
+
+       t.Run("correct password decodes cert and key", func(t *testing.T) {
+               cert, key, err := decodePkcs12(pfx, testPFXPassword)
+               require.NoError(t, err)
+               require.NotNil(t, cert)
+               require.NotNil(t, key)
+               assert.Equal(t, "kubelogin-test", cert.Subject.CommonName)
+               assert.IsType(t, &rsa.PrivateKey{}, key)
+               require.NoError(t, key.Validate())
+       })
+
+       t.Run("wrong password returns error", func(t *testing.T) {
+               cert, key, err := decodePkcs12(pfx, "not-the-password")
+               require.Error(t, err)
+               assert.Nil(t, cert)
+               assert.Nil(t, key)
+       })
+}
+
+func TestReadCertificatePFX(t *testing.T) {
+       pfx := testPFXBytes(t)
+       dir := t.TempDir()
+       pfxPath := filepath.Join(dir, "client.pfx")
+       require.NoError(t, os.WriteFile(pfxPath, pfx, 0o600))
+
+       t.Run("valid pfx with correct password", func(t *testing.T) {
+               cert, key, err := readCertificate(pfxPath, testPFXPassword)
+               require.NoError(t, err)
+               require.NotNil(t, cert)
+               require.NotNil(t, key)
+               assert.Equal(t, "kubelogin-test", cert.Subject.CommonName)
+       })
+
+       t.Run("valid pfx with wrong password", func(t *testing.T) {
+               _, _, err := readCertificate(pfxPath, "not-the-password")
+               require.Error(t, err)
+       })
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/kubelogin-0.2.19/pkg/internal/token/needauthenticate_test.go 
new/kubelogin-0.2.20/pkg/internal/token/needauthenticate_test.go
--- old/kubelogin-0.2.19/pkg/internal/token/needauthenticate_test.go    
1970-01-01 01:00:00.000000000 +0100
+++ new/kubelogin-0.2.20/pkg/internal/token/needauthenticate_test.go    
2026-09-23 19:41:35.000000000 +0200
@@ -0,0 +1,47 @@
+package token
+
+import (
+       "testing"
+
+       "github.com/stretchr/testify/assert"
+)
+
+// TestNeedAuthenticate locks in which credential types require the MSAL
+// Authenticate() step before GetToken. Only the interactive / public-client
+// flows (device code, interactive browser, username-password) report true;
+// every other credential authenticates as part of GetToken and reports false.
+//
+// NeedAuthenticate is a constant predicate on each type, so a zero-value
+// instance is sufficient to exercise it.
+func TestNeedAuthenticate(t *testing.T) {
+       testCases := []struct {
+               name string
+               cred interface{ NeedAuthenticate() bool }
+               want bool
+       }{
+               {"ADALClientCertCredential", &ADALClientCertCredential{}, 
false},
+               {"ADALClientSecretCredential", &ADALClientSecretCredential{}, 
false},
+               {"ADALDeviceCodeCredential", &ADALDeviceCodeCredential{}, 
false},
+               {"AzureCLICredential", &AzureCLICredential{}, false},
+               {"AzureDeveloperCLICredential", &AzureDeveloperCLICredential{}, 
false},
+               {"AzurePipelinesCredential", &AzurePipelinesCredential{}, 
false},
+               {"ClientCertificateCredential", &ClientCertificateCredential{}, 
false},
+               {"ClientCertificateCredentialWithPoP", 
&ClientCertificateCredentialWithPoP{}, false},
+               {"ClientSecretCredential", &ClientSecretCredential{}, false},
+               {"ClientSecretCredentialWithPoP", 
&ClientSecretCredentialWithPoP{}, false},
+               {"DeviceCodeCredential", &DeviceCodeCredential{}, true},
+               {"GithubActionsCredential", &GithubActionsCredential{}, false},
+               {"InteractiveBrowserCredential", 
&InteractiveBrowserCredential{}, true},
+               {"InteractiveBrowserCredentialWithPoP", 
&InteractiveBrowserCredentialWithPoP{}, false},
+               {"ManagedIdentityCredential", &ManagedIdentityCredential{}, 
false},
+               {"UsernamePasswordCredential", &UsernamePasswordCredential{}, 
true},
+               {"UsernamePasswordCredentialWithPoP", 
&UsernamePasswordCredentialWithPoP{}, false},
+               {"WorkloadIdentityCredential", &WorkloadIdentityCredential{}, 
false},
+       }
+
+       for _, tc := range testCases {
+               t.Run(tc.name, func(t *testing.T) {
+                       assert.Equal(t, tc.want, tc.cred.NeedAuthenticate())
+               })
+       }
+}

++++++ kubelogin.obsinfo ++++++
--- /var/tmp/diff_new_pack.M77kHC/_old  2026-09-24 23:01:39.516763637 +0200
+++ /var/tmp/diff_new_pack.M77kHC/_new  2026-09-24 23:01:39.519763762 +0200
@@ -1,5 +1,5 @@
 name: kubelogin
-version: 0.2.19
-mtime: 1782235607
-commit: a9b10fbf8422f0c5b687eb58f26d7995f2fe206d
+version: 0.2.20
+mtime: 1790185295
+commit: 51be4471f0367039d4f79029f2ead5408199076d
 

++++++ vendor.tar.gz ++++++
/work/SRC/openSUSE:Factory/kubelogin/vendor.tar.gz 
/work/SRC/openSUSE:Factory/.kubelogin.new.383539/vendor.tar.gz differ: char 12, 
line 1

Reply via email to