File kubelogin-0.2.13.obscpio of Package kubelogin

07070100000000000041ED000000000000000000000002691F8CFD00000000000000000000000000000000000000000000001800000000kubelogin-0.2.13/.bingo07070100000001000081A4000000000000000000000001691F8CFD0000007B000000000000000000000000000000000000002300000000kubelogin-0.2.13/.bingo/.gitignore
# Ignore everything
*

# But not these files:
!.gitignore
!*.mod
!*.sum
!README.md
!Variables.mk
!variables.env

*tmp.mod
07070100000002000081A4000000000000000000000001691F8CFD00000334000000000000000000000000000000000000002200000000kubelogin-0.2.13/.bingo/README.md# Project Development Dependencies.

This is directory which stores Go modules with pinned buildable package that is used within this repository, managed by https://github.com/bwplotka/bingo.

* Run `bingo get` to install all tools having each own module file in this directory.
* Run `bingo get <tool>` to install <tool> that have own module file in this directory.
* For Makefile: Make sure to put `include .bingo/Variables.mk` in your Makefile, then use $(<upper case tool name>) variable where <tool> is the .bingo/<tool>.mod.
* For shell: Run `source .bingo/variables.env` to source all environment variable for each tool.
* For go: Import `.bingo/variables.go` to for variable names.
* See https://github.com/bwplotka/bingo or -h on how to add, remove or change binaries dependencies.

## Requirements

* Go 1.14+
07070100000003000081A4000000000000000000000001691F8CFD000004A9000000000000000000000000000000000000002500000000kubelogin-0.2.13/.bingo/Variables.mk# Auto generated binary variables helper managed by https://github.com/bwplotka/bingo v0.9. DO NOT EDIT.
# All tools are designed to be build inside $GOBIN.
BINGO_DIR := $(dir $(lastword $(MAKEFILE_LIST)))
GOPATH ?= $(shell go env GOPATH)
GOBIN  ?= $(firstword $(subst :, ,${GOPATH}))/bin
GO     ?= $(shell which go)

# Below generated variables ensure that every time a tool under each variable is invoked, the correct version
# will be used; reinstalling only if needed.
# For example for golangci-lint variable:
#
# In your main Makefile (for non array binaries):
#
#include .bingo/Variables.mk # Assuming -dir was set to .bingo .
#
#command: $(GOLANGCI_LINT)
#	@echo "Running golangci-lint"
#	@$(GOLANGCI_LINT) <flags/args..>
#
GOLANGCI_LINT := $(GOBIN)/golangci-lint-v2.5.0
$(GOLANGCI_LINT): $(BINGO_DIR)/golangci-lint.mod
	@# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies.
	@echo "(re)installing $(GOBIN)/golangci-lint-v2.5.0"
	@cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=golangci-lint.mod -o=$(GOBIN)/golangci-lint-v2.5.0 "github.com/golangci/golangci-lint/v2/cmd/golangci-lint"

07070100000004000081A4000000000000000000000001691F8CFD00000094000000000000000000000000000000000000001F00000000kubelogin-0.2.13/.bingo/go.modmodule _ // Fake go.mod auto-created by 'bingo' for go -moddir compatibility with non-Go projects. Commit this file, together with other .mod files.07070100000005000081A4000000000000000000000001691F8CFD000000A2000000000000000000000000000000000000002A00000000kubelogin-0.2.13/.bingo/golangci-lint.modmodule _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT

go 1.24.9

require github.com/golangci/golangci-lint/v2 v2.5.0 // cmd/golangci-lint
07070100000006000081A4000000000000000000000001691F8CFD00016122000000000000000000000000000000000000002A00000000kubelogin-0.2.13/.bingo/golangci-lint.sum4d63.com/gocheckcompilerdirectives v1.3.0 h1:Ew5y5CtcAAQeTVKUVFrE7EwHMrTO6BggtEj8BZSjZ3A=
4d63.com/gocheckcompilerdirectives v1.3.0/go.mod h1:ofsJ4zx2QAuIP/NO/NAh1ig6R1Fb18/GI7RVMwz7kAY=
4d63.com/gochecknoglobals v0.2.2 h1:H1vdnwnMaZdQW/N+NrkT1SZMTBmcwHe9Vq8lJcYYTtU=
4d63.com/gochecknoglobals v0.2.2/go.mod h1:lLxwTQjL5eIesRbvnzIP3jZtG140FnTdz+AlMa+ogt0=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
codeberg.org/chavacava/garif v0.2.0 h1:F0tVjhYbuOCnvNcU3YSpO6b3Waw6Bimy4K0mM8y6MfY=
codeberg.org/chavacava/garif v0.2.0/go.mod h1:P2BPbVbT4QcvLZrORc2T29szK3xEOlnl0GiPTJmEqBQ=
dev.gaijin.team/go/exhaustruct/v4 v4.0.0 h1:873r7aNneqoBB3IaFIzhvt2RFYTuHgmMjoKfwODoI1Y=
dev.gaijin.team/go/exhaustruct/v4 v4.0.0/go.mod h1:aZ/k2o4Y05aMJtiux15x8iXaumE88YdiB0Ai4fXOzPI=
dev.gaijin.team/go/golib v0.6.0 h1:v6nnznFTs4bppib/NyU1PQxobwDHwCXXl15P7DV5Zgo=
dev.gaijin.team/go/golib v0.6.0/go.mod h1:uY1mShx8Z/aNHWDyAkZTkX+uCi5PdX7KsG1eDQa2AVE=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/4meepo/tagalign v1.4.3 h1:Bnu7jGWwbfpAie2vyl63Zup5KuRv21olsPIha53BJr8=
github.com/4meepo/tagalign v1.4.3/go.mod h1:00WwRjiuSbrRJnSVeGWPLp2epS5Q/l4UEy0apLLS37c=
github.com/Abirdcfly/dupword v0.1.6 h1:qeL6u0442RPRe3mcaLcbaCi2/Y/hOcdtw6DE9odjz9c=
github.com/Abirdcfly/dupword v0.1.6/go.mod h1:s+BFMuL/I4YSiFv29snqyjwzDp4b65W2Kvy+PKzZ6cw=
github.com/AdminBenni/iota-mixing v1.0.0 h1:Os6lpjG2dp/AE5fYBPAA1zfa2qMdCAWwPMCgpwKq7wo=
github.com/AdminBenni/iota-mixing v1.0.0/go.mod h1:i4+tpAaB+qMVIV9OK3m4/DAynOd5bQFaOu+2AhtBCNY=
github.com/AlwxSin/noinlineerr v1.0.5 h1:RUjt63wk1AYWTXtVXbSqemlbVTb23JOSRiNsshj7TbY=
github.com/AlwxSin/noinlineerr v1.0.5/go.mod h1:+QgkkoYrMH7RHvcdxdlI7vYYEdgeoFOVjU9sUhw/rQc=
github.com/Antonboom/errname v1.1.1 h1:bllB7mlIbTVzO9jmSWVWLjxTEbGBVQ1Ff/ClQgtPw9Q=
github.com/Antonboom/errname v1.1.1/go.mod h1:gjhe24xoxXp0ScLtHzjiXp0Exi1RFLKJb0bVBtWKCWQ=
github.com/Antonboom/nilnil v1.1.1 h1:9Mdr6BYd8WHCDngQnNVV0b554xyisFioEKi30sksufQ=
github.com/Antonboom/nilnil v1.1.1/go.mod h1:yCyAmSw3doopbOWhJlVci+HuyNRuHJKIv6V2oYQa8II=
github.com/Antonboom/testifylint v1.6.4 h1:gs9fUEy+egzxkEbq9P4cpcMB6/G0DYdMeiFS87UiqmQ=
github.com/Antonboom/testifylint v1.6.4/go.mod h1:YO33FROXX2OoUfwjz8g+gUxQXio5i9qpVy7nXGbxDD4=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Djarvur/go-err113 v0.1.1 h1:eHfopDqXRwAi+YmCUas75ZE0+hoBHJ2GQNLYRSxao4g=
github.com/Djarvur/go-err113 v0.1.1/go.mod h1:IaWJdYFLg76t2ihfflPZnM1LIQszWOsFDh2hhhAVF6k=
github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4=
github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/MirrexOne/unqueryvet v1.2.1 h1:M+zdXMq84g+E1YOLa7g7ExN3dWfZQrdDSTCM7gC+m/A=
github.com/MirrexOne/unqueryvet v1.2.1/go.mod h1:IWwCwMQlSWjAIteW0t+28Q5vouyktfujzYznSIWiuOg=
github.com/OpenPeeDeeP/depguard/v2 v2.2.1 h1:vckeWVESWp6Qog7UZSARNqfu/cZqvki8zsuj3piCMx4=
github.com/OpenPeeDeeP/depguard/v2 v2.2.1/go.mod h1:q4DKzC4UcVaAvcfd41CZh0PWpGgzrVxUYBlgKNGquUo=
github.com/alecthomas/chroma/v2 v2.20.0 h1:sfIHpxPyR07/Oylvmcai3X/exDlE8+FA820NTz+9sGw=
github.com/alecthomas/chroma/v2 v2.20.0/go.mod h1:e7tViK0xh/Nf4BYHl00ycY6rV7b8iXBksI9E359yNmA=
github.com/alecthomas/go-check-sumtype v0.3.1 h1:u9aUvbGINJxLVXiFvHUlPEaD7VDULsrxJb4Aq31NLkU=
github.com/alecthomas/go-check-sumtype v0.3.1/go.mod h1:A8TSiN3UPRw3laIgWEUOHHLPa6/r9MtoigdlP5h3K/E=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/alexkohler/nakedret/v2 v2.0.6 h1:ME3Qef1/KIKr3kWX3nti3hhgNxw6aqN5pZmQiFSsuzQ=
github.com/alexkohler/nakedret/v2 v2.0.6/go.mod h1:l3RKju/IzOMQHmsEvXwkqMDzHHvurNQfAgE1eVmT40Q=
github.com/alexkohler/prealloc v1.0.0 h1:Hbq0/3fJPQhNkN0dR95AVrr6R7tou91y0uHG5pOcUuw=
github.com/alexkohler/prealloc v1.0.0/go.mod h1:VetnK3dIgFBBKmg0YnD9F9x6Icjd+9cvfHR56wJVlKE=
github.com/alfatraining/structtag v1.0.0 h1:2qmcUqNcCoyVJ0up879K614L9PazjBSFruTB0GOFjCc=
github.com/alfatraining/structtag v1.0.0/go.mod h1:p3Xi5SwzTi+Ryj64DqjLWz7XurHxbGsq6y3ubePJPus=
github.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQRnw=
github.com/alingse/asasalint v0.0.11/go.mod h1:nCaoMhw7a9kSJObvQyVzNTPBDbNpdocqrSP7t/cW5+I=
github.com/alingse/nilnesserr v0.2.0 h1:raLem5KG7EFVb4UIDAXgrv3N2JIaffeKNtcEXkEWd/w=
github.com/alingse/nilnesserr v0.2.0/go.mod h1:1xJPrXonEtX7wyTq8Dytns5P2hNzoWymVUIaKm4HNFg=
github.com/ashanbrown/forbidigo/v2 v2.1.0 h1:NAxZrWqNUQiDz19FKScQ/xvwzmij6BiOw3S0+QUQ+Hs=
github.com/ashanbrown/forbidigo/v2 v2.1.0/go.mod h1:0zZfdNAuZIL7rSComLGthgc/9/n2FqspBOH90xlCHdA=
github.com/ashanbrown/makezero/v2 v2.0.1 h1:r8GtKetWOgoJ4sLyUx97UTwyt2dO7WkGFHizn/Lo8TY=
github.com/ashanbrown/makezero/v2 v2.0.1/go.mod h1:kKU4IMxmYW1M4fiEHMb2vc5SFoPzXvgbMR9gIp5pjSw=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bkielbasa/cyclop v1.2.3 h1:faIVMIGDIANuGPWH031CZJTi2ymOQBULs9H21HSMa5w=
github.com/bkielbasa/cyclop v1.2.3/go.mod h1:kHTwA9Q0uZqOADdupvcFJQtp/ksSnytRMe8ztxG8Fuo=
github.com/blizzy78/varnamelen v0.8.0 h1:oqSblyuQvFsW1hbBHh1zfwrKe3kcSj0rnXkKzsQ089M=
github.com/blizzy78/varnamelen v0.8.0/go.mod h1:V9TzQZ4fLJ1DSrjVDfl89H7aMnTvKkApdHeyESmyR7k=
github.com/bombsimon/wsl/v4 v4.7.0 h1:1Ilm9JBPRczjyUs6hvOPKvd7VL1Q++PL8M0SXBDf+jQ=
github.com/bombsimon/wsl/v4 v4.7.0/go.mod h1:uV/+6BkffuzSAVYD+yGyld1AChO7/EuLrCF/8xTiapg=
github.com/bombsimon/wsl/v5 v5.2.0 h1:PyCCwd3Q7abGs3e34IW4jLYlBS+FbsU6iK+Tb3NnDp4=
github.com/bombsimon/wsl/v5 v5.2.0/go.mod h1:Gp8lD04z27wm3FANIUPZycXp+8huVsn0oxc+n4qfV9I=
github.com/breml/bidichk v0.3.3 h1:WSM67ztRusf1sMoqH6/c4OBCUlRVTKq+CbSeo0R17sE=
github.com/breml/bidichk v0.3.3/go.mod h1:ISbsut8OnjB367j5NseXEGGgO/th206dVa427kR8YTE=
github.com/breml/errchkjson v0.4.1 h1:keFSS8D7A2T0haP9kzZTi7o26r7kE3vymjZNeNDRDwg=
github.com/breml/errchkjson v0.4.1/go.mod h1:a23OvR6Qvcl7DG/Z4o0el6BRAjKnaReoPQFciAl9U3s=
github.com/butuzov/ireturn v0.4.0 h1:+s76bF/PfeKEdbG8b54aCocxXmi0wvYdOVsWxVO7n8E=
github.com/butuzov/ireturn v0.4.0/go.mod h1:ghI0FrCmap8pDWZwfPisFD1vEc56VKH4NpQUxDHta70=
github.com/butuzov/mirror v1.3.0 h1:HdWCXzmwlQHdVhwvsfBb2Au0r3HyINry3bDWLYXiKoc=
github.com/butuzov/mirror v1.3.0/go.mod h1:AEij0Z8YMALaq4yQj9CPPVYOyJQyiexpQEQgihajRfI=
github.com/catenacyber/perfsprint v0.9.1 h1:5LlTp4RwTooQjJCvGEFV6XksZvWE7wCOUvjD2z0vls0=
github.com/catenacyber/perfsprint v0.9.1/go.mod h1:q//VWC2fWbcdSLEY1R3l8n0zQCDPdE4IjZwyY1HMunM=
github.com/ccojocar/zxcvbn-go v1.0.4 h1:FWnCIRMXPj43ukfX000kvBZvV6raSxakYr1nzyNrUcc=
github.com/ccojocar/zxcvbn-go v1.0.4/go.mod h1:3GxGX+rHmueTUMvm5ium7irpyjmm7ikxYFOSJB21Das=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/charithe/durationcheck v0.0.10 h1:wgw73BiocdBDQPik+zcEoBG/ob8uyBHf2iyoHGPf5w4=
github.com/charithe/durationcheck v0.0.10/go.mod h1:bCWXb7gYRysD1CU3C+u4ceO49LoGOY1C1L6uouGNreQ=
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE=
github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q=
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8=
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/ckaznocha/intrange v0.3.1 h1:j1onQyXvHUsPWujDH6WIjhyH26gkRt/txNlV7LspvJs=
github.com/ckaznocha/intrange v0.3.1/go.mod h1:QVepyz1AkUoFQkpEqksSYpNpUo3c5W7nWh/s6SHIJJk=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/curioswitch/go-reassign v0.3.0 h1:dh3kpQHuADL3cobV/sSGETA8DOv457dwl+fbBAhrQPs=
github.com/curioswitch/go-reassign v0.3.0/go.mod h1:nApPCCTtqLJN/s8HfItCcKV0jIPwluBOvZP+dsJGA88=
github.com/daixiang0/gci v0.13.7 h1:+0bG5eK9vlI08J+J/NWGbWPTNiXPG4WhNLJOkSxWITQ=
github.com/daixiang0/gci v0.13.7/go.mod h1:812WVN6JLFY9S6Tv76twqmNqevN0pa3SX3nih0brVzQ=
github.com/dave/dst v0.27.3 h1:P1HPoMza3cMEquVf9kKy8yXsFirry4zEnWOdYPOoIzY=
github.com/dave/dst v0.27.3/go.mod h1:jHh6EOibnHgcUW3WjKHisiooEkYwqpHLBSX1iOBhEyc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denis-tingaikin/go-header v0.5.0 h1:SRdnP5ZKvcO9KKRP1KJrhFR3RrlGuD+42t4429eC9k8=
github.com/denis-tingaikin/go-header v0.5.0/go.mod h1:mMenU5bWrok6Wl2UsZjy+1okegmwQ3UgWl4V1D8gjlY=
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/ettle/strcase v0.2.0 h1:fGNiVF21fHXpX1niBgk0aROov1LagYsOwV/xqKDKR/Q=
github.com/ettle/strcase v0.2.0/go.mod h1:DajmHElDSaX76ITe3/VHVyMin4LWSJN5Z909Wp+ED1A=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
github.com/firefart/nonamedreturns v1.0.6 h1:vmiBcKV/3EqKY3ZiPxCINmpS431OcE1S47AQUwhrg8E=
github.com/firefart/nonamedreturns v1.0.6/go.mod h1:R8NisJnSIpvPWheCq0mNRXJok6D8h7fagJTF8EMEwCo=
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo=
github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA=
github.com/ghostiam/protogetter v0.3.16 h1:UkrisuJBYLnZW6FcYUNBDJOqY3X22RtoYMlCsiNlFFA=
github.com/ghostiam/protogetter v0.3.16/go.mod h1:4SRRIv6PcjkIMpUkRUsP4TsUTqO/N3Fmvwivuc/sCHA=
github.com/go-critic/go-critic v0.13.0 h1:kJzM7wzltQasSUXtYyTl6UaPVySO6GkaR1thFnJ6afY=
github.com/go-critic/go-critic v0.13.0/go.mod h1:M/YeuJ3vOCQDnP2SU+ZhjgRzwzcBW87JqLpMJLrZDLI=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-toolsmith/astcast v1.1.0 h1:+JN9xZV1A+Re+95pgnMgDboWNVnIMMQXwfBwLRPgSC8=
github.com/go-toolsmith/astcast v1.1.0/go.mod h1:qdcuFWeGGS2xX5bLM/c3U9lewg7+Zu4mr+xPwZIB4ZU=
github.com/go-toolsmith/astcopy v1.1.0 h1:YGwBN0WM+ekI/6SS6+52zLDEf8Yvp3n2seZITCUBt5s=
github.com/go-toolsmith/astcopy v1.1.0/go.mod h1:hXM6gan18VA1T/daUEHCFcYiW8Ai1tIwIzHY6srfEAw=
github.com/go-toolsmith/astequal v1.0.3/go.mod h1:9Ai4UglvtR+4up+bAD4+hCj7iTo4m/OXVTSLnCyTAx4=
github.com/go-toolsmith/astequal v1.1.0/go.mod h1:sedf7VIdCL22LD8qIvv7Nn9MuWJruQA/ysswh64lffQ=
github.com/go-toolsmith/astequal v1.2.0 h1:3Fs3CYZ1k9Vo4FzFhwwewC3CHISHDnVUPC4x0bI2+Cw=
github.com/go-toolsmith/astequal v1.2.0/go.mod h1:c8NZ3+kSFtFY/8lPso4v8LuJjdJiUFVnSuU3s0qrrDY=
github.com/go-toolsmith/astfmt v1.1.0 h1:iJVPDPp6/7AaeLJEruMsBUlOYCmvg0MoCfJprsOmcco=
github.com/go-toolsmith/astfmt v1.1.0/go.mod h1:OrcLlRwu0CuiIBp/8b5PYF9ktGVZUjlNMV634mhwuQ4=
github.com/go-toolsmith/astp v1.1.0 h1:dXPuCl6u2llURjdPLLDxJeZInAeZ0/eZwFJmqZMnpQA=
github.com/go-toolsmith/astp v1.1.0/go.mod h1:0T1xFGz9hicKs8Z5MfAqSUitoUYS30pDMsRVIDHs8CA=
github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8=
github.com/go-toolsmith/strparse v1.1.0 h1:GAioeZUK9TGxnLS+qfdqNbA4z0SSm5zVNtCQiyP2Bvw=
github.com/go-toolsmith/strparse v1.1.0/go.mod h1:7ksGy58fsaQkGQlY8WVoBFNyEPMGuJin1rfoPS4lBSQ=
github.com/go-toolsmith/typep v1.1.0 h1:fIRYDyF+JywLfqzyhdiHzRop/GQDxxNhLGQ6gFUNHus=
github.com/go-toolsmith/typep v1.1.0/go.mod h1:fVIw+7zjdsMxDA3ITWnH1yOiw1rnTQKCsF/sk2H/qig=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/go-xmlfmt/xmlfmt v1.1.3 h1:t8Ey3Uy7jDSEisW2K3somuMKIpzktkWptA0iFCnRUWY=
github.com/go-xmlfmt/xmlfmt v1.1.3/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/godoc-lint/godoc-lint v0.10.0 h1:OcyrziBi18sQSEpib6NesVHEJ/Xcng97NunePBA48g4=
github.com/godoc-lint/godoc-lint v0.10.0/go.mod h1:KleLcHu/CGSvkjUH2RvZyoK1MBC7pDQg4NxMYLcBBsw=
github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E=
github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golangci/asciicheck v0.5.0 h1:jczN/BorERZwK8oiFBOGvlGPknhvq0bjnysTj4nUfo0=
github.com/golangci/asciicheck v0.5.0/go.mod h1:5RMNAInbNFw2krqN6ibBxN/zfRFa9S6tA1nPdM0l8qQ=
github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 h1:WUvBfQL6EW/40l6OmeSBYQJNSif4O11+bmWEz+C7FYw=
github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32/go.mod h1:NUw9Zr2Sy7+HxzdjIULge71wI6yEg1lWQr7Evcu8K0E=
github.com/golangci/go-printf-func-name v0.1.1 h1:hIYTFJqAGp1iwoIfsNTpoq1xZAarogrvjO9AfiW3B4U=
github.com/golangci/go-printf-func-name v0.1.1/go.mod h1:Es64MpWEZbh0UBtTAICOZiB+miW53w/K9Or/4QogJss=
github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d h1:viFft9sS/dxoYY0aiOTsLKO2aZQAPT4nlQCsimGcSGE=
github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d/go.mod h1:ivJ9QDg0XucIkmwhzCDsqcnxxlDStoTl89jDMIoNxKY=
github.com/golangci/golangci-lint/v2 v2.5.0 h1:BDRg4ASm4J1y/DSRY6zwJ5tr5Yy8ZqbZ79XrCeFxaQo=
github.com/golangci/golangci-lint/v2 v2.5.0/go.mod h1:IJtWJBZkLbx7AVrIUzLd8Oi3ADtwaNpWbR3wthVWHcc=
github.com/golangci/golines v0.0.0-20250217134842-442fd0091d95 h1:AkK+w9FZBXlU/xUmBtSJN1+tAI4FIvy5WtnUnY8e4p8=
github.com/golangci/golines v0.0.0-20250217134842-442fd0091d95/go.mod h1:k9mmcyWKSTMcPPvQUCfRWWQ9VHJ1U9Dc0R7kaXAgtnQ=
github.com/golangci/misspell v0.7.0 h1:4GOHr/T1lTW0hhR4tgaaV1WS/lJ+ncvYCoFKmqJsj0c=
github.com/golangci/misspell v0.7.0/go.mod h1:WZyyI2P3hxPY2UVHs3cS8YcllAeyfquQcKfdeE9AFVg=
github.com/golangci/nilerr v0.0.0-20250918000102-015671e622fe h1:F1pK9tBy41i7eesBFkSNMldwtiAaWiU+3fT/24sTnNI=
github.com/golangci/nilerr v0.0.0-20250918000102-015671e622fe/go.mod h1:CtTxAluxD2ng9aIT9bPrVoMuISFWCD+SaxtvYtdWA2k=
github.com/golangci/plugin-module-register v0.1.2 h1:e5WM6PO6NIAEcij3B053CohVp3HIYbzSuP53UAYgOpg=
github.com/golangci/plugin-module-register v0.1.2/go.mod h1:1+QGTsKBvAIvPvoY/os+G5eoqxWn70HYDm2uvUyGuVw=
github.com/golangci/revgrep v0.8.0 h1:EZBctwbVd0aMeRnNUsFogoyayvKHyxlV3CdUA46FX2s=
github.com/golangci/revgrep v0.8.0/go.mod h1:U4R/s9dlXZsg8uJmaR1GrloUr14D7qDl8gi2iPXJH8k=
github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e h1:ai0EfmVYE2bRA5htgAG9r7s3tHsfjIhN98WshBTJ9jM=
github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e/go.mod h1:Vrn4B5oR9qRwM+f54koyeH3yzphlecwERs0el27Fr/s=
github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e h1:gD6P7NEo7Eqtt0ssnqSJNNndxe69DOQ24A5h7+i3KpM=
github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e/go.mod h1:h+wZwLjUTJnm/P2rwlbJdRPZXOzaT36/FwnPnY2inzc=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gordonklaus/ineffassign v0.2.0 h1:Uths4KnmwxNJNzq87fwQQDDnbNb7De00VOk9Nu0TySs=
github.com/gordonklaus/ineffassign v0.2.0/go.mod h1:TIpymnagPSexySzs7F9FnO1XFTy8IT3a59vmZp5Y9Lw=
github.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk=
github.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/ojApNWb6C1//mXO48CXbVc=
github.com/gostaticanalysis/comment v1.4.2/go.mod h1:KLUTGDv6HOCotCH8h2erHKmpci2ZoR8VPu34YA2uzdM=
github.com/gostaticanalysis/comment v1.5.0 h1:X82FLl+TswsUMpMh17srGRuKaaXprTaytmEpgnKIDu8=
github.com/gostaticanalysis/comment v1.5.0/go.mod h1:V6eb3gpCv9GNVqb6amXzEUX3jXLVK/AdA+IrAMSqvEc=
github.com/gostaticanalysis/forcetypeassert v0.2.0 h1:uSnWrrUEYDr86OCxWa4/Tp2jeYDlogZiZHzGkWFefTk=
github.com/gostaticanalysis/forcetypeassert v0.2.0/go.mod h1:M5iPavzE9pPqWyeiVXSFghQjljW1+l/Uke3PXHS6ILY=
github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M=
github.com/hashicorp/go-immutable-radix/v2 v2.1.0 h1:CUW5RYIcysz+D3B+l1mDeXrQ7fUvGGCwJfdASSzbrfo=
github.com/hashicorp/go-immutable-radix/v2 v2.1.0/go.mod h1:hgdqLXA4f6NIjRVisM1TJ9aOJVNRqKZj+xDGF6m7PBw=
github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jgautheron/goconst v1.8.2 h1:y0XF7X8CikZ93fSNT6WBTb/NElBu9IjaY7CCYQrCMX4=
github.com/jgautheron/goconst v1.8.2/go.mod h1:A0oxgBCHy55NQn6sYpO7UdnA9p+h7cPtoOZUmvNIako=
github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjzq7gFzUs=
github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c=
github.com/jjti/go-spancheck v0.6.5 h1:lmi7pKxa37oKYIMScialXUK6hP3iY5F1gu+mLBPgYB8=
github.com/jjti/go-spancheck v0.6.5/go.mod h1:aEogkeatBrbYsyW6y5TgDfihCulDYciL1B7rG2vSsrU=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/julz/importas v0.2.0 h1:y+MJN/UdL63QbFJHws9BVC5RpA2iq0kpjrFajTGivjQ=
github.com/julz/importas v0.2.0/go.mod h1:pThlt589EnCYtMnmhmRYY/qn9lCf/frPOK+WMx3xiJY=
github.com/karamaru-alpha/copyloopvar v1.2.1 h1:wmZaZYIjnJ0b5UoKDjUHrikcV0zuPyyxI4SVplLd2CI=
github.com/karamaru-alpha/copyloopvar v1.2.1/go.mod h1:nFmMlFNlClC2BPvNaHMdkirmTJxVCY0lhxBtlfOypMM=
github.com/kisielk/errcheck v1.9.0 h1:9xt1zI9EBfcYBvdU1nVrzMzzUPUtPKs9bVSIM3TAb3M=
github.com/kisielk/errcheck v1.9.0/go.mod h1:kQxWMMVZgIkDq7U8xtG/n2juOjbLgZtedi0D+/VL/i8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kkHAIKE/contextcheck v1.1.6 h1:7HIyRcnyzxL9Lz06NGhiKvenXq7Zw6Q0UQu/ttjfJCE=
github.com/kkHAIKE/contextcheck v1.1.6/go.mod h1:3dDbMRNBFaq8HFXWC1JyvDSPm43CmE6IuHam8Wr0rkg=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kulti/thelper v0.7.1 h1:fI8QITAoFVLx+y+vSyuLBP+rcVIB8jKooNSCT2EiI98=
github.com/kulti/thelper v0.7.1/go.mod h1:NsMjfQEy6sd+9Kfw8kCP61W1I0nerGSYSFnGaxQkcbs=
github.com/kunwardeep/paralleltest v1.0.14 h1:wAkMoMeGX/kGfhQBPODT/BL8XhK23ol/nuQ3SwFaUw8=
github.com/kunwardeep/paralleltest v1.0.14/go.mod h1:di4moFqtfz3ToSKxhNjhOZL+696QtJGCFe132CbBLGk=
github.com/lasiar/canonicalheader v1.1.2 h1:vZ5uqwvDbyJCnMhmFYimgMZnJMjwljN5VGY0VKbMXb4=
github.com/lasiar/canonicalheader v1.1.2/go.mod h1:qJCeLFS0G/QlLQ506T+Fk/fWMa2VmBUiEI2cuMK4djI=
github.com/ldez/exptostd v0.4.4 h1:58AtQjnLcT/tI5W/1KU7xE/O7zW9RAWB6c/ScQAnfus=
github.com/ldez/exptostd v0.4.4/go.mod h1:QfdzPw6oHjFVdNV7ILoPu5sw3OZ3OG1JS0I5JN3J4Js=
github.com/ldez/gomoddirectives v0.7.0 h1:EOx8Dd56BZYSez11LVgdj025lKwlP0/E5OLSl9HDwsY=
github.com/ldez/gomoddirectives v0.7.0/go.mod h1:wR4v8MN9J8kcwvrkzrx6sC9xe9Cp68gWYCsda5xvyGc=
github.com/ldez/grignotin v0.10.1 h1:keYi9rYsgbvqAZGI1liek5c+jv9UUjbvdj3Tbn5fn4o=
github.com/ldez/grignotin v0.10.1/go.mod h1:UlDbXFCARrXbWGNGP3S5vsysNXAPhnSuBufpTEbwOas=
github.com/ldez/tagliatelle v0.7.2 h1:KuOlL70/fu9paxuxbeqlicJnCspCRjH0x8FW+NfgYUk=
github.com/ldez/tagliatelle v0.7.2/go.mod h1:PtGgm163ZplJfZMZ2sf5nhUT170rSuPgBimoyYtdaSI=
github.com/ldez/usetesting v0.5.0 h1:3/QtzZObBKLy1F4F8jLuKJiKBjjVFi1IavpoWbmqLwc=
github.com/ldez/usetesting v0.5.0/go.mod h1:Spnb4Qppf8JTuRgblLrEWb7IE6rDmUpGvxY3iRrzvDQ=
github.com/leonklingele/grouper v1.1.2 h1:o1ARBDLOmmasUaNDesWqWCIFH3u7hoFlM84YrjT3mIY=
github.com/leonklingele/grouper v1.1.2/go.mod h1:6D0M/HVkhs2yRKRFZUoGjeDy7EZTfFBE9gl4kjmIGkA=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/macabu/inamedparam v0.2.0 h1:VyPYpOc10nkhI2qeNUdh3Zket4fcZjEWe35poddBCpE=
github.com/macabu/inamedparam v0.2.0/go.mod h1:+Pee9/YfGe5LJ62pYXqB89lJ+0k5bsR8Wgz/C0Zlq3U=
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/manuelarte/embeddedstructfieldcheck v0.4.0 h1:3mAIyaGRtjK6EO9E73JlXLtiy7ha80b2ZVGyacxgfww=
github.com/manuelarte/embeddedstructfieldcheck v0.4.0/go.mod h1:z8dFSyXqp+fC6NLDSljRJeNQJJDWnY7RoWFzV3PC6UM=
github.com/manuelarte/funcorder v0.5.0 h1:llMuHXXbg7tD0i/LNw8vGnkDTHFpTnWqKPI85Rknc+8=
github.com/manuelarte/funcorder v0.5.0/go.mod h1:Yt3CiUQthSBMBxjShjdXMexmzpP8YGvGLjrxJNkO2hA=
github.com/maratori/testableexamples v1.0.0 h1:dU5alXRrD8WKSjOUnmJZuzdxWOEQ57+7s93SLMxb2vI=
github.com/maratori/testableexamples v1.0.0/go.mod h1:4rhjL1n20TUTT4vdh3RDqSizKLyXp7K2u6HgraZCGzE=
github.com/maratori/testpackage v1.1.1 h1:S58XVV5AD7HADMmD0fNnziNHqKvSdDuEKdPD1rNTU04=
github.com/maratori/testpackage v1.1.1/go.mod h1:s4gRK/ym6AMrqpOa/kEbQTV4Q4jb7WeLZzVhVVVOQMc=
github.com/matoous/godox v1.1.0 h1:W5mqwbyWrwZv6OQ5Z1a/DHGMOvXYCBP3+Ht7KMoJhq4=
github.com/matoous/godox v1.1.0/go.mod h1:jgE/3fUXiTurkdHOLT5WEkThTSuE7yxHv5iWPa80afs=
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mgechev/revive v1.12.0 h1:Q+/kkbbwerrVYPv9d9efaPGmAO/NsxwW/nE6ahpQaCU=
github.com/mgechev/revive v1.12.0/go.mod h1:VXsY2LsTigk8XU9BpZauVLjVrhICMOV3k1lpB3CXrp8=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/moricho/tparallel v0.3.2 h1:odr8aZVFA3NZrNybggMkYO3rgPRcqjeQUlBBFVxKHTI=
github.com/moricho/tparallel v0.3.2/go.mod h1:OQ+K3b4Ln3l2TZveGCywybl68glfLEwFGqvnjok8b+U=
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81U=
github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE=
github.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhKRf3Swg=
github.com/nishanths/exhaustive v0.12.0/go.mod h1:mEZ95wPIZW+x8kC4TgC+9YCUgiST7ecevsVDTgc2obs=
github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk=
github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c=
github.com/nunnatsa/ginkgolinter v0.21.0 h1:IYwuX+ajy3G1MezlMLB1BENRtFj16+Evyi4uki1NOOQ=
github.com/nunnatsa/ginkgolinter v0.21.0/go.mod h1:QlzY9UP9zaqu58FjYxhp9bnjuwXwG1bfW5rid9ChNMw=
github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw=
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/polyfloyd/go-errorlint v1.8.0 h1:DL4RestQqRLr8U4LygLw8g2DX6RN1eBJOpa2mzsrl1Q=
github.com/polyfloyd/go-errorlint v1.8.0/go.mod h1:G2W0Q5roxbLCt0ZQbdoxQxXktTjwNyDbEaj3n7jvl4s=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk=
github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4=
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/quasilyte/go-ruleguard v0.4.4 h1:53DncefIeLX3qEpjzlS1lyUmQoUEeOWPFWqaTJq9eAQ=
github.com/quasilyte/go-ruleguard v0.4.4/go.mod h1:Vl05zJ538vcEEwu16V/Hdu7IYZWyKSwIy4c88Ro1kRE=
github.com/quasilyte/go-ruleguard/dsl v0.3.22 h1:wd8zkOhSNr+I+8Qeciml08ivDt1pSXe60+5DqOpCjPE=
github.com/quasilyte/go-ruleguard/dsl v0.3.22/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU=
github.com/quasilyte/gogrep v0.5.0 h1:eTKODPXbI8ffJMN+W2aE0+oL0z/nh8/5eNdiO34SOAo=
github.com/quasilyte/gogrep v0.5.0/go.mod h1:Cm9lpz9NZjEoL1tgZ2OgeUKPIxL1meE7eo60Z6Sk+Ng=
github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 h1:TCg2WBOl980XxGFEZSS6KlBGIV0diGdySzxATTWoqaU=
github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0=
github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 h1:M8mH9eK4OUR4lu7Gd+PU1fV2/qnDNfzT635KRSObncs=
github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ=
github.com/raeperd/recvcheck v0.2.0 h1:GnU+NsbiCqdC2XX5+vMZzP+jAJC5fht7rcVTAhX74UI=
github.com/raeperd/recvcheck v0.2.0/go.mod h1:n04eYkwIR0JbgD73wT8wL4JjPC3wm0nFtzBnWNocnYU=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryancurrah/gomodguard v1.4.1 h1:eWC8eUMNZ/wM/PWuZBv7JxxqT5fiIKSIyTvjb7Elr+g=
github.com/ryancurrah/gomodguard v1.4.1/go.mod h1:qnMJwV1hX9m+YJseXEBhd2s90+1Xn6x9dLz11ualI1I=
github.com/ryanrolds/sqlclosecheck v0.5.1 h1:dibWW826u0P8jNLsLN+En7+RqWWTYrjCB9fJfSfdyCU=
github.com/ryanrolds/sqlclosecheck v0.5.1/go.mod h1:2g3dUjoS6AL4huFdv6wn55WpLIDjY7ZgUR4J8HOO/XQ=
github.com/sanposhiho/wastedassign/v2 v2.1.0 h1:crurBF7fJKIORrV85u9UUpePDYGWnwvv3+A96WvwXT0=
github.com/sanposhiho/wastedassign/v2 v2.1.0/go.mod h1:+oSmSC+9bQ+VUAxA66nBb0Z7N8CK7mscKTDYC6aIek4=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tMEOsumirXcOJqAw=
github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ=
github.com/sashamelentyev/usestdlibvars v1.29.0 h1:8J0MoRrw4/NAXtjQqTHrbW9NN+3iMf7Knkq057v4XOQ=
github.com/sashamelentyev/usestdlibvars v1.29.0/go.mod h1:8PpnjHMk5VdeWlVb4wCdrB8PNbLqZ3wBZTZWkrpZZL8=
github.com/securego/gosec/v2 v2.22.8 h1:3NMpmfXO8wAVFZPNsd3EscOTa32Jyo6FLLlW53bexMI=
github.com/securego/gosec/v2 v2.22.8/go.mod h1:ZAw8K2ikuH9qDlfdV87JmNghnVfKB1XC7+TVzk6Utto=
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+Wwfd0XE=
github.com/sivchari/containedctx v1.0.3/go.mod h1:c1RDvCbnJLtH4lLcYD/GqwiBSSf4F5Qk0xld2rBqzJ4=
github.com/sonatard/noctx v0.4.0 h1:7MC/5Gg4SQ4lhLYR6mvOP6mQVSxCrdyiExo7atBs27o=
github.com/sonatard/noctx v0.4.0/go.mod h1:64XdbzFb18XL4LporKXp8poqZtPKbCrqQ402CV+kJas=
github.com/sourcegraph/go-diff v0.7.0 h1:9uLlrd5T46OXs5qpp8L/MTltk0zikUGi0sNNyCpA8G0=
github.com/sourcegraph/go-diff v0.7.0/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs=
github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA=
github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo=
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ=
github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI=
github.com/ssgreg/nlreturn/v2 v2.2.1 h1:X4XDI7jstt3ySqGU86YGAURbxw3oTDPK9sPEi6YEwQ0=
github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I=
github.com/stbenjam/no-sprintf-host-port v0.2.0 h1:i8pxvGrt1+4G0czLr/WnmyH7zbZ8Bg8etvARQ1rpyl4=
github.com/stbenjam/no-sprintf-host-port v0.2.0/go.mod h1:eL0bQ9PasS0hsyTyfTjjG+E80QIyPnBVQbYZyv20Jfk=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs=
github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0=
github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY=
github.com/tetafro/godot v1.5.4 h1:u1ww+gqpRLiIA16yF2PV1CV1n/X3zhyezbNXC3E14Sg=
github.com/tetafro/godot v1.5.4/go.mod h1:eOkMrVQurDui411nBY2FA05EYH01r14LuWY/NrVDVcU=
github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67 h1:9LPGD+jzxMlnk5r6+hJnar67cgpDIz/iyD+rfl5r2Vk=
github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67/go.mod h1:mkjARE7Yr8qU23YcGMSALbIxTQ9r9QBVahQOBRfU460=
github.com/timonwong/loggercheck v0.11.0 h1:jdaMpYBl+Uq9mWPXv1r8jc5fC3gyXx4/WGwTnnNKn4M=
github.com/timonwong/loggercheck v0.11.0/go.mod h1:HEAWU8djynujaAVX7QI65Myb8qgfcZ1uKbdpg3ZzKl8=
github.com/tomarrell/wrapcheck/v2 v2.11.0 h1:BJSt36snX9+4WTIXeJ7nvHBQBcm1h2SjQMSlmQ6aFSU=
github.com/tomarrell/wrapcheck/v2 v2.11.0/go.mod h1:wFL9pDWDAbXhhPZZt+nG8Fu+h29TtnZ2MW6Lx4BRXIU=
github.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+yU8u1Zw=
github.com/tommy-muehle/go-mnd/v2 v2.5.1/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw=
github.com/ultraware/funlen v0.2.0 h1:gCHmCn+d2/1SemTdYMiKLAHFYxTYz7z9VIDRaTGyLkI=
github.com/ultraware/funlen v0.2.0/go.mod h1:ZE0q4TsJ8T1SQcjmkhN/w+MceuatI6pBFSxxyteHIJA=
github.com/ultraware/whitespace v0.2.0 h1:TYowo2m9Nfj1baEQBjuHzvMRbp19i+RCcRYrSWoFa+g=
github.com/ultraware/whitespace v0.2.0/go.mod h1:XcP1RLD81eV4BW8UhQlpaR+SDc2givTvyI8a586WjW8=
github.com/uudashr/gocognit v1.2.0 h1:3BU9aMr1xbhPlvJLSydKwdLN3tEUUrzPSSM8S4hDYRA=
github.com/uudashr/gocognit v1.2.0/go.mod h1:k/DdKPI6XBZO1q7HgoV2juESI2/Ofj9AcHPZhBBdrTU=
github.com/uudashr/iface v1.4.1 h1:J16Xl1wyNX9ofhpHmQ9h9gk5rnv2A6lX/2+APLTo0zU=
github.com/uudashr/iface v1.4.1/go.mod h1:pbeBPlbuU2qkNDn0mmfrxP2X+wjPMIQAy+r1MBXSXtg=
github.com/xen0n/gosmopolitan v1.3.0 h1:zAZI1zefvo7gcpbCOrPSHJZJYA9ZgLfJqtKzZ5pHqQM=
github.com/xen0n/gosmopolitan v1.3.0/go.mod h1:rckfr5T6o4lBtM1ga7mLGKZmLxswUoH1zxHgNXOsEt4=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM=
github.com/yagipy/maintidx v1.0.0/go.mod h1:0qNf/I/CCZXSMhsRsrEPDZ+DkekpKLXAJfsTACwgXLk=
github.com/yeya24/promlinter v0.3.0 h1:JVDbMp08lVCP7Y6NP3qHroGAO6z2yGKQtS5JsjqtoFs=
github.com/yeya24/promlinter v0.3.0/go.mod h1:cDfJQQYv9uYciW60QT0eeHlFodotkYZlL+YcPQN+mW4=
github.com/ykadowak/zerologlint v0.1.5 h1:Gy/fMz1dFQN9JZTPjv1hxEk+sRWm05row04Yoolgdiw=
github.com/ykadowak/zerologlint v0.1.5/go.mod h1:KaUskqF3e/v59oPmdq1U1DnKcuHokl2/K1U4pmIELKg=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
gitlab.com/bosi/decorder v0.4.2 h1:qbQaV3zgwnBZ4zPMhGLW4KZe7A7NwxEhJx39R3shffo=
gitlab.com/bosi/decorder v0.4.2/go.mod h1:muuhHoaJkA9QLcYHq4Mj8FJUwDZ+EirSHRiaTcTf6T8=
go-simpler.org/musttag v0.14.0 h1:XGySZATqQYSEV3/YTy+iX+aofbZZllJaqwFWs+RTtSo=
go-simpler.org/musttag v0.14.0/go.mod h1:uP8EymctQjJ4Z1kUnjX0u2l60WfUdQxCwSNKzE1JEOE=
go-simpler.org/sloglint v0.11.1 h1:xRbPepLT/MHPTCA6TS/wNfZrDzkGvCCqUv4Bdwc3H7s=
go-simpler.org/sloglint v0.11.1/go.mod h1:2PowwiCOK8mjiF+0KGifVOT8ZsCNiFzvfyJeJOIt8MQ=
go.augendre.info/arangolint v0.2.0 h1:2NP/XudpPmfBhQKX4rMk+zDYIj//qbt4hfZmSSTcpj8=
go.augendre.info/arangolint v0.2.0/go.mod h1:Vx4KSJwu48tkE+8uxuf0cbBnAPgnt8O1KWiT7bljq7w=
go.augendre.info/fatcontext v0.8.1 h1:/T4+cCjpL9g71gJpcFAgVo/K5VFpqlN+NPU7QXxD5+A=
go.augendre.info/fatcontext v0.8.1/go.mod h1:r3Qz4ZOzex66wfyyj5VZ1xUcl81vzvHQ6/GWzzlMEwA=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
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.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk=
golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/exp/typeparams v0.0.0-20250911091902-df9299821621 h1:Yl4H5w2RV7L/dvSHp2GerziT5K2CORgFINPaMFxWGWw=
golang.org/x/exp/typeparams v0.0.0-20250911091902-df9299821621/go.mod h1:4Mzdyp/6jzw9auFDJ3OMF5qksa7UvPnzKqTVGcb04ms=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
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=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/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.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/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-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
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.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200329025819-fd4102a86c65/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU=
golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
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=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.6.1 h1:R094WgE8K4JirYjBaOpz/AvTyUu/3wbmAoskKN/pxTI=
honnef.co/go/tools v0.6.1/go.mod h1:3puzxxljPCe8RGJX7BIy1plGbxEOZni5mR2aXe3/uk4=
mvdan.cc/gofumpt v0.9.1 h1:p5YT2NfFWsYyTieYgwcQ8aKV3xRvFH4uuN/zB2gBbMQ=
mvdan.cc/gofumpt v0.9.1/go.mod h1:3xYtNemnKiXaTh6R4VtlqDATFwBbdXI8lJvH/4qk7mw=
mvdan.cc/unparam v0.0.0-20250301125049-0df0534333a4 h1:WjUu4yQoT5BHT1w8Zu56SP8367OuBV5jvo+4Ulppyf8=
mvdan.cc/unparam v0.0.0-20250301125049-0df0534333a4/go.mod h1:rthT7OuvRbaGcd5ginj6dA2oLE7YNlta9qhBNNdCaLE=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
07070100000007000081A4000000000000000000000001691F8CFD000001A1000000000000000000000000000000000000002600000000kubelogin-0.2.13/.bingo/variables.env# Auto generated binary variables helper managed by https://github.com/bwplotka/bingo v0.9. DO NOT EDIT.
# All tools are designed to be build inside $GOBIN.
# Those variables will work only until 'bingo get' was invoked, or if tools were installed via Makefile's Variables.mk.
GOBIN=${GOBIN:=$(go env GOBIN)}

if [ -z "$GOBIN" ]; then
	GOBIN="$(go env GOPATH)/bin"
fi


GOLANGCI_LINT="${GOBIN}/golangci-lint-v2.5.0"

07070100000008000081A4000000000000000000000001691F8CFD00000252000000000000000000000000000000000000001F00000000kubelogin-0.2.13/.dockerignore# Docker ignore file for kubelogin
# Ignore development and build files that are not needed in Docker context

# Version control
.git
.gitignore

# Build artifacts (except the final binary)
.bingo/
hack/

# Documentation
docs/
README.md
CHANGELOG.md
CODE_OF_CONDUCT.md
SECURITY.md

# Test files
*_test.go
**/*_test.go
**/testdata/
**/*VCR.yaml

# Development files
.github/dependabot.yml
.github/workflows/golangci-lint.yml
.github/workflows/website.yaml
.github/workflows/dependency-review.yml
.pre-commit-config.yaml

# IDE files
.vscode/
.idea/
*.swp
*.swo
*~

# OS files
.DS_Store
Thumbs.db07070100000009000041ED000000000000000000000002691F8CFD00000000000000000000000000000000000000000000001900000000kubelogin-0.2.13/.github0707010000000A000081A4000000000000000000000001691F8CFD00000243000000000000000000000000000000000000002800000000kubelogin-0.2.13/.github/dependabot.yml# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates

version: 2
updates:
- package-ecosystem: "gomod" # See documentation for possible values
  directory: "/" # Location of package manifests
  schedule:
    interval: "daily"

- package-ecosystem: github-actions
  directory: /
  schedule:
    interval: daily
0707010000000B000041ED000000000000000000000002691F8CFD00000000000000000000000000000000000000000000002300000000kubelogin-0.2.13/.github/workflows0707010000000C000081A4000000000000000000000001691F8CFD00000A55000000000000000000000000000000000000002D00000000kubelogin-0.2.13/.github/workflows/build.ymlname: Build on Push
on:
  push:
    paths-ignore:
      - docs/**
      - README.md
  pull_request:
    branches:
      - main
    paths-ignore:
      - docs/**
      - README.md

permissions:
  contents: read

jobs:
  test:
    name: Test
    runs-on: ubuntu-latest
    permissions:
      actions: read
      contents: read
      deployments: read
      packages: none
    env:
      GO111MODULE: on
    steps:
      - name: Check out code
        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1

      - name: Set up Go
        uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
        with:
          go-version-file: "go.mod"
          cache: false

      - name: Run tests
        run: make test

      - name: Upload coverage reports to Codecov
        uses: codecov/codecov-action@1e68e06f1dbfde0e4cefc87efeba9e4643565303 # v5.1.2

  build-linux-and-windows:
    name: Build Linux and Windows
    runs-on: ubuntu-latest
    needs: test
    steps:
      - name: Check out code
        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1

      - name: Set up Go
        uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
        with:
          go-version-file: "go.mod"
          cache: false

      - name: Build Linux AMD64
        run: make
        env:
          GOOS: linux
          GOARCH: amd64
          CGO_ENABLED: 0

      - name: Build Linux ARM64
        run: make
        env:
          GOOS: linux
          GOARCH: arm64
          CGO_ENABLED: 0

      - name: Build Linux ARMv7
        run: make
        env:
          GOOS: linux
          GOARCH: arm
          GOARM: "7"
          CGO_ENABLED: 0

      - name: Build Windows AMD64
        run: make
        env:
          GOOS: windows
          GOARCH: amd64
          CGO_ENABLED: 0

      - name: Build Windows ARM64
        run: make
        env:
          GOOS: windows
          GOARCH: arm64
          CGO_ENABLED: 0

  build-macos:
    name: Build macOS
    runs-on: macos-latest
    needs: test
    steps:
      - name: Check out code
        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1

      - name: Set up Go
        uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
        with:
          go-version-file: "go.mod"
          cache: false

      - name: Build macOS AMD64
        run: make
        env:
          GOOS: darwin
          GOARCH: amd64
          CGO_ENABLED: 1

      - name: Build macOS ARM64
        run: make
        env:
          GOOS: darwin
          GOARCH: arm64
          CGO_ENABLED: 1
0707010000000D000081A4000000000000000000000001691F8CFD00000323000000000000000000000000000000000000003900000000kubelogin-0.2.13/.github/workflows/dependency-review.yml# Dependency Review Action
#
# This Action will scan dependency manifest files that change as part of a Pull Request,
# surfacing known-vulnerable versions of the packages declared or updated in the PR.
# Once installed, if the workflow run is marked as required,
# PRs introducing known-vulnerable packages will be blocked from merging.
#
# Source repository: https://github.com/actions/dependency-review-action
name: 'Dependency Review'
on: [pull_request]

permissions:
  contents: read

jobs:
  dependency-review:
    runs-on: ubuntu-latest
    steps:
      - name: 'Checkout Repository'
        uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
      - name: 'Dependency Review'
        uses: actions/dependency-review-action@0efb1d1d84fc9633afcdaad14c485cbbc90ef46c # v2.5.1
0707010000000E000081A4000000000000000000000001691F8CFD00000BF3000000000000000000000000000000000000003600000000kubelogin-0.2.13/.github/workflows/docker-publish.ymlname: Docker Build and Publish

on:
  release:
    types: [published]
  workflow_dispatch:
    inputs:
      tag:
        description: 'Docker image tag'
        required: false
        default: 'latest'

permissions:
  contents: read
  packages: write

env:
  REGISTRY: ghcr.io

jobs:
  docker:
    name: Build and Publish Docker Image
    runs-on: ubuntu-latest
    steps:
      - name: Set IMAGE_NAME to lowercase
        run: echo "IMAGE_NAME=$(echo '${{ github.repository }}' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV

      - name: Check out code
        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1

      - name: Set up Go
        uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
        with:
          go-version-file: "go.mod"
          cache: false

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0

      - name: Log in to GitHub Container Registry
        uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract release tag
        id: extract_tag
        run: |
          if [ "${{ github.event_name }}" = "release" ]; then
            echo "tag=${{ github.event.release.tag_name }}" >> $GITHUB_OUTPUT
          else
            echo "tag=${{ github.event.inputs.tag }}" >> $GITHUB_OUTPUT
          fi

      - name: Build kubelogin binaries for multi-arch
        run: |
          # Build for amd64
          GOOS=linux GOARCH=amd64 CGO_ENABLED=0 GIT_TAG=${{ steps.extract_tag.outputs.tag }} make kubelogin
          
          # Build for arm64  
          GOOS=linux GOARCH=arm64 CGO_ENABLED=0 GIT_TAG=${{ steps.extract_tag.outputs.tag }} make kubelogin
          
          # Verify binaries were created
          ls -la bin/linux_amd64/kubelogin
          ls -la bin/linux_arm64/kubelogin
          
          # Test the binaries
          file bin/linux_amd64/kubelogin
          file bin/linux_arm64/kubelogin

      - name: Build and push Docker image
        uses: docker/build-push-action@4a13e500e55cf31b7a5d59a38ab2040ab0f42f56 # v5.1.0
        with:
          context: .
          platforms: linux/amd64,linux/arm64
          push: true
          build-args: |
            VERSION=${{ steps.extract_tag.outputs.tag }}
          tags: |
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.extract_tag.outputs.tag }}
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
          file: Dockerfile

      - name: Generate Docker image summary
        run: |
          echo "## Docker Image Published" >> $GITHUB_STEP_SUMMARY
          echo "- **Registry**: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}" >> $GITHUB_STEP_SUMMARY
          echo "- **Tag**: ${{ steps.extract_tag.outputs.tag }}" >> $GITHUB_STEP_SUMMARY
          echo "- **Platforms**: linux/amd64, linux/arm64" >> $GITHUB_STEP_SUMMARY0707010000000F000081A4000000000000000000000001691F8CFD0000038D000000000000000000000000000000000000003500000000kubelogin-0.2.13/.github/workflows/golangci-lint.ymlname: golangci-lint
on:
  push:
    tags:
      - v*
    branches:
      - main
    paths-ignore:
      - docs/**
      - README.md
  pull_request:
    paths-ignore:
      - docs/**
      - README.md
permissions:
  contents: read
  # Optional: allow read access to pull request. Use with `only-new-issues` option.
  # pull-requests: read
jobs:
  golangci:
    name: lint
    runs-on: ubuntu-latest
    permissions:
      actions: read
      contents: read
      deployments: read
      packages: none
    steps:
      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
      - uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
        with:
          go-version-file: "go.mod"
          cache: false
      - name: golangci-lint
        uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0
        with:
          version: v2.5.0
07070100000010000081A4000000000000000000000001691F8CFD00000DAB000000000000000000000000000000000000003700000000kubelogin-0.2.13/.github/workflows/publish-winget.yamlname: Publish Azure kubelogin to winget

on:
  # Manual trigger so the workflow can be run on demand when the installer is available
  workflow_dispatch:
    inputs:
      version:
        description: 'Version of kubelogin to publish (e.g., 0.2.12)'
        required: true

permissions: {}

env:
  # winget-create will read the following environment variable to access the GitHub token needed for submitting a PR
  # See https://aka.ms/winget-create-token
  WINGET_CREATE_GITHUB_TOKEN: ${{ secrets.WINGET_TOKEN }}
  WINGET_PACKAGE_ID: Microsoft.Azure.Kubelogin

jobs:
  publish-winget:
    runs-on: windows-latest
    steps:
      - name: Normalize and set version
        id: normalize_version
        shell: pwsh
        run: |
          $version = '${{ github.event.inputs.version }}'.TrimStart('v')
          "version=$version" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
          Write-Host "Normalized version: $version" -ForegroundColor Cyan

      - name: Set installer URL
        id: set_url
        shell: pwsh
        run: |
          $version = '${{ steps.normalize_version.outputs.version }}'
          $url = "https://packages.aks.azure.com/dalec-packages/kubelogin/$version/windows/amd64/kubelogin_${version}-1_amd64.zip"
          "url=$url" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
          Write-Host "Installer URL: $url" -ForegroundColor Cyan

      - name: Validate installer availability
        shell: pwsh
        run: |
          $installerUrl = '${{ steps.set_url.outputs.url }}'
          Write-Host "Checking installer availability at $installerUrl" -ForegroundColor Cyan
          try {
            $response = Invoke-WebRequest -Uri $installerUrl -Method Head -UseBasicParsing -ErrorAction Stop
            if ($response.StatusCode -ne 200) {
              Write-Error "Installer not found. Received HTTP status code $($response.StatusCode)."
              exit 1
            } else {
              Write-Host "Installer found (HTTP $($response.StatusCode)). Proceeding..." -ForegroundColor Green
            }
          } catch {
            Write-Error "Failed to access the installer URL: $($_.Exception.Message)"
            exit 1
          }

      - name: Download wingetcreate
        shell: pwsh
        run: |
          Write-Host "Downloading wingetcreate..." -ForegroundColor Cyan
          & curl.exe -JLO https://aka.ms/wingetcreate/latest
          if (-not (Test-Path .\wingetcreate.exe)) {
            Write-Error "Failed to download wingetcreate.exe"
            exit 1
          }
          Write-Host "wingetcreate downloaded successfully" -ForegroundColor Green

      - name: Publish to winget
        shell: pwsh
        run: |
          # Use the constructed installer URL and the normalized version
          $installerUrl = '${{ steps.set_url.outputs.url }}'
          $version = '${{ steps.normalize_version.outputs.version }}'
          $packageId = '${{ env.WINGET_PACKAGE_ID }}'
          
          Write-Host "Publishing $packageId version $version to winget..." -ForegroundColor Cyan
          
          & .\wingetcreate.exe update $packageId `
            --urls $installerUrl `
            --version $version `
            --submit
          
          if ($LASTEXITCODE -ne 0) {
            Write-Error "wingetcreate failed with exit code $LASTEXITCODE"
            exit $LASTEXITCODE
          }
          
          Write-Host "Successfully submitted to winget!" -ForegroundColor Green07070100000011000081A4000000000000000000000001691F8CFD00002337000000000000000000000000000000000000002F00000000kubelogin-0.2.13/.github/workflows/release.ymlname: Release
on:
  workflow_dispatch:

permissions:
  contents: read

jobs:
  create-release:
    name: Create Release
    runs-on: ubuntu-latest
    permissions:
      actions: read
      contents: write
      deployments: read
      packages: none
    outputs:
      release_id: ${{ steps.create_release.outputs.id }}
      tag_version: "v${{ steps.changelog_reader.outputs.version }}"
    steps:
      - name: Check out code into the Go module directory
        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
        
      # Read changelog and read versions etc.
      - name: Check version is mentioned in Changelog.md
        id: changelog_reader
        uses: mindsers/changelog-reader-action@b97ce03a10d9bdbb07beb491c76a5a01d78cd3ef # v2.2.2
        with:
          validation_depth: 10
          path: "CHANGELOG.md"

      # Check if the newest tag already exists
      - name: Check if tag exist
        uses: mukunku/tag-exists-action@bdad1eaa119ce71b150b952c97351c75025c06a9 # v1.6.0
        id: check-tag-exists
        with:
          tag: "v${{ steps.changelog_reader.outputs.version }} release"
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      # Create Draft Release
      - name: Create Kubelogin Draft Release
        id: create_release
        if: ${{ steps.check-tag-exists.outputs.exists == 'false'}}
        uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          tag_name: "v${{ steps.changelog_reader.outputs.version }}"
          name: "v${{ steps.changelog_reader.outputs.version }} release"
          body: ${{ steps.changelog_reader.outputs.changes }}
          draft: true

  build-linux-and-windows:
    name: Build Linux and Windows
    runs-on: ubuntu-latest
    needs: create-release
    if: ${{ needs.create-release.outputs.release_id != '' }}
    steps:
      - name: Check out code
        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1

      - name: Get tags
        run: git fetch --tags

      - name: Set up Go
        uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
        with:
          go-version-file: "go.mod"
          cache: false

      - name: Build Linux AMD64
        run: make
        env:
          GOOS: linux
          GOARCH: amd64
          CGO_ENABLED: 0
          GIT_TAG: "${{ needs.create-release.outputs.tag_version }}"

      - name: Build Linux ARM64
        run: make
        env:
          GOOS: linux
          GOARCH: arm64
          CGO_ENABLED: 0
          GIT_TAG: "${{ needs.create-release.outputs.tag_version }}"

      - name: Build Linux ARMv7
        run: make
        env:
          GOOS: linux
          GOARCH: arm
          GOARM: "7"
          CGO_ENABLED: 0
          GIT_TAG: "${{ needs.create-release.outputs.tag_version }}"
      
      - name: Build Windows AMD64
        run: make
        env:
          GOOS: windows
          GOARCH: amd64
          CGO_ENABLED: 0
          GIT_TAG: "${{ needs.create-release.outputs.tag_version }}"
    
      - name: Build Windows ARM64
        run: make
        env:
          GOOS: windows
          GOARCH: arm64
          CGO_ENABLED: 0
          GIT_TAG: "${{ needs.create-release.outputs.tag_version }}"

      - name: tarball Linux binaries
        run: |
          tar -czf linux-kubelogin.tar.gz bin/linux_*
      
      - name: tarball windows binaries
        run: |
          tar -czf windows-kubelogin.tar.gz bin/windows_*

      - name: Upload Linux artifacts
        uses: actions/upload-artifact@v4
        with:
          name: linux-binaries
          path: linux-kubelogin.tar.gz
      
      - name: Upload Windows artifacts
        uses: actions/upload-artifact@v4
        with:
          name: windows-binaries
          path: windows-kubelogin.tar.gz

  build-macos:
    name: Build macOS
    runs-on: macos-latest
    needs: create-release
    if: ${{ needs.create-release.outputs.release_id != '' }}
    steps:
      - name: Check out code
        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1

      - name: Get tags
        run: git fetch --tags

      - name: Set up Go
        uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
        with:
          go-version-file: "go.mod"
          cache: false

      - name: Build macOS AMD64
        run: make
        env:
          GOOS: darwin
          GOARCH: amd64
          CGO_ENABLED: 1
          GIT_TAG: "${{ needs.create-release.outputs.tag_version }}"

      - name: Build macOS ARM64
        run: make
        env:
          GOOS: darwin
          GOARCH: arm64
          CGO_ENABLED: 1
          GIT_TAG: "${{ needs.create-release.outputs.tag_version }}"

      - name: tarball macos binaries
        run: |
          tar -czf macos-kubelogin.tar.gz bin/darwin_*

      - name: Upload macOS artifacts
        uses: actions/upload-artifact@v4
        with:
          name: macos-binaries
          path: macos-kubelogin.tar.gz

  package-and-publish:
    name: Package and Publish
    runs-on: ubuntu-latest
    permissions:
      contents: write
    needs: [create-release, build-linux-and-windows, build-macos]
    if: ${{ needs.create-release.outputs.release_id != '' }}
    steps:
      - name: Download all artifacts
        uses: actions/download-artifact@v4
        with:
          path: bin

      - name: untar binaries
        run: |
          tar -xzf bin/linux-binaries/linux-kubelogin.tar.gz
          tar -xzf bin/macos-binaries/macos-kubelogin.tar.gz
          tar -xzf bin/windows-binaries/windows-kubelogin.tar.gz

      - name: Move binaries to correct locations
        run: |
          mkdir -p bin/linux_amd64 bin/linux_arm64 bin/linux_armv7 \
                 bin/darwin_amd64 bin/darwin_arm64 \
                 bin/windows_amd64 bin/windows_arm64
          rm -rf bin/linux-binaries bin/macos-binaries bin/windows-binaries

      - name: Zip
        uses: montudor/action-zip@a8e75c9faefcd80fac3baf53ef40b9b119d5b702 # v1
        with:
          args: zip -qq -r kubelogin.zip bin

      - name: Zip (win-amd64)
        uses: montudor/action-zip@a8e75c9faefcd80fac3baf53ef40b9b119d5b702 # v1
        with:
          args: zip -qq kubelogin-win-amd64.zip bin/windows_amd64/kubelogin.exe

      - name: Zip (win-arm64)
        uses: montudor/action-zip@a8e75c9faefcd80fac3baf53ef40b9b119d5b702 # v1
        with:
          args: zip -qq kubelogin-win-arm64.zip bin/windows_arm64/kubelogin.exe

      - name: Zip (darwin-amd64)
        uses: montudor/action-zip@a8e75c9faefcd80fac3baf53ef40b9b119d5b702 # v1
        with:
          args: zip -qq kubelogin-darwin-amd64.zip bin/darwin_amd64/kubelogin

      - name: Zip (darwin-arm64)
        uses: montudor/action-zip@a8e75c9faefcd80fac3baf53ef40b9b119d5b702 # v1
        with:
          args: zip -qq kubelogin-darwin-arm64.zip bin/darwin_arm64/kubelogin

      - name: Zip (linux-amd64)
        uses: montudor/action-zip@a8e75c9faefcd80fac3baf53ef40b9b119d5b702 # v1
        with:
          args: zip -qq kubelogin-linux-amd64.zip bin/linux_amd64/kubelogin

      - name: Zip (linux-arm64)
        uses: montudor/action-zip@a8e75c9faefcd80fac3baf53ef40b9b119d5b702 # v1
        with:
          args: zip -qq kubelogin-linux-arm64.zip bin/linux_arm64/kubelogin

      - name: Zip (linux-armv7)
        uses: montudor/action-zip@a8e75c9faefcd80fac3baf53ef40b9b119d5b702 # v1
        with:
          args: zip -qq kubelogin-linux-armv7.zip bin/linux_armv7/kubelogin

      - name: Create sha256 Checksums
        run: |
          sha256sum kubelogin.zip > kubelogin.zip.sha256
          sha256sum kubelogin-win-amd64.zip > kubelogin-win-amd64.zip.sha256
          sha256sum kubelogin-win-arm64.zip > kubelogin-win-arm64.zip.sha256
          sha256sum kubelogin-darwin-amd64.zip > kubelogin-darwin-amd64.zip.sha256
          sha256sum kubelogin-darwin-arm64.zip > kubelogin-darwin-arm64.zip.sha256
          sha256sum kubelogin-linux-amd64.zip > kubelogin-linux-amd64.zip.sha256
          sha256sum kubelogin-linux-arm64.zip > kubelogin-linux-arm64.zip.sha256
          sha256sum kubelogin-linux-armv7.zip > kubelogin-linux-armv7.zip.sha256

      - 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
          releaseId: ${{ needs.create-release.outputs.release_id }}
07070100000012000081A4000000000000000000000001691F8CFD000003BF000000000000000000000000000000000000003000000000kubelogin-0.2.13/.github/workflows/website.yamlname: generate github pages

on:
  push:
    branches:
      - main
    paths:
      - ".github/workflows/website.yaml"
      - "docs/**"

jobs:
  deploy:
    permissions:
      contents: write
      pages: write
      
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
        with:
          submodules: true
          fetch-depth: 0

      - name: Set TOOLS_BIN_DIR and add to PATH
        run: |
          TOOLS_BIN_DIR="${HOME}/.cargo/bin"
          echo "TOOLS_BIN_DIR=${TOOLS_BIN_DIR}" >> ${GITHUB_ENV}
          echo "${TOOLS_BIN_DIR}" >> ${GITHUB_PATH}

      - name: Build
        run: make -C docs/book build

      - name: Deploy
        uses: peaceiris/actions-gh-pages@373f7f263a76c20808c831209c920827a82a2847 # v3.9.3
        if: ${{ github.ref == 'refs/heads/main' }}
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./docs/book/book
07070100000013000081A4000000000000000000000001691F8CFD0000014D000000000000000000000000000000000000001C00000000kubelogin-0.2.13/.gitignore# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
*.cov
bin
kubelogin

# Test binary, built with `go test -c`
*.test

# Output of the go coverage tool, specifically when used with LiteIDE
*.out

# Dependency directories (remove the comment below to include it)
# vendor/

coverage.txt

# JetBrains IDE folder
.idea
07070100000014000081A4000000000000000000000001691F8CFD00000323000000000000000000000000000000000000001F00000000kubelogin-0.2.13/.golangci.ymlversion: "2"
run:
  concurrency: 4
  tests: false
linters:
  default: none
  enable:
    - errcheck
    - goconst
    - gocritic
    - gosec
    - govet
    - ineffassign
    - misspell
    - paralleltest
    - staticcheck
    - unused
  settings:
    gocritic:
      disabled-checks:
        - ifElseChain
    gosec:
      excludes:
        - G101
    misspell:
      locale: US
  exclusions:
    generated: lax
    presets:
      - comments
      - common-false-positives
      - legacy
      - std-error-handling
    paths:
      - third_party$
      - builtin$
      - examples$
formatters:
  enable:
    - goimports
  settings:
    goimports:
      local-prefixes:
        - github.com/org/project
  exclusions:
    generated: lax
    paths:
      - third_party$
      - builtin$
      - examples$
07070100000015000081A4000000000000000000000001691F8CFD000001A3000000000000000000000000000000000000002900000000kubelogin-0.2.13/.pre-commit-config.yamlrepos:
- repo: https://github.com/gitleaks/gitleaks
  rev: v8.16.3
  hooks:
  - id: gitleaks
- repo: https://github.com/golangci/golangci-lint
  rev: v1.52.2
  hooks:
  - id: golangci-lint
- repo: https://github.com/jumanjihouse/pre-commit-hooks
  rev: 3.0.0
  hooks:
  - id: shellcheck
- repo: https://github.com/pre-commit/pre-commit-hooks
  rev: v4.4.0
  hooks:
  - id: end-of-file-fixer
  - id: trailing-whitespace
07070100000016000081A4000000000000000000000001691F8CFD0000000D000000000000000000000000000000000000002000000000kubelogin-0.2.13/.tool-versionsgolang 1.24.907070100000017000081A4000000000000000000000001691F8CFD000061EC000000000000000000000000000000000000001E00000000kubelogin-0.2.13/CHANGELOG.md# Change Log

## [0.2.13]

### What's Changed

* Mention PEM support for client certificates by @ijrsvt in https://github.com/Azure/kubelogin/pull/717
* added winget publish actions by @weinong in https://github.com/Azure/kubelogin/pull/718
* Fix PoP token keychain storage conflict by @fangluguomsft in https://github.com/Azure/kubelogin/pull/723
* docs(installation): add alternative mise installation method by @jylenhof in https://github.com/Azure/kubelogin/pull/724

### Maintenance

* Bump Go to 1.24.9 to address stdlib CVEs by @Copilot in https://github.com/Azure/kubelogin/pull/726
* Bump golang.org/x/crypto from 0.40.0 to 0.45.0 by @dependabot[bot] in https://github.com/Azure/kubelogin/pull/727

### New Contributors

* @ijrsvt made their first contribution in https://github.com/Azure/kubelogin/pull/717
* @fangluguomsft made their first contribution in https://github.com/Azure/kubelogin/pull/723
* @jylenhof made their first contribution in https://github.com/Azure/kubelogin/pull/724

**Full Changelog**: https://github.com/Azure/kubelogin/compare/v0.2.12...v0.2.13

## [0.2.12]

### What's Changed

* Support Azure Pipelines Environment Variables for AzurePipelinesCredential by @Copilot in https://github.com/Azure/kubelogin/pull/713
* [PoP token Cache] Fallback to no caching when encrypted cache creation fails  by @vineeth-thumma in https://github.com/Azure/kubelogin/pull/715

**Full Changelog**: https://github.com/Azure/kubelogin/compare/v0.2.11...v0.2.12

## [0.2.11]

### What's Changed

* Add OpenContainers Image Spec labels to Docker image by @Copilot in https://github.com/Azure/kubelogin/pull/693
* Add AzurePipelinesCredential to authentication options by @Copilot in https://github.com/Azure/kubelogin/pull/703
* Add encrypted caching support for PoP token flow by @vineeth-thumma in https://github.com/Azure/kubelogin/pull/676

### Maintenance

* update install doc by @weinong in https://github.com/Azure/kubelogin/pull/688
* Update asdf installation instructions by @o-farooq in https://github.com/Azure/kubelogin/pull/690
* docs: clarify sp login-mode precedence by @duduz in https://github.com/Azure/kubelogin/pull/692
* bumped go version by @weinong in https://github.com/Azure/kubelogin/pull/709

### New Contributors

* @o-farooq made their first contribution in https://github.com/Azure/kubelogin/pull/690
* @duduz made their first contribution in https://github.com/Azure/kubelogin/pull/692
* @vineeth-thumma made their first contribution in https://github.com/Azure/kubelogin/pull/676

**Full Changelog**: https://github.com/Azure/kubelogin/compare/v0.2.10...v0.2.11

## [0.2.10]

### What's Changed

* Add Docker Image Build and Publish Workflow by @Copilot in https://github.com/Azure/kubelogin/pull/680
* Fix Docker registry name case issue in GitHub Actions workflow by @Copilot in https://github.com/Azure/kubelogin/pull/682

### Maintenance

* Fix CVE-2025-22871 and CVE-2025-22868: Update Go version and security dependencies by @Copilot in https://github.com/Azure/kubelogin/pull/678

### New Contributors

* @Copilot made their first contribution in https://github.com/Azure/kubelogin/pull/678

**Full Changelog**: https://github.com/Azure/kubelogin/compare/v0.2.9...v0.2.10

## [0.2.9]

### What's Changed

* added redirect-url to interactive mode by @weinong in https://github.com/Azure/kubelogin/pull/661
* added Interactive login hint by @weinong in https://github.com/Azure/kubelogin/pull/663
* Add caching support for PoP token acquisition by @JorgeDaboub in https://github.com/Azure/kubelogin/pull/662
* feat: update documentation by @jakangah in https://github.com/Azure/kubelogin/pull/669

### New Contributors

* @jakangah made their first contribution in https://github.com/Azure/kubelogin/pull/669

**Full Changelog**: https://github.com/Azure/kubelogin/compare/v0.2.8...v0.2.9

## [0.2.8]

### Maintenance

* Bump github.com/golang-jwt/jwt/v4 from 4.5.1 to 4.5.2 by @dependabot in https://github.com/Azure/kubelogin/pull/648
* Bump golang.org/x/net from 0.36.0 to 0.38.0 by @dependabot in https://github.com/Azure/kubelogin/pull/651

**Full Changelog**: https://github.com/Azure/kubelogin/compare/v0.2.7...v0.2.8

## [0.2.7]

### What's Changed

* fix missing prompt and increase default timeout to 60s by @weinong in https://github.com/Azure/kubelogin/pull/643
* ignore cache create failure by @weinong in https://github.com/Azure/kubelogin/pull/644
* fix missing build tag in --version by @weinong in https://github.com/Azure/kubelogin/pull/645

**Full Changelog**: https://github.com/Azure/kubelogin/compare/v0.2.6...v0.2.7

## [0.2.6]

### What's Changed

* fixed the unneeded tenant id validation in azurecli login by @weinong in https://github.com/Azure/kubelogin/pull/637
* remove tenant id validation for azuredevops login by @weinong in https://github.com/Azure/kubelogin/pull/638

**Full Changelog**: https://github.com/Azure/kubelogin/compare/v0.2.5...v0.2.6

## [0.2.5]

### What's Changed

* fixed a bug where server-id flag is always required by @weinong in https://github.com/Azure/kubelogin/pull/634

**Full Changelog**: https://github.com/Azure/kubelogin/compare/v0.2.4...v0.2.5

## [0.2.4]

### What's Changed

* disable cgo for linux and windows by @weinong in https://github.com/Azure/kubelogin/pull/631

**Full Changelog**: https://github.com/Azure/kubelogin/compare/v0.2.3...v0.2.4

## [0.2.3]

### What's Changed

* use tar to retain executable bit in released assets by @weinong in https://github.com/Azure/kubelogin/pull/628

**Full Changelog**: https://github.com/Azure/kubelogin/compare/v0.2.2...v0.2.3

## [0.2.2]

### What's Changed

* fixed broken release assets by @weinong in https://github.com/Azure/kubelogin/pull/625

**Full Changelog**: https://github.com/Azure/kubelogin/compare/v0.2.1...v0.2.2

## [0.2.1]

### What's Changed

* updated artifact actions by @weinong in https://github.com/Azure/kubelogin/pull/622

**Full Changelog**: https://github.com/Azure/kubelogin/compare/v0.2.0...v0.2.1

## [0.2.0]

### What's Changed

* rewrote token implementation and added official cache support by @weinong in https://github.com/Azure/kubelogin/pull/608
  **This change includes breaking change so that the minor version is bumped**:
  - Previous caching implementation is removed. Now we are using caching provided by azidentity. This also means any credential flows not implemented by azidentity will not have any caching. Notably, interactive with pop, device code with legacy and ropc with pop will NOT have cache.
  - The binary is now built with CGO enabled to allow secure token caching on the host

### Maintenance

* Bump golang.org/x/net from 0.33.0 to 0.36.0 by @dependabot in https://github.com/Azure/kubelogin/pull/618
* added missing checkout to fix release by @weinong in https://github.com/Azure/kubelogin/pull/620

**Full Changelog**: https://github.com/Azure/kubelogin/compare/v0.1.9...v0.2.0

## [0.1.9]

### What's Changed

* Add disable-instance-discovery option in interactive pop mode by @Aijing2333 in https://github.com/Azure/kubelogin/pull/593

### Maintenance

* Bump codecov/codecov-action from 3.1.5 to 5.1.2 by @dependabot in https://github.com/Azure/kubelogin/pull/583
* Bump mukunku/tag-exists-action from 1.1.0 to 1.6.0 by @dependabot in https://github.com/Azure/kubelogin/pull/405
* Bump go.uber.org/mock from 0.4.0 to 0.5.0 by @dependabot in https://github.com/Azure/kubelogin/pull/545
* chore: bump go to 1.23.7 by @bcho in https://github.com/Azure/kubelogin/pull/611

### New Contributors
* @Aijing2333 made their first contribution in https://github.com/Azure/kubelogin/pull/593

**Full Changelog**: https://github.com/Azure/kubelogin/compare/v0.1.7...v0.1.9

## [0.1.7]

### What's Changed

* Improve shell completion for convert-config by @albers in https://github.com/Azure/kubelogin/pull/582
* Shell completion enhancements by @albers in https://github.com/Azure/kubelogin/pull/586
* Adding an option to disable instance discovery in AcquirePoPTokenConfidential by @bganapa in https://github.com/Azure/kubelogin/pull/595
* Add disable environment override option. by @dpersson in https://github.com/Azure/kubelogin/pull/594

### Maintenance

* chore: bump golang.org/x/net to v0.33.0 to mitigate CVE-2024-45338 by @bcho in https://github.com/Azure/kubelogin/pull/584
* address codeql issues by @weinong in https://github.com/Azure/kubelogin/pull/588
* Update website.yaml by @weinong in https://github.com/Azure/kubelogin/pull/589
* Fix install link for golangci-lint by @albers in https://github.com/Azure/kubelogin/pull/585
* use bingo to manage golangci-lint by @weinong in https://github.com/Azure/kubelogin/pull/590
* default codeql does not allow uploading 3rd party scanning result by @weinong in https://github.com/Azure/kubelogin/pull/591
* fixed the default target in makefile by @weinong in https://github.com/Azure/kubelogin/pull/601

### New Contributors

* @albers made their first contribution in https://github.com/Azure/kubelogin/pull/582
* @bganapa made their first contribution in https://github.com/Azure/kubelogin/pull/595
* @dpersson made their first contribution in https://github.com/Azure/kubelogin/pull/594

**Full Changelog**: https://github.com/Azure/kubelogin/compare/v0.1.6...v0.1.7

## [0.1.6]

### Enhancements

* remove snap since it's unsupported by @weinong in https://github.com/Azure/kubelogin/pull/564
* Add x5c Header when Acquiring PoP Tokens by @JorgeDaboub in https://github.com/Azure/kubelogin/pull/568

### Maintenance

* Bump golang.org/x/crypto from 0.27.0 to 0.31.0 by @dependabot in https://github.com/Azure/kubelogin/pull/576

**Full Changelog**: https://github.com/Azure/kubelogin/compare/v0.1.5...v0.1.6

## [0.1.5]

### Enhancements

* support of environment variable KUBECACHEDIR #500 by @jjournet in https://github.com/Azure/kubelogin/pull/501
* Use AZURE_CONFIG_DIR in kubelogin command example by @tspearconquest in https://github.com/Azure/kubelogin/pull/522
* fix: fix fallback to Git tag if VCS is unavailable by @maxbrunet in https://github.com/Azure/kubelogin/pull/530
* Expose MSAL PoP for Consistent CSP Integration by @JorgeDaboub in https://github.com/Azure/kubelogin/pull/542

### Maintenance

* Bump ossf/scorecard-action from 2.0.6 to 2.4.0 by @dependabot in https://github.com/Azure/kubelogin/pull/498
* Bump golang.org/x/crypto from 0.24.0 to 0.25.0 by @dependabot in https://github.com/Azure/kubelogin/pull/490
* Bump golang.org/x/crypto from 0.25.0 to 0.26.0 by @dependabot in https://github.com/Azure/kubelogin/pull/505
* Bump github.com/golang-jwt/jwt/v4 from 4.5.0 to 4.5.1 by @dependabot in https://github.com/Azure/kubelogin/pull/543
* Bump github.com/Azure/azure-sdk-for-go/sdk/azidentity from 1.6.0 to 1.8.0 by @dependabot in https://github.com/Azure/kubelogin/pull/534
* Preemptive fix for the breaking GH Action. by @Tatsinnit in https://github.com/Azure/kubelogin/pull/546

### New Contributors

* @jjournet made their first contribution in https://github.com/Azure/kubelogin/pull/501
* @tspearconquest made their first contribution in https://github.com/Azure/kubelogin/pull/522
* @maxbrunet made their first contribution in https://github.com/Azure/kubelogin/pull/530
* @JorgeDaboub made their first contribution in https://github.com/Azure/kubelogin/pull/542

**Full Changelog**: https://github.com/Azure/kubelogin/compare/v0.1.4...v0.1.5

## [0.1.4]

### Maintenance

* Bump github.com/Azure/azure-sdk-for-go/sdk/azidentity from 1.5.1 to 1.6.0 by @dependabot in https://github.com/Azure/kubelogin/pull/474
* feat: declare go version directive with patch version by @bcho in https://github.com/Azure/kubelogin/pull/476
* Bump github.com/Azure/azure-sdk-for-go/sdk/azcore from 1.11.1 to 1.12.0 by @dependabot in https://github.com/Azure/kubelogin/pull/478
* chore: upgrade go to v1.21.11 to fix CVE-2024-24790 by @strivedi-px in https://github.com/Azure/kubelogin/pull/485
* Bump k8s.io/klog/v2 from 2.110.1 to 2.130.1 by @dependabot in https://github.com/Azure/kubelogin/pull/483
* Bump github.com/spf13/cobra from 1.8.0 to 1.8.1 by @dependabot in https://github.com/Azure/kubelogin/pull/482
* Bump github.com/stretchr/testify from 1.8.4 to 1.9.0 by @dependabot in https://github.com/Azure/kubelogin/pull/444
* Bump gopkg.in/dnaeon/go-vcr.v3 from 3.1.2 to 3.2.0 by @dependabot in https://github.com/Azure/kubelogin/pull/459

### New Contributors

* @strivedi-px made their first contribution in https://github.com/Azure/kubelogin/pull/485

**Full Changelog**: https://github.com/Azure/kubelogin/compare/v0.1.3...v0.1.4

## [0.1.3]

- Bump golang.org/x/net from 0.21.0 to 0.23.0 by @dependabot in https://github.com/Azure/kubelogin/pull/451

**Full Changelog**: https://github.com/Azure/kubelogin/compare/v0.1.2...v0.1.3

## [0.1.2]

### Maintenance

- Bump google.golang.org/protobuf from 1.30.0 to 1.33.0 by @dependabot in https://github.com/Azure/kubelogin/pull/430
- Bump k8s.io/cli-runtime from 0.28.3 to 0.29.3 by @dependabot in https://github.com/Azure/kubelogin/pull/433
- fix: tidy go.mod and bump go version by @bcho in https://github.com/Azure/kubelogin/pull/448
- Bump golang.org/x/crypto from 0.18.0 to 0.22.0 by @dependabot in https://github.com/Azure/kubelogin/pull/445
- Bump github.com/google/uuid from 1.5.0 to 1.6.0 by @dependabot in https://github.com/Azure/kubelogin/pull/406
- Bump github.com/golang-jwt/jwt/v5 from 5.2.0 to 5.2.1 by @dependabot in https://github.com/Azure/kubelogin/pull/443

**Full Changelog**: https://github.com/Azure/kubelogin/compare/v0.1.1...v0.1.2

## [0.1.1]

### Enhancements

- Adds Azure Developer CLI (azd) as a new login method by @wbreza in https://github.com/Azure/kubelogin/pull/398
- Add PoP token support for ROPC flow by @rharpavat in https://github.com/Azure/kubelogin/pull/412

### Maintenance

- Default branch is now main. by @Tatsinnit in https://github.com/Azure/kubelogin/pull/390
- Changes in correlation with new GH Action Permission Changes. by @Tatsinnit in https://github.com/Azure/kubelogin/pull/400
- Bump github.com/AzureAD/microsoft-authentication-library-for-go from 1.2.0 to 1.2.1 by @dependabot in https://github.com/Azure/kubelogin/pull/391
- Bump golang.org/x/crypto from 0.17.0 to 0.18.0 by @dependabot in https://github.com/Azure/kubelogin/pull/392
- [StepSecurity] Apply security best practices by @step-security-bot in https://github.com/Azure/kubelogin/pull/404

### New Contributors

- @wbreza made their first contribution in https://github.com/Azure/kubelogin/pull/398
- @step-security-bot made their first contribution in https://github.com/Azure/kubelogin/pull/404

**Full Changelog**: https://github.com/Azure/kubelogin/compare/v0.1.0...v0.1.1

## [0.1.0]

### Enhancements

- [library usage] Move modules under `pkg` to `pkg/internal` by @bcho in https://github.com/Azure/kubelogin/pull/376
- [library usage] Update module version usages by @bcho in https://github.com/Azure/kubelogin/pull/377
- [library usage] Refine internal token types by @bcho in https://github.com/Azure/kubelogin/pull/379
- [library usage] Implement library token provider by @bcho in https://github.com/Azure/kubelogin/pull/380
- [library usage] fix: downgrade required go version to 1.20 by @bcho in https://github.com/Azure/kubelogin/pull/386

### Maintenance

- Bump github.com/spf13/cobra from 1.7.0 to 1.8.0 by @dependabot in https://github.com/Azure/kubelogin/pull/359
- Bump golang.org/x/crypto from 0.14.0 to 0.17.0 by @dependabot in https://github.com/Azure/kubelogin/pull/378
- Bump github.com/golang-jwt/jwt/v5 from 5.0.0 to 5.2.0 by @dependabot in https://github.com/Azure/kubelogin/pull/370
- Bump github.com/Azure/azure-sdk-for-go/sdk/azcore from 1.8.0 to 1.9.1 by @dependabot in https://github.com/Azure/kubelogin/pull/372
- Bump go.uber.org/mock from 0.3.0 to 0.4.0 by @dependabot in https://github.com/Azure/kubelogin/pull/385
- Bump github.com/google/uuid from 1.4.0 to 1.5.0 by @dependabot in https://github.com/Azure/kubelogin/pull/383

**Full Changelog**: https://github.com/Azure/kubelogin/compare/v0.0.34...v0.1.0

## [0.0.34]

### Enhancements

* feat(timeout): Implement customizable timeout for Azure CLI token ret… by @Aricg in https://github.com/Azure/kubelogin/pull/362
* added github token support by @weinong in https://github.com/Azure/kubelogin/pull/366
* added armv7 support by @weinong in https://github.com/Azure/kubelogin/pull/367

### Maintenance

* bump golang to 1.21 by @weinong in https://github.com/Azure/kubelogin/pull/356
* Bump k8s.io/klog/v2 from 2.100.1 to 2.110.1 by @dependabot in https://github.com/Azure/kubelogin/pull/357
* Bump github.com/google/uuid from 1.3.1 to 1.4.0 by @dependabot in https://github.com/Azure/kubelogin/pull/355

## New Contributors
* @Aricg made their first contribution in https://github.com/Azure/kubelogin/pull/362

**Full Changelog**: https://github.com/Azure/kubelogin/compare/v0.0.33...v0.0.34

## [0.0.33]

### Enhancements

- use the adal library for spn when --legacy is specified by @weinong in https://github.com/Azure/kubelogin/pull/338

### Maintenance

- Bump github.com/google/uuid from 1.3.0 to 1.3.1 by @dependabot in https://github.com/Azure/kubelogin/pull/334
- Add 1P client/server app IDs to docs by @rharpavat in https://github.com/Azure/kubelogin/pull/336
- Update install.md by @torreymicrosoft in https://github.com/Azure/kubelogin/pull/342
- Bump golang.org/x/net from 0.10.0 to 0.17.0 by @dependabot in https://github.com/Azure/kubelogin/pull/347
- Bump github.com/Azure/azure-sdk-for-go/sdk/azcore from 1.6.1 to 1.8.0 by @dependabot in https://github.com/Azure/kubelogin/pull/344
- Bump github.com/Azure/azure-sdk-for-go/sdk/azidentity from 1.3.0 to 1.4.0 by @dependabot in https://github.com/Azure/kubelogin/pull/346
- Bump k8s.io/cli-runtime from 0.27.2 to 0.28.2 by @dependabot in https://github.com/Azure/kubelogin/pull/340
- Bump k8s.io/cli-runtime from 0.28.2 to 0.28.3 by @dependabot in https://github.com/Azure/kubelogin/pull/351
- Bump github.com/google/go-cmp from 0.5.9 to 0.6.0 by @dependabot in https://github.com/Azure/kubelogin/pull/349
- Bump github.com/stretchr/testify from 1.8.2 to 1.8.4 by @dependabot in https://github.com/Azure/kubelogin/pull/348

## New Contributors

- @torreymicrosoft made their first contribution in https://github.com/Azure/kubelogin/pull/342

**Full Changelog**: https://github.com/Azure/kubelogin/compare/v0.0.32...v0.0.33

## [0.0.32]

### Enhancements

- Add PoP token support to interactive+spn get-token/convert-kubeconfig flows by @rharpavat in https://github.com/Azure/kubelogin/pull/319

### Maintenance

- Fixed typo in top header for convert-kubeconfig documentation by @byk0t in https://github.com/Azure/kubelogin/pull/323
- Bump golang.org/x/crypto from 0.11.0 to 0.12.0 by @dependabot in https://github.com/Azure/kubelogin/pull/315
- Bump k8s.io/apimachinery from 0.27.3 to 0.27.4 by @dependabot in https://github.com/Azure/kubelogin/pull/310

## New Contributors

- @byk0t made their first contribution in https://github.com/Azure/kubelogin/pull/323
- @rharpavat made their first contribution in https://github.com/Azure/kubelogin/pull/319

**Full Changelog**: https://github.com/Azure/kubelogin/compare/v0.0.31...v0.0.32

## [0.0.31]

### Enhancements

- upgrade klog from v1 to v2 by @peterbom in https://github.com/Azure/kubelogin/pull/306

### Maintenance

- Bump k8s.io/apimachinery from 0.27.2 to 0.27.3 by @dependabot in https://github.com/Azure/kubelogin/pull/297
- Bump golang.org/x/crypto from 0.10.0 to 0.11.0 by @dependabot in https://github.com/Azure/kubelogin/pull/303
- Bump github.com/Azure/azure-sdk-for-go/sdk/azcore from 1.6.0 to 1.6.1 by @dependabot in https://github.com/Azure/kubelogin/pull/292
- Bump golang.org/x/crypto from 0.9.0 to 0.10.0 by @dependabot in https://github.com/Azure/kubelogin/pull/294

### Doc Update

- docs: Use asdf-plugins index instead of hard coded repo https://github.com/Azure/kubelogin/pull/298
- Add chocolatey installation instructions https://github.com/Azure/kubelogin/pull/299

### New Contributors

- @peterbom made their first contribution in https://github.com/Azure/kubelogin/pull/306
- @sechmann made their first contribution in https://github.com/Azure/kubelogin/pull/298
- @moredatapls made their first contribution in https://github.com/Azure/kubelogin/pull/299

**Full Changelog**: https://github.com/Azure/kubelogin/compare/v0.0.30...v0.0.31

## [0.0.30]

### Enhancements

- added verbose logging in convert-kubeconfig by @weinong in https://github.com/Azure/kubelogin/pull/272
- Adding installHint field to kubeconfigs that have been converted to the exec format by @cirvine-MSFT in https://github.com/Azure/kubelogin/pull/282

### Maintenance

- Bump github.com/Azure/azure-sdk-for-go/sdk/azcore from 1.1.1 to 1.5.0 by @dependabot in https://github.com/Azure/kubelogin/pull/249
- Bump github.com/AzureAD/microsoft-authentication-library-for-go from 0.9.0 to 1.0.0 by @dependabot in https://github.com/Azure/kubelogin/pull/259
- Bump k8s.io/cli-runtime from 0.26.3 to 0.27.1 by @dependabot in https://github.com/Azure/kubelogin/pull/262
- Bump github.com/Azure/go-autorest/autorest from 0.11.28 to 0.11.29 by @dependabot in https://github.com/Azure/kubelogin/pull/273
- add unit tests for `manualtoken_test.go` by @khareyash05 in https://github.com/Azure/kubelogin/pull/268
- Bump github.com/Azure/azure-sdk-for-go/sdk/azcore from 1.5.0 to 1.6.0 by @dependabot in https://github.com/Azure/kubelogin/pull/274
- Bump golang.org/x/crypto from 0.8.0 to 0.9.0 by @dependabot in https://github.com/Azure/kubelogin/pull/277
- Bump github.com/Azure/azure-sdk-for-go/sdk/azidentity from 1.2.2 to 1.3.0 by @dependabot in https://github.com/Azure/kubelogin/pull/278
- Bump k8s.io/apimachinery from 0.27.1 to 0.27.2 by @dependabot in https://github.com/Azure/kubelogin/pull/283
- Bump k8s.io/cli-runtime from 0.27.1 to 0.27.2 by @dependabot in https://github.com/Azure/kubelogin/pull/285
- Azidentity migration for service principal token by @ekoehn in https://github.com/Azure/kubelogin/pull/287
- update go to address CVE by @weinong in https://github.com/Azure/kubelogin/pull/290

### Doc Update

- update doc for v0.0.29 by @weinong in https://github.com/Azure/kubelogin/pull/270

### New Contributors

- @khareyash05 made their first contribution in https://github.com/Azure/kubelogin/pull/268
- @ekoehn made their first contribution in https://github.com/Azure/kubelogin/pull/287

**Full Changelog**: https://github.com/Azure/kubelogin/compare/v0.0.29...v0.0.30

## [0.0.29]

### Enhancements

- add --context support in convert subcommand by @weinong in https://github.com/Azure/kubelogin/pull/260
- return error when specified context is not found by @weinong in https://github.com/Azure/kubelogin/pull/261
- add --azure-config-dir in convert-kubeconfig subcommand by @weinong in https://github.com/Azure/kubelogin/pull/263

### Maintenance

- Enable Code Cov for this repo. by @Tatsinnit in https://github.com/Azure/kubelogin/pull/229
- Bump golang.org/x/crypto from 0.6.0 to 0.7.0 by @dependabot in https://github.com/Azure/kubelogin/pull/230
- Bump k8s.io/client-go from 0.26.2 to 0.26.3 by @dependabot in https://github.com/Azure/kubelogin/pull/234
- Feature/addtests by @Tatsinnit in https://github.com/Azure/kubelogin/pull/238
- Bump k8s.io/cli-runtime from 0.26.2 to 0.26.3 by @dependabot in https://github.com/Azure/kubelogin/pull/237
- Bump github.com/spf13/cobra from 1.6.1 to 1.7.0 by @dependabot in https://github.com/Azure/kubelogin/pull/245
- Bump golang.org/x/crypto from 0.7.0 to 0.8.0 by @dependabot in https://github.com/Azure/kubelogin/pull/250
- Add codecov badge to this repo. by @Tatsinnit in https://github.com/Azure/kubelogin/pull/252
- Bump k8s.io/apimachinery from 0.26.3 to 0.27.1 by @dependabot in https://github.com/Azure/kubelogin/pull/257
- Bump k8s.io/client-go from 0.26.3 to 0.27.1 by @dependabot in https://github.com/Azure/kubelogin/pull/258
- Fix merge conflicts and breaking changes in PR 221 by @cirvine-MSFT in https://github.com/Azure/kubelogin/pull/264
- Fix merge conflicts in PR 232 updating adal from 0.9.22 to 0.9.23 by @cirvine-MSFT in https://github.com/Azure/kubelogin/pull/265

### Doc Update

- refactor windows install doc by @weinong in https://github.com/Azure/kubelogin/pull/233
- adding github pages by @weinong in https://github.com/Azure/kubelogin/pull/241
- added inline toc by @weinong in https://github.com/Azure/kubelogin/pull/244
- Document scoop installation option by @goostleek in https://github.com/Azure/kubelogin/pull/242
- revamp the website by @weinong in https://github.com/Azure/kubelogin/pull/246
- update readme and docs by @weinong in https://github.com/Azure/kubelogin/pull/247
- ignore docs and readme on some workflows by @weinong in https://github.com/Azure/kubelogin/pull/248
- Add reference to a context. by @Tatsinnit in https://github.com/Azure/kubelogin/pull/253
- How to install kubelogin with asdf tool manager by @daveneeley in https://github.com/Azure/kubelogin/pull/256
- Update devicecode.md by @madhurgupta03 in https://github.com/Azure/kubelogin/pull/26607070100000018000081A4000000000000000000000001691F8CFD000001BC000000000000000000000000000000000000002400000000kubelogin-0.2.13/CODE_OF_CONDUCT.md# Microsoft Open Source Code of Conduct

This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).

Resources:

- [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/)
- [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
- Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns
07070100000019000081A4000000000000000000000001691F8CFD00000396000000000000000000000000000000000000001C00000000kubelogin-0.2.13/Dockerfile# Dockerfile for kubelogin
# This Dockerfile copies a pre-built binary into a minimal scratch image.
# The binary should be built before running docker build using: make kubelogin
# 
# Usage:
#   make build-image                    # Build with latest tag
#   GIT_TAG=v1.0.0 make build-image   # Build with specific tag
#
FROM scratch

# Build arguments for multi-architecture support
ARG TARGETARCH=amd64
ARG VERSION=""

# OpenContainers Image Spec labels
LABEL org.opencontainers.image.source="https://github.com/Azure/kubelogin"
LABEL org.opencontainers.image.description="Kubernetes credential plugin for Azure authentication"
LABEL org.opencontainers.image.licenses="MIT"
LABEL org.opencontainers.image.version="${VERSION}"

# Copy the pre-built binary from local build to /usr/local/bin
COPY bin/linux_${TARGETARCH}/kubelogin /usr/local/bin/kubelogin

# Set the entrypoint
ENTRYPOINT ["/usr/local/bin/kubelogin"]0707010000001A000081A4000000000000000000000001691F8CFD0000048A000000000000000000000000000000000000001900000000kubelogin-0.2.13/LICENSE    MIT License

    Copyright (c) Microsoft Corporation.

    Permission is hereby granted, free of charge, to any person obtaining a copy
    of this software and associated documentation files (the "Software"), to deal
    in the Software without restriction, including without limitation the rights
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in all
    copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    SOFTWARE
0707010000001B000081A4000000000000000000000001691F8CFD0000074B000000000000000000000000000000000000001A00000000kubelogin-0.2.13/Makefile.DEFAULT_GOAL := all

include .bingo/Variables.mk

TARGET     := kubelogin
OS         := $(if $(GOOS),$(GOOS),$(shell go env GOOS))
ARCH       := $(if $(GOARCH),$(GOARCH),$(shell go env GOARCH))
GOARM      := $(if $(GOARM),$(GOARM),)
BIN         = bin/$(OS)_$(ARCH)$(if $(GOARM),v$(GOARM),)/$(TARGET)
ifeq ($(OS),windows)
  BIN = bin/$(OS)_$(ARCH)$(if $(GOARM),v$(GOARM),)/$(TARGET).exe
endif

GIT_TAG    := $(if $(GIT_TAG),$(GIT_TAG),)

LDFLAGS    := -X main.gitTag=$(GIT_TAG)

all: $(TARGET)

help:
	@echo "Available targets:"
	@echo "  all          - Build the kubelogin binary (default)"
	@echo "  $(TARGET)    - Build the kubelogin binary"
	@echo "  lint         - Run linting checks"
	@echo "  test         - Run tests (includes linting)"
	@echo "  clean        - Remove built binaries"
	@echo "  build-image  - Build Docker image with kubelogin binary"
	@echo ""
	@echo "Docker image build options:"
	@echo "  make build-image                    # Build with 'latest' tag"
	@echo "  GIT_TAG=v1.0.0 make build-image   # Build with specific tag"
	@echo ""
	@echo "Environment variables:"
	@echo "  GOOS         - Target OS (default: $(OS))"
	@echo "  GOARCH       - Target architecture (default: $(ARCH))"
	@echo "  GIT_TAG      - Git tag for version info and Docker tagging"

lint: $(GOLANGCI_LINT)
	$(GOLANGCI_LINT) run

test: lint
	go test -race -coverprofile=coverage.txt -covermode=atomic ./...

$(TARGET): clean
	CGO_ENABLED=$(if $(CGO_ENABLED),$(CGO_ENABLED),0) go build -o $(BIN) -ldflags "$(LDFLAGS)"

clean:
	-rm -f $(BIN)

# Docker image build target
IMAGE_NAME    := ghcr.io/azure/kubelogin
IMAGE_TAG     := $(if $(GIT_TAG),$(GIT_TAG),latest)

build-image: $(TARGET)
	docker build --build-arg VERSION=$(IMAGE_TAG) -t $(IMAGE_NAME):$(IMAGE_TAG) .
	@if [ "$(GIT_TAG)" != "" ]; then \
		docker tag $(IMAGE_NAME):$(IMAGE_TAG) $(IMAGE_NAME):latest; \
	fi
0707010000001C000081A4000000000000000000000001691F8CFD0000088F000000000000000000000000000000000000001B00000000kubelogin-0.2.13/README.md# kubelogin

[![Go Report Card](https://goreportcard.com/badge/github.com/Azure/kubelogin)](https://goreportcard.com/report/github.com/Azure/kubelogin)
[![golangci-lint](https://github.com/Azure/kubelogin/actions/workflows/golangci-lint.yml/badge.svg)](https://github.com/Azure/kubelogin/actions/workflows/golangci-lint.yml)
[![Build on Push](https://github.com/Azure/kubelogin/actions/workflows/build.yml/badge.svg)](https://github.com/Azure/kubelogin/actions/workflows/build.yml)
[![Go Reference](https://pkg.go.dev/badge/github.com/Azure/kubelogin.svg)](https://pkg.go.dev/github.com/Azure/kubelogin)
[![codecov](https://codecov.io/gh/Azure/kubelogin/branch/main/graph/badge.svg?token=02PZRX59VM)](https://codecov.io/gh/Azure/kubelogin)

This is a [client-go credential (exec) plugin](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#client-go-credential-plugins) implementing azure authentication. This plugin provides features that are not available in kubectl. It is supported on kubectl v1.11+

Check out [the official doc page](https://azure.github.io/kubelogin/index.html) for more details

## Installation

https://azure.github.io/kubelogin/install.html

## Quick Start

https://azure.github.io/kubelogin/quick-start.html

## Contributing

This project welcomes contributions and suggestions. Most contributions require you to agree to a
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
the rights to use your contribution. For details, visit <https://cla.opensource.microsoft.com>.

When you submit a pull request, a CLA bot will automatically determine whether you need to provide
a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions
provided by the bot. You will only need to do this once across all repos using our CLA.

This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
0707010000001D000081A4000000000000000000000001691F8CFD00000B04000000000000000000000000000000000000001D00000000kubelogin-0.2.13/SECURITY.md<!-- BEGIN MICROSOFT SECURITY.MD V0.0.4 BLOCK -->

## Security

Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).

If you believe you have found a security vulnerability in any Microsoft-owned repository that meets Microsoft's [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)) of a security vulnerability, please report it to us as described below.

## Reporting Security Issues

**Please do not report security vulnerabilities through public GitHub issues.**

Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report).

If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com).  If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc).

You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). 

Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:

  * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
  * Full paths of source file(s) related to the manifestation of the issue
  * The location of the affected source code (tag/branch/commit or direct URL)
  * Any special configuration required to reproduce the issue
  * Step-by-step instructions to reproduce the issue
  * Proof-of-concept or exploit code (if possible)
  * Impact of the issue, including how an attacker might exploit the issue

This information will help us triage your report more quickly.

If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs.

## Preferred Languages

We prefer all communications to be in English.

## Policy

Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd).

<!-- END MICROSOFT SECURITY.MD BLOCK -->0707010000001E000041ED000000000000000000000002691F8CFD00000000000000000000000000000000000000000000001600000000kubelogin-0.2.13/docs0707010000001F000041ED000000000000000000000002691F8CFD00000000000000000000000000000000000000000000001B00000000kubelogin-0.2.13/docs/book07070100000020000081A4000000000000000000000001691F8CFD00000005000000000000000000000000000000000000002600000000kubelogin-0.2.13/docs/book/.gitignorebook
07070100000021000081A4000000000000000000000001691F8CFD000002D1000000000000000000000000000000000000002400000000kubelogin-0.2.13/docs/book/MakefileTOOLS_BIN_DIR ?= $(PWD)/bin
# include tools bin dir in path so that mdbook-toc can be run by mdbook
PATH := ${PATH}:${TOOLS_BIN_DIR}
MDBOOK_VERSION ?= v0.4.27
# this version of mdbook-toc is built against mdbook 0.4.27
MDBOOK_TOC_VERSION ?= 0.11.2
MDBOOK_INSTALL := $(realpath ../../hack/install-mdbook.sh)
MDBOOK_TOC_INSTALL := $(realpath ../../hack/install-mdbook-toc.sh)

MDBOOK := $(TOOLS_BIN_DIR)/mdbook
$(MDBOOK):
	$(MDBOOK_INSTALL) ${MDBOOK_VERSION} ${TOOLS_BIN_DIR}

MDBOOK_TOC := $(TOOLS_BIN_DIR)/mdbook-toc
$(MDBOOK_TOC):
	$(MDBOOK_TOC_INSTALL) ${MDBOOK_TOC_VERSION} ${TOOLS_BIN_DIR}

DEPS := $(MDBOOK) $(MDBOOK_TOC)

.PHONY: build
build: $(DEPS)
	$(MDBOOK) build

.PHONY: serve
serve: $(DEPS)
	$(MDBOOK) serve
07070100000022000081A4000000000000000000000001691F8CFD0000014D000000000000000000000000000000000000002500000000kubelogin-0.2.13/docs/book/book.toml[book]
authors = ["Weinong Wang"]
language = "en"
multilingual = false
src = "src"
title = "Azure Kubelogin"
description = "A Kubernetes credential (exec) plugin implementing azure authentication"

[preprocessor.toc]
command = "mdbook-toc"

[output.html]
curly-quotes = true
git-repository-url = "https://github.com/Azure/kubelogin"
07070100000023000041ED000000000000000000000002691F8CFD00000000000000000000000000000000000000000000001F00000000kubelogin-0.2.13/docs/book/src07070100000024000081A4000000000000000000000001691F8CFD00000436000000000000000000000000000000000000002900000000kubelogin-0.2.13/docs/book/src/README.md# Introduction

`kubelogin` is a [client-go credential (exec) plugin](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#client-go-credential-plugins) implementing azure authentication. This plugin provides features that are not available in kubectl. It is supported on kubectl v1.11+

## Features

- [interactive device code login](./concepts/login-modes/devicecode.md)
- [interactive web browser login](./concepts/login-modes/interactive.md)
- [non-interactive service principal login](./concepts/login-modes/sp.md)
- [non-interactive user principal login](./concepts/login-modes/ropc.md) using [Resource owner login flow](https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth-ropc)
- [non-interactive managed service identity login](./concepts/login-modes/msi.md)
- [non-interactive Azure CLI token login (AKS only)](./concepts/login-modes/azurecli.md)
- [non-interactive Azure Developer CLI token login (AKS only)](./concepts/login-modes/azd.md)
- [non-interactive workload identity login](./concepts/login-modes/workloadidentity.md)
07070100000025000081A4000000000000000000000001691F8CFD0000062D000000000000000000000000000000000000002A00000000kubelogin-0.2.13/docs/book/src/SUMMARY.md# Summary

- [Introduction](./README.md)
- [Installation](./install.md)
- [Quick Start](./quick-start.md)
- [Concepts](./concepts.md)
  - [Exec Plugin](./concepts/exec-plugin.md)
  - [Login Modes](./concepts/login-modes.md)
    - [Device Code](./concepts/login-modes/devicecode.md)
    - [Azure CLI](./concepts/login-modes/azurecli.md)
    - [Azure Developer CLI](./concepts/login-modes/azd.md)
    - [Azure Pipelines](./concepts/login-modes/azurepipelines.md)
    - [Web Browser Interactive](./concepts/login-modes/interactive.md)
    - [Service Principal](./concepts/login-modes/sp.md)
    - [Managed Service Identity](./concepts/login-modes/msi.md)
    - [Workload Identity](./concepts/login-modes/workloadidentity.md)
    - [Resource Owner Password Credential](./concepts/login-modes/ropc.md)
  - [Using kubelogin with AKS](./concepts/aks.md)
  - [Using kubelogin to get Proof-of-Possession (PoP) tokens for Azure Arc](./concepts/azure-arc.md)
- [Command-Line Tool](./cli-reference.md)
  - [convert-kubeconfig](./cli/convert-kubeconfig.md)
  - [get-token](./cli/get-token.md)
  - [remove-cache-dir](./cli/remove-cache-dir.md)
- [Topics](./topics.md)
  - [Using in different environments](./topics/environments.md)
  - [Using Service Principal](./topics/sp.md)
  - [Setup k8s OIDC Provider using Azure AD](./topics/k8s-oidc-aad.md)
  - [Using kubelogin in Jenkins](./topics/jenkins.md)
- [Known Issues](./known-issues.md)
- [Development](./development.md)
  - [Releasing](./development/releasing.md)
- [Contributing](./contributing.md)
- [Code of Conduct](./code-of-conduct.md)
07070100000026000041ED000000000000000000000002691F8CFD00000000000000000000000000000000000000000000002300000000kubelogin-0.2.13/docs/book/src/cli07070100000027000081A4000000000000000000000001691F8CFD00000620000000000000000000000000000000000000003000000000kubelogin-0.2.13/docs/book/src/cli-reference.md# Command Line Tool

`kubelogin` command-line tool has following subcommands:

```sh
kubelogin -h
login to azure active directory and populate kubeconfig with AAD tokens

Usage:
  kubelogin [flags]
  kubelogin [command]

Available Commands:
  completion         Generate the autocompletion script for the specified shell
  convert-kubeconfig convert kubeconfig to use exec auth module
  get-token          get AAD token
  help               Help about any command
  remove-cache-dir   Remove all cached authentication record from filesystem

Flags:
  -h, --help          help for kubelogin
      --logtostderr   log to standard error instead of files (default true)
  -v, --v Level       number for the log level verbosity
      --version       version for kubelogin

Use "kubelogin [command] --help" for more information about a command.

```

Following sections provide in-depth information on these subcommands:

* [`kubelogin convert-kubeconfig`](./cli/convert-kubeconfig.md) - converts the kubeconfig to different login mode
* [`kubelogin get-token`](./cli/get-token.md) - gets the Azure AD token based on configured login mode. This subcommand is typically used in kubeconfig via [exec plugin](./concepts/exec-plugin.md) and is invoked by kubectl or any command-line tool, such as helm, implementing exec plugin.
* [`kubelogin remove-cache-dir`](./cli/remove-cache-dir.md) - remove all cached authentication record from filesystem.
* [DEPRECATED] [`kubelogin remove-tokens`](./cli/remove-cache-dir.md) - remove all cached authentication record from filesystem.

07070100000028000081A4000000000000000000000001691F8CFD000012DB000000000000000000000000000000000000003900000000kubelogin-0.2.13/docs/book/src/cli/convert-kubeconfig.md# convert-kubeconfig

This subcommand converts kubeconfig to [Exec plugin](../concepts/exec-plugin.md) using `kubelogin get-token` with specified [login mode](../concepts/login-modes.md).

Note that when `--context` is specified, only the matching kubeconfig context will be converted. Otherwise, every kubeconfig context that uses azure auth or Exec plugin will be converted.

## Usage

```sh
kubelogin convert-kubeconfig -h
convert kubeconfig to use exec auth module

Usage:
  kubelogin convert-kubeconfig [flags]

Flags:
      --authority-host string                          Workload Identity authority host. It may be specified in AZURE_AUTHORITY_HOST environment variable
      --azure-config-dir string                        Azure CLI config path
      --azure-pipelines-service-connection-id string   Service connection (resource) ID used by azurepipelines login method
      --cache-dir string                               directory to cache authentication record (default "/home/weinongw/.kube/cache/kubelogin/")
      --client-certificate string            AAD client cert in pfx or PEM. Used in spn login. It may be specified in AAD_SERVICE_PRINCIPAL_CLIENT_CERTIFICATE or AZURE_CLIENT_CERTIFICATE_PATH environment variable
      --client-certificate-password string   Password for AAD client cert. Used in spn login. It may be specified in AAD_SERVICE_PRINCIPAL_CLIENT_CERTIFICATE_PASSWORD or AZURE_CLIENT_CERTIFICATE_PASSWORD environment variable. Only used for PFX encoded certs.
      --client-id string                     AAD client application ID. It may be specified in AAD_SERVICE_PRINCIPAL_CLIENT_ID or AZURE_CLIENT_ID environment variable
      --client-secret string                 AAD client application secret. Used in spn login. It may be specified in AAD_SERVICE_PRINCIPAL_CLIENT_SECRET or AZURE_CLIENT_SECRET environment variable
      --context string                       The name of the kubeconfig context to use
      --disable-environment-override         Enable or disable the use of env-variables. Default false
      --disable-instance-discovery           set to true to disable instance discovery in environments with their own simple Identity Provider (not AAD) that do not have instance metadata discovery endpoint. Default false
  -e, --environment string                   Azure environment name (default "AzurePublicCloud")
      --federated-token-file string          Workload Identity federated token file. It may be specified in AZURE_FEDERATED_TOKEN_FILE environment variable
  -h, --help                                 help for convert-kubeconfig
      --identity-resource-id string          Managed Identity resource id.
      --kubeconfig string                    Path to the kubeconfig file to use for CLI requests.
      --legacy                               set to true to get token with 'spn:' prefix in audience claim
  -l, --login string                         Login method. Supported methods: devicecode, interactive, spn, ropc, msi, azurecli, azd, workloadidentity, azurepipelines. It may be specified in AAD_LOGIN_METHOD environment variable (default "devicecode")
      --login-hint string                    The login hint to pre-fill the username in the interactive login flow.
      --password string                      password for ropc login flow. It may be specified in AAD_USER_PRINCIPAL_PASSWORD or AZURE_PASSWORD environment variable
      --pop-claims key=val,key2=val2         contains a comma-separated list of claims to attach to the pop token in the format key=val,key2=val2. At minimum, specify the ARM ID of the cluster as `u=ARM_ID`
      --pop-enabled                          set to true to use a PoP token for authentication or false to use a regular bearer token
      --redirect-url string                  The URL Microsoft Entra ID will redirect to with the access token. This is only used for interactive login. This is an optional parameter.
      --server-id string                     AAD server application ID
  -t, --tenant-id string                     AAD tenant ID. It may be specified in AZURE_TENANT_ID environment variable
      --timeout duration                     Timeout duration for Azure CLI token requests. It may be specified in AZURE_CLI_TIMEOUT environment variable (default 30s)
      --use-azurerm-env-vars                 Use environment variable names of Terraform Azure Provider (ARM_CLIENT_ID, ARM_CLIENT_SECRET, ARM_CLIENT_CERTIFICATE_PATH, ARM_CLIENT_CERTIFICATE_PASSWORD, ARM_TENANT_ID)
      --username string                      user name for ropc login flow. It may be specified in AAD_USER_PRINCIPAL_NAME or AZURE_USERNAME environment variable

Global Flags:
      --logtostderr   log to standard error instead of files (default true)
  -v, --v Level       number for the log level verbosity
```
07070100000029000081A4000000000000000000000001691F8CFD000025F5000000000000000000000000000000000000003000000000kubelogin-0.2.13/docs/book/src/cli/get-token.md# get-token

This subcommand uses specified [login mode](../concepts/login-modes.md) to authenticate with Azure AD and return the access token to standard out.

## Usage

```sh
kubelogin get-token -h
get AAD token

Usage:
  kubelogin get-token [flags]

Flags:
      --authority-host string                          Workload Identity authority host. It may be specified in AZURE_AUTHORITY_HOST environment variable
      --azure-pipelines-service-connection-id string   Service connection (resource) ID used by azurepipelines login method. It may be specified in AZURESUBSCRIPTION_SERVICE_CONNECTION_ID environment variable
      --cache-dir string                               directory to cache authentication record (default "/home/weinongw/.kube/cache/kubelogin/")
      --client-certificate string            AAD client cert in pfx or PEM. Used in spn login. It may be specified in AAD_SERVICE_PRINCIPAL_CLIENT_CERTIFICATE or AZURE_CLIENT_CERTIFICATE_PATH environment variable
      --client-certificate-password string   Password for AAD client cert. Used in spn login. It may be specified in AAD_SERVICE_PRINCIPAL_CLIENT_CERTIFICATE_PASSWORD or AZURE_CLIENT_CERTIFICATE_PASSWORD environment variable. Only used for PFX encoded certs.
      --client-id string                     AAD client application ID. It may be specified in AAD_SERVICE_PRINCIPAL_CLIENT_ID or AZURE_CLIENT_ID environment variable. For Azure Pipelines login, it may be specified in AZURESUBSCRIPTION_CLIENT_ID environment variable
      --client-secret string                 AAD client application secret. Used in spn login. It may be specified in AAD_SERVICE_PRINCIPAL_CLIENT_SECRET or AZURE_CLIENT_SECRET environment variable
      --disable-environment-override         Enable or disable the use of env-variables. Default false
      --disable-instance-discovery           set to true to disable instance discovery in environments with their own simple Identity Provider (not AAD) that do not have instance metadata discovery endpoint. Default false
  -e, --environment string                   Azure environment name (default "AzurePublicCloud")
      --federated-token-file string          Workload Identity federated token file. It may be specified in AZURE_FEDERATED_TOKEN_FILE environment variable
  -h, --help                                 help for get-token
      --identity-resource-id string          Managed Identity resource id.
      --legacy                               set to true to get token with 'spn:' prefix in audience claim
  -l, --login string                         Login method. Supported methods: devicecode, interactive, spn, ropc, msi, azurecli, azd, workloadidentity, azurepipelines. It may be specified in AAD_LOGIN_METHOD environment variable (default "devicecode")
      --login-hint string                    The login hint to pre-fill the username in the interactive login flow.
      --password string                      password for ropc login flow. It may be specified in AAD_USER_PRINCIPAL_PASSWORD or AZURE_PASSWORD environment variable
      --pop-claims key=val,key2=val2         contains a comma-separated list of claims to attach to the pop token in the format key=val,key2=val2. At minimum, specify the ARM ID of the cluster as `u=ARM_ID`
      --pop-enabled                          set to true to use a PoP token for authentication or false to use a regular bearer token
      --redirect-url string                  The URL Microsoft Entra ID will redirect to with the access token. This is only used for interactive login. This is an optional parameter.
      --server-id string                     AAD server application ID
  -t, --tenant-id string                     AAD tenant ID. It may be specified in AZURE_TENANT_ID environment variable. For Azure Pipelines login, it may be specified in AZURESUBSCRIPTION_TENANT_ID environment variable
      --timeout duration                     Timeout duration for Azure CLI token requests. It may be specified in AZURE_CLI_TIMEOUT environment variable (default 30s)
      --use-azurerm-env-vars                 Use environment variable names of Terraform Azure Provider (ARM_CLIENT_ID, ARM_CLIENT_SECRET, ARM_CLIENT_CERTIFICATE_PATH, ARM_CLIENT_CERTIFICATE_PASSWORD, ARM_TENANT_ID)
      --username string                      user name for ropc login flow. It may be specified in AAD_USER_PRINCIPAL_NAME or AZURE_USERNAME environment variable

Global Flags:
      --logtostderr   log to standard error instead of files (default true)
  -v, --v Level       number for the log level verbosity
```

## Exec Plugin Examples

> cluster info including cluster CA and FQDN are omitted in below examples

### Device Code Flow (default)

```yaml
kind: Config
preferences: {}
users:
  - name: user-name
    user:
      exec:
        apiVersion: client.authentication.k8s.io/v1beta1
        command: kubelogin
        args:
          - get-token
          - --environment
          - AzurePublicCloud
          - --server-id
          - <AAD server app ID>
          - --client-id
          - <AAD client app ID>
          - --tenant-id
          - <AAD tenant ID>
```
### web browser Flow (default)

```yaml
kind: Config
preferences: {}
users:
  - name: user-name
    user:
      exec:
        apiVersion: client.authentication.k8s.io/v1beta1
        args:
        - get-token
        - --login
        - interactive
        - --server-id
        - <AAD server app ID>
        - --client-id
        - <AAD client app ID>
        - --tenant-id
        - <AAD tenant ID>
        - --environment
        - AzurePublicCloud
        command: kubelogin
```

### Spn login with secret

```yaml
kind: Config
preferences: {}
users:
  - name: demouser
    user:
      exec:
        apiVersion: client.authentication.k8s.io/v1beta1
        args:
          - get-token
          - --environment
          - AzurePublicCloud
          - --server-id
          - <AAD server app ID>
          - --client-id
          - <AAD client app ID>
          - --client-secret
          - <client_secret>
          - --tenant-id
          - <AAD tenant ID>
          - --login
          - spn
        command: kubelogin
        env: null
```

### Spn login with pfx certificate

```yaml
kind: Config
preferences: {}
users:
  - name: demouser
    user:
      exec:
        apiVersion: client.authentication.k8s.io/v1beta1
        args:
          - get-token
          - --environment
          - AzurePublicCloud
          - --server-id
          - <AAD server app ID>
          - --client-id
          - <AAD client app ID>
          - --client-certificate
          - <client_certificate_path>
          - --tenant-id
          - <AAD tenant ID>
          - --login
          - spn
        command: kubelogin
        env: null
```

### Managed Service Identity

```yaml
kind: Config
preferences: {}
users:
  - name: user-name
    user:
      exec:
        apiVersion: client.authentication.k8s.io/v1beta1
        command: kubelogin
        args:
          - get-token
          - --server-id
          - <AAD server app ID>
          - --login
          - msi
```

### Managed Service Identity with specific client ID

```yaml
kind: Config
preferences: {}
users:
  - name: user-name
    user:
      exec:
        apiVersion: client.authentication.k8s.io/v1beta1
        command: kubelogin
        args:
          - get-token
          - --server-id
          - <AAD server app ID>
          - --client-id
          - <MSI app ID>
          - --login
          - msi
```

### Azure CLI token login

```yaml
kind: Config
preferences: {}
users:
  - name: demouser
    user:
      exec:
        apiVersion: client.authentication.k8s.io/v1beta1
        args:
          - get-token
          - --server-id
          - <AAD server app ID>
          - --login
          - azurecli
        command: kubelogin
        env: null
```

### Workload Identity

```yaml
kind: Config
preferences: {}
users:
  - name: demouser
    user:
      exec:
        apiVersion: client.authentication.k8s.io/v1beta1
        args:
          - get-token
          - --server-id
          - <AAD server app ID>
          - --login
          - workloadidentity
        command: kubelogin
        env: null
```

### Azure Pipelines

When using `AzureCLI@2` task with Azure Resource Manager service connections, environment variables are automatically set. You only need to provide the `--server-id`:

```yaml
kind: Config
preferences: {}
users:
  - name: demouser
    user:
      exec:
        apiVersion: client.authentication.k8s.io/v1beta1
        args:
          - get-token
          - --server-id
          - <AAD server app ID>
          - --login
          - azurepipelines
        command: kubelogin
        env: null
```

If environment variables are not available, provide all parameters explicitly:

```yaml
kind: Config
preferences: {}
users:
  - name: demouser
    user:
      exec:
        apiVersion: client.authentication.k8s.io/v1beta1
        args:
          - get-token
          - --server-id
          - <AAD server app ID>
          - --client-id
          - <AAD client app ID>
          - --tenant-id
          - <AAD tenant ID>
          - --login
          - azurepipelines
          - --azure-pipelines-service-connection-id
          - <service connection resource ID>
        command: kubelogin
        env: null
```

> **Note**: When using `AzureCLI@2` task with Azure Resource Manager service connections, the following environment variables are automatically set and used:
> - `AZURESUBSCRIPTION_TENANT_ID` for `--tenant-id`
> - `AZURESUBSCRIPTION_CLIENT_ID` for `--client-id`
> - `AZURESUBSCRIPTION_SERVICE_CONNECTION_ID` for `--azure-pipelines-service-connection-id`
0707010000002A000081A4000000000000000000000001691F8CFD00000296000000000000000000000000000000000000003700000000kubelogin-0.2.13/docs/book/src/cli/remove-cache-dir.md# remove-cache-dir

This subcommand removes the cached access/refresh token from filesystem. Note that only `devicelogin`, `interactive`, and `ropc` login modes will cache the token.

## Usage

```sh
kubelogin remove-cache-dir -h
Remove all cached authentication record from filesystem

Usage:
  kubelogin remove-cache-dir [flags]

Flags:
      --cache-dir string   directory to cache authentication record (default "/home/weinongw/.kube/cache/kubelogin/")
  -h, --help               help for remove-cache-dir

Global Flags:
      --logtostderr   log to standard error instead of files (default true)
  -v, --v Level       number for the log level verbosity
```
0707010000002B000081A4000000000000000000000001691F8CFD000001BC000000000000000000000000000000000000003200000000kubelogin-0.2.13/docs/book/src/code-of-conduct.md# Microsoft Open Source Code of Conduct

This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).

Resources:

- [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/)
- [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
- Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns
0707010000002C000041ED000000000000000000000002691F8CFD00000000000000000000000000000000000000000000002800000000kubelogin-0.2.13/docs/book/src/concepts0707010000002D000081A4000000000000000000000001691F8CFD0000006F000000000000000000000000000000000000002B00000000kubelogin-0.2.13/docs/book/src/concepts.md# Concepts

This section documents the key concepts that will be used throughout the `kubelogin` command-line.
0707010000002E000081A4000000000000000000000001691F8CFD00000337000000000000000000000000000000000000002F00000000kubelogin-0.2.13/docs/book/src/concepts/aks.md# Using kubelogin with AKS

AKS uses a pair of first party Azure AD applications. These application IDs are the same in all environments.

## Azure Kubernetes Service AAD Server

applicationID: 6dae42f8-4368-4678-94ff-3960e28e3630

This is the application used by the server side. The access token accessing AKS clusters need to be issued for this app.
In most of `kubelogin` [login modes](./login-modes.md), `--server-id` is required parameter in `kubelogin get-token`.

## Azure Kubernetes Service AAD Client

applicationID: 80faf920-1908-4b52-b5ef-a8e7bedfc67a

This is a public client application used by `kubelogin` to perform login on behalf of the user. 
It's used in [device code](./login-modes/devicecode.md), [web browser interactive](./login-modes/interactive.md), and [ropc](./login-modes/ropc.md) login modes.
0707010000002F000081A4000000000000000000000001691F8CFD00000667000000000000000000000000000000000000003500000000kubelogin-0.2.13/docs/book/src/concepts/azure-arc.md# Using kubelogin with Azure Arc

kubelogin can be used to authenticate with Azure Arc-enabled clusters by requesting a [proof-of-possession (PoP) token](https://learn.microsoft.com/en-us/entra/msal/dotnet/advanced/proof-of-possession-tokens). This can be done by providing both of the following flags together:

1. `--pop-enabled`: indicates that `kubelogin` should request a PoP token instead of a regular bearer token
2. `--pop-claims`: is a comma-separated list of `key=value` claims to include in the PoP token. At minimum, this must include the u-claim as `u=ARM_ID_OF_CLUSTER`, which specifies the host that the requested token should allow access on.

These flags can be provided to either `kubelogin get-token` directly to get a PoP token, or to `kubelogin convert-kubeconfig` for `kubectl` to request the token internally. 

PoP token requests only work with `interactive` and `spn` login modes; these flags will be ignored if provided for other login modes.

## AAD Server App

```
applicationID: 6256c85f-0aad-4d50-b960-e6e9b21efe35
```

This is the application used by the server side. The access token needs to be issued for this app to access a 1P Arc-enabled cluster.

This server app ID is a required parameter for [`web browser interactive`](./login-modes/interactive.md) login mode supporting PoP token authentication.

## AAD Client App

```
applicationID: 3f4439ff-e698-4d6d-84fe-09c9d574f06b
```

This is a 1P client application used by `kubelogin` to perform login on behalf of the user. It should be used for [`web browser interactive`](./login-modes/interactive.md) login mode when using PoP token authentication.
07070100000030000081A4000000000000000000000001691F8CFD0000050B000000000000000000000000000000000000003700000000kubelogin-0.2.13/docs/book/src/concepts/exec-plugin.md# Exec Plugin

[Exec plugin](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#client-go-credential-plugins) 
is one of Kubernetes authentication strategies which allows `kubectl` to execute an external command to receive user credentials to send to api-server.
Since Kubernetes 1.26, [the default azure auth plugin is removed from `client-go` and `kubectl`](https://github.com/kubernetes/kubernetes/blob/ad18954259eae3db51bac2274ed4ca7304b923c4/CHANGELOG/CHANGELOG-1.26.md).

To interact with an Azure AD enabled Kubernetes cluster, Exec plugin using `kubelogin` will be required.

A kubeconfig using exec plugin will look somewhat like:

```yaml
kind: Config
preferences: {}
users:
  - name: user-name
    user:
      exec:
        apiVersion: client.authentication.k8s.io/v1beta1
        command: kubelogin
        args:
          - get-token
          - --environment
          - AzurePublicCloud
          - --server-id
          - <AAD server app ID>
          - --client-id
          - <AAD client app ID>
          - --tenant-id
          - <AAD tenant ID>
```

When using `kubelogin` in Exec plugin, the kubeconfig tells `kubectl` to execute `kubelogin get-token` subcommand to perform various Azure AD [login modes](./login-modes.md) to get the access token.
07070100000031000041ED000000000000000000000002691F8CFD00000000000000000000000000000000000000000000003400000000kubelogin-0.2.13/docs/book/src/concepts/login-modes07070100000032000081A4000000000000000000000001691F8CFD0000074A000000000000000000000000000000000000003700000000kubelogin-0.2.13/docs/book/src/concepts/login-modes.md# Login Modes

Most of the interaction with `kubelogin` is around `convert-kubeconfig` subcommand 
which uses the input kubeconfig specified in `--kubeconfig` or `KUBECONFIG` environment variable 
to convert to the final kubeconfig in [exec format](./concepts/exec-plugin.md) based on specified login mode.

In this section, the login modes will be explained in details.

## How Login Works

The login modes that `kubelogin` implements are [AAD OAuth 2.0 token grant flows](https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow).
Throughout `kubelogin` subcommands, you will see below common flags. In general, these flags are already setup when you get the kubeconfig from AKS.

- `--tenant-id`: [Azure AD tenant ID](https://learn.microsoft.com/en-us/azure/active-directory/fundamentals/active-directory-how-to-find-tenant)
- `--client-id`: the application ID of the [public client application](https://learn.microsoft.com/en-us/azure/active-directory/develop/msal-client-applications).
This client app is only used in [device code](./login-modes/devicecode.md), [web browser interactive](./login-modes/interactive.md), and [ropc](./login-modes/ropc.md) login modes.
- `--server-id`: the application ID of the [web app, or resource server](https://learn.microsoft.com/en-us/azure/active-directory/fundamentals/auth-oauth2). 
The token should be issued to this resource.

## References

* https://learn.microsoft.com/en-us/azure/active-directory/fundamentals/auth-oauth2
* https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow
* https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow
* https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth-ropc
* https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-protocols-oidc
07070100000033000081A4000000000000000000000001691F8CFD00000280000000000000000000000000000000000000003B00000000kubelogin-0.2.13/docs/book/src/concepts/login-modes/azd.md# Azure Developer CLI (azd)

This login mode uses the already logged-in context performed by Azure Developer CLI to get the access token.
The token will be issued in the same Azure AD tenant as in `azd auth login`.

`kubelogin` will not cache any token since it's already managed by Azure Developer CLI.

> ### NOTE
>
> This login mode only works with managed AAD in AKS.

## Usage Examples

```sh
azd auth login

export KUBECONFIG=/path/to/kubeconfig

kubelogin convert-kubeconfig -l azd

kubectl get nodes
```

## References

- https://learn.microsoft.com/azure/developer/azure-developer-cli/overview
- https://github.com/azure/azure-dev
07070100000034000081A4000000000000000000000001691F8CFD00000405000000000000000000000000000000000000004000000000kubelogin-0.2.13/docs/book/src/concepts/login-modes/azurecli.md# Azure CLI

This login mode uses the already logged-in context performed by Azure CLI to get the [access token](https://docs.microsoft.com/en-us/cli/azure/account?view=azure-cli-latest#az_account_get_access_token).
The token will be issued in the same Azure AD tenant as in `az login`.

`kubelogin` will not cache any token since it's already managed by Azure CLI.

> ### NOTE
>
> This login mode only works with managed AAD in AKS.

## Usage Examples

```sh
az login

export KUBECONFIG=/path/to/kubeconfig

kubelogin convert-kubeconfig -l azurecli

kubectl get nodes
```

When Azure CLI's config directory is outside the `${HOME}` directory, `--azure-config-dir` should be specified in `convert-kubeconfig` subcommand. It will generate the kubeconfig with environment variable configured. The same thing can also be achieved by setting environment variable `AZURE_CONFIG_DIR` to this directory while running `kubectl` command.

## References

- https://learn.microsoft.com/en-us/cli/azure/
- https://github.com/Azure/azure-cli
07070100000035000081A4000000000000000000000001691F8CFD00001BA8000000000000000000000000000000000000004600000000kubelogin-0.2.13/docs/book/src/concepts/login-modes/azurepipelines.md# Azure Pipelines

This login mode uses Azure Pipelines service connections and the built-in `SYSTEM_ACCESSTOKEN` to authenticate with Azure AD. This is particularly useful when running kubelogin as an exec plugin within Azure DevOps pipelines, such as in Terraform deployments that need to interact with Azure Kubernetes Service clusters.

The authentication leverages Azure Pipelines' managed identity integration through service connections, providing a seamless way to authenticate without additional credential management.

> ### NOTE
>
> This login mode only works within Azure DevOps pipelines and requires proper pipeline configuration.

## Prerequisites

1. **Service Connection**: An Azure Resource Manager service connection configured in your Azure DevOps project
2. **Pipeline Configuration**: The pipeline must have "Allow scripts to access the OAuth token" enabled in the agent job settings
3. **Environment Variables**: The following environment variables must be available (automatically set by Azure Pipelines when OAuth token access is enabled):
   - `SYSTEM_ACCESSTOKEN`: The OAuth token provided by Azure Pipelines
   - `SYSTEM_OIDCREQUESTURI`: The OIDC request URI (automatically set by Azure Pipelines)

## Required Parameters

- `--tenant-id`: Azure AD tenant ID where the service connection is configured
- `--client-id`: Application ID of the client application (typically the AKS cluster's client ID)  
- `--server-id`: Application ID of the server/resource (typically the AKS cluster's server ID)
- `--azure-pipelines-service-connection-id`: The resource ID of the Azure Resource Manager service connection

> **Note**: When using `AzureCLI@2` task with Azure Resource Manager service connections, Azure Pipelines automatically sets the following environment variables which kubelogin will use if the corresponding flags are not provided:
> - `AZURESUBSCRIPTION_TENANT_ID` - Automatically used for `--tenant-id`
> - `AZURESUBSCRIPTION_CLIENT_ID` - Automatically used for `--client-id`
> - `AZURESUBSCRIPTION_SERVICE_CONNECTION_ID` - Automatically used for `--azure-pipelines-service-connection-id`
>
> This means you only need to provide the `--server-id` parameter when these environment variables are available.

## Usage Examples

### Basic Usage in Pipeline

```yaml
# azure-pipelines.yml
steps:
- task: AzureCLI@2
  displayName: 'Deploy to AKS'
  inputs:
    azureSubscription: 'my-service-connection'
    scriptType: 'bash'
    scriptLocation: 'inlineScript'
    inlineScript: |
      # Download kubeconfig from AKS
      az aks get-credentials -g ${RESOURCE_GROUP} -n ${AKS_NAME}
      
      # Configure kubeconfig to use azurepipelines login
      # tenant-id, client-id, and service-connection-id are automatically detected from environment variables
      kubelogin convert-kubeconfig --login azurepipelines
      
      # Now kubectl commands will authenticate using Azure Pipelines credentials
      kubectl get nodes
```

### Basic Usage with Explicit Parameters

If you prefer to explicitly provide all parameters:

```yaml
# azure-pipelines.yml
steps:
- task: AzureCLI@2
  displayName: 'Deploy to AKS'
  inputs:
    azureSubscription: 'my-service-connection'
    scriptType: 'bash'
    scriptLocation: 'inlineScript'
    inlineScript: |
      # Configure kubeconfig to use azurepipelines login with explicit parameters
      kubelogin convert-kubeconfig \
        --login azurepipelines \
        --tenant-id $(tenant-id) \
        --client-id $(client-id) \
        --server-id $(server-id) \
        --azure-pipelines-service-connection-id $(service-connection-resource-id)
      
      # Now kubectl commands will authenticate using Azure Pipelines credentials
      kubectl get nodes
```

### Direct Token Retrieval

```bash
# In Azure DevOps pipeline (with "Allow scripts to access the OAuth token" enabled)
# Simplified version - uses environment variables automatically set by Azure Pipelines
kubelogin get-token \
  --login azurepipelines \
  --server-id <cluster-server-id>

# Or with explicit parameters
kubelogin get-token \
  --login azurepipelines \
  --tenant-id <tenant-id> \
  --client-id <client-id> \
  --server-id <cluster-server-id> \
  --azure-pipelines-service-connection-id <service-connection-resource-id>
```

## Environment Variable Support

When using `AzureCLI@2` task with Azure Resource Manager service connections, Azure Pipelines automatically sets environment variables for the service connection. Kubelogin automatically detects and uses these variables:

| Environment Variable | Used For | Command-line Flag Equivalent |
|---------------------|----------|------------------------------|
| `AZURESUBSCRIPTION_TENANT_ID` | Tenant ID | `--tenant-id` |
| `AZURESUBSCRIPTION_CLIENT_ID` | Client ID | `--client-id` |
| `AZURESUBSCRIPTION_SERVICE_CONNECTION_ID` | Service Connection ID | `--azure-pipelines-service-connection-id` |

**Precedence**: Command-line flags always take precedence over environment variables. This allows you to override specific values when needed.

**Disabling Environment Variables**: You can use the `--disable-environment-override` flag to ignore all environment variables and require explicit parameters.

## How It Works

1. **Service Connection**: Azure DevOps service connections provide managed identity or service principal authentication to Azure resources
2. **System Access Token**: When "Allow scripts to access the OAuth token" is enabled, Azure Pipelines provides a `SYSTEM_ACCESSTOKEN` environment variable
3. **Environment Variables**: When using `AzureCLI@2` task with Azure Resource Manager service connections, Azure Pipelines automatically sets subscription-specific environment variables
4. **OIDC Integration**: The `azurepipelines` login method uses Azure SDK's `AzurePipelinesCredential` to exchange the system access token for an Azure AD token
5. **Token Caching**: Authentication tokens are cached to improve performance across multiple kubectl operations

## Troubleshooting

### Common Errors

- **"SYSTEM_ACCESSTOKEN environment variable not set"**: Enable "Allow scripts to access the OAuth token" in your pipeline job settings
- **"SYSTEM_OIDCREQUESTURI environment variable not set"**: This should be automatically set by Azure Pipelines; check your Azure DevOps version and configuration
- **"tenant ID is required"**: Provide the `--tenant-id` parameter
- **"--azure-pipelines-service-connection-id is required"**: Provide the service connection resource ID parameter

### Finding Service Connection Resource ID

The service connection resource ID can be found in the Azure DevOps portal:
1. Go to Project Settings → Service connections
2. Select your Azure Resource Manager service connection
3. The resource ID is displayed in the connection details

## References

- https://learn.microsoft.com/en-us/azure/devops/pipelines/process/system-and-variable-groups
- https://learn.microsoft.com/en-us/azure/devops/pipelines/library/service-endpoints
- https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity#AzurePipelinesCredential07070100000036000081A4000000000000000000000001691F8CFD000004F5000000000000000000000000000000000000004200000000kubelogin-0.2.13/docs/book/src/concepts/login-modes/devicecode.md# Device Code

This is the default login mode in `convert-kubeconfig` subcommand. So `-l devicecode` is optional. This login will prompt the device code for user to login on a browser. 

Before `kubelogin` and [Exec plugin](./concepts/exec-plugin.md) were introduced, the azure authentication mode in `kubectl` supports device code flow only. 
It uses an old library that produces the token with `audience` claim that has `spn:` prefix 
which is not compatible with AKS Managed AAD using On-Behalf-Of mode ([Issue86410](https://github.com/kubernetes/kubernetes/issues/86410)).
So when running `convert-kubeconfig` subcommand, `kubelogin` will remove the `spn:` prefix in `audience` claim.
If it's desired to keep the old behavior, add `--legacy`. 

If you are using kubeconfig from AKS Legacy AAD (AADv1) clusters, `kubelogin` will automatically add `--legacy` flag.

## Usage Examples

```sh
export KUBECONFIG=/path/to/kubeconfig

kubelogin convert-kubeconfig

kubectl get nodes

```

## 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.


## References

- https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-device-code
07070100000037000081A4000000000000000000000001691F8CFD000004DD000000000000000000000000000000000000004300000000kubelogin-0.2.13/docs/book/src/concepts/login-modes/interactive.md# Web Browser Interactive

This login mode will automatically open a browser to login the user.
Once authenticated, the browser will redirect back to a local web server with access token.
The redirect URL can be set via `--redirect-url`.
This login mode complies with Conditional Access policy.

## Usage Examples

### Bearer token with interactive flow

```sh
export KUBECONFIG=/path/to/kubeconfig

kubelogin convert-kubeconfig -l interactive

kubectl get nodes
```

### Specifying Redirect URL

```sh
export KUBECONFIG=/path/to/kubeconfig

kubelogin convert-kubeconfig -l interactive --redirect-url http://localhost:8080

kubectl get nodes
```

### Specifying login user hint

```sh
export KUBECONFIG=/path/to/kubeconfig

kubelogin convert-kubeconfig -l interactive --login-hint user@example.com

kubectl get nodes
```


### Proof-of-possession (PoP) token with interactive flow

```sh
export KUBECONFIG=/path/to/kubeconfig

kubelogin convert-kubeconfig -l interactive --pop-enabled --pop-claims "u=/ARM/ID/OF/CLUSTER"

kubectl get nodes
```

### Clearing the cache

```sh
kubelogin remove-cache-dir
```

## References

- https://learn.microsoft.com/en-us/python/api/azure-identity/azure.identity.interactivebrowsercredential?view=azure-python07070100000038000081A4000000000000000000000001691F8CFD000002F9000000000000000000000000000000000000003B00000000kubelogin-0.2.13/docs/book/src/concepts/login-modes/msi.md# Managed Service Identity

This login mode should be used in an environment where 
[Managed Service Identity](https://learn.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview) 
is available such as Azure Virtual Machine, Azure Virtual Machine ScaleSet, Cloud Shell, Azure Container Instance, and Azure App Service.

The token will not be cached on the filesystem.

## Usage Examples

### Using default Managed Service Identity

```sh
export KUBECONFIG=/path/to/kubeconfig

kubelogin convert-kubeconfig -l msi

kubectl get nodes
```

### Using Managed Service Identity with specific identity

```sh
export KUBECONFIG=/path/to/kubeconfig

kubelogin convert-kubeconfig -l msi --client-id <msi-client-id>

kubectl get nodes
```
07070100000039000081A4000000000000000000000001691F8CFD000003E1000000000000000000000000000000000000003C00000000kubelogin-0.2.13/docs/book/src/concepts/login-modes/ropc.md# Resource Owner Password Credential (ropc)

> ### Warning: 
> [Microsoft recommends you do not use the ROPC flow](https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth-ropc)

> ### Note: 
> ROPC is not supported in hybrid identity federation scenarios (for example, Azure AD and ADFS used to authenticate on-premises accounts). If users are redirected to an on-premises identity providers, Azure AD is not able to test the username and password against that identity provider. Pass-through authentication is supported with ROPC, however.
> It also does not work when MFA policy is enabled
> Personal accounts that are invited to an Azure AD tenant can't use ROPC

## Usage Examples

```sh
export KUBECONFIG=/path/to/kubeconfig

kubelogin convert-kubeconfig -l ropc

export AAD_USER_PRINCIPAL_NAME=foo@bar.com
export AAD_USER_PRINCIPAL_PASSWORD=<password>

kubectl get nodes
```

## Reference

https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth-ropc
0707010000003A000081A4000000000000000000000001691F8CFD0000091A000000000000000000000000000000000000003A00000000kubelogin-0.2.13/docs/book/src/concepts/login-modes/sp.md# Service Principal

This login mode uses the service principal to login. The credential may be provided via environment variables or flag.
The supported credentials are password and pfx client certificate.

The token will not be cached on the filesystem.

```text
When AAD_SERVICE_PRINCIPAL_CLIENT_ID and AZURE_CLIENT_ID both exists,
AZURE_CLIENT_ID takes precedence.
```

## Usage Examples

### Client secret in environment variable

```sh
export KUBECONFIG=/path/to/kubeconfig

kubelogin convert-kubeconfig -l spn

export AAD_SERVICE_PRINCIPAL_CLIENT_ID=<spn client id>
export AAD_SERVICE_PRINCIPAL_CLIENT_SECRET=<spn secret>

kubectl get nodes
```

### Client secret in environment variable

```sh
export KUBECONFIG=/path/to/kubeconfig

kubelogin convert-kubeconfig -l spn

export AZURE_CLIENT_ID=<spn client id>
export AZURE_CLIENT_SECRET=<spn secret>

kubectl get nodes
```

### Client secret in command-line flag

```sh
export KUBECONFIG=/path/to/kubeconfig

kubelogin convert-kubeconfig -l spn --client-id <spn client id> --client-secret <spn client secret>

kubectl get nodes
```

> ### Warning
> this will leave the secret in the kubeconfig

### Client certificate

```sh
export KUBECONFIG=/path/to/kubeconfig

kubelogin convert-kubeconfig -l spn

export AAD_SERVICE_PRINCIPAL_CLIENT_ID=<spn client id>
export AAD_SERVICE_PRINCIPAL_CLIENT_CERTIFICATE=/path/to/cert.pfx
export AAD_SERVICE_PRINCIPAL_CLIENT_CERTIFICATE_PASSWORD=<pfx password>

kubectl get nodes
```

### Client certificate

```sh
export KUBECONFIG=/path/to/kubeconfig

kubelogin convert-kubeconfig -l spn

export AZURE_CLIENT_ID=<spn client id>
export AZURE_CLIENT_CERTIFICATE_PATH=/path/to/cert.pfx
export AZURE_CLIENT_CERTIFICATE_PASSWORD=<pfx password>

kubectl get nodes
```

### Proof-of-possession (PoP) token with client secret from environment variables
```sh
export KUBECONFIG=/path/to/kubeconfig

kubelogin convert-kubeconfig -l spn --pop-enabled --pop-claims "u=/ARM/ID/OF/CLUSTER"

export AAD_SERVICE_PRINCIPAL_CLIENT_ID=<spn client id>
export AAD_SERVICE_PRINCIPAL_CLIENT_SECRET=<spn secret>

kubectl get nodes
```

## Restrictions

- on AKS, it will only work with managed AAD
- the service principal can be member of [maximum 200 AAD groups](https://learn.microsoft.com/en-us/azure/active-directory/hybrid/how-to-connect-fed-group-claims) 
0707010000003B000081A4000000000000000000000001691F8CFD000004F0000000000000000000000000000000000000004800000000kubelogin-0.2.13/docs/book/src/concepts/login-modes/workloadidentity.md# Workload Identity

This login mode uses [Azure AD federated identity credentials](https://docs.microsoft.com/en-us/graph/api/resources/federatedidentitycredentials-overview?view=graph-rest-beta) to authenticate to Kubernetes clusters with Azure AD integration. This works by setting the environment variables:
* `AZURE_CLIENT_ID` is Azure Active Directory application ID that is federated with workload identity
* `AZURE_TENANT_ID` is Azure Active Directory tenant ID
* `AZURE_FEDERATED_TOKEN_FILE` is the file containing signed assertion of workload identity. E.g. Kubernetes projected service account (jwt) token
* `AZURE_AUTHORITY_HOST` is the base URL of an Azure Active Directory authority. E.g. `https://login.microsoftonline.com/`

With workload identity, it's possible to access Kubernetes clusters from CI/CD system such as Github, ArgoCD, etc. without storing Service Principal credentials in those external systems. To learn more, [here](https://github.com/weinong/azure-federated-identity-samples) is a sample to setup OIDC federation from Github.

In this login mode, token will not be cached on the filesystem.

## Usage Examples

```sh
export KUBECONFIG=/path/to/kubeconfig

kubelogin convert-kubeconfig -l workloadidentity

kubectl get nodes
```
0707010000003C000081A4000000000000000000000001691F8CFD0000027D000000000000000000000000000000000000002F00000000kubelogin-0.2.13/docs/book/src/contributing.md# Contributing

The Azure Kubelogin project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit [https://cla.microsoft.com](https://cla.microsoft.com).

When you submit a pull request, a CLA-bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA. Contributing
0707010000003D000041ED000000000000000000000002691F8CFD00000000000000000000000000000000000000000000002B00000000kubelogin-0.2.13/docs/book/src/development0707010000003E000081A4000000000000000000000001691F8CFD00000347000000000000000000000000000000000000002E00000000kubelogin-0.2.13/docs/book/src/development.md# Development

## Prerequisites

### System Dependencies

kubelogin uses secure token storage that requires platform-specific libraries:

#### Linux (Ubuntu/Debian)
```bash
sudo apt update
sudo apt install libsecret-1-0 libsecret-1-dev
```

#### Linux (CentOS/RHEL/Fedora)
```bash
# CentOS/RHEL
sudo yum install libsecret-devel

# Fedora
sudo dnf install libsecret-devel
```

#### macOS
No additional dependencies required (uses Keychain)

#### Windows
No additional dependencies required (uses Windows Credential Manager)

### Go Dependencies
- Go 1.23 or later
- Make

## Building

```bash
make build
```

## Testing

```bash
make test
```

**Note**: Tests require the system dependencies listed above. If you encounter errors related to `libsecret-1.so` or "encrypted storage isn't possible", ensure the libsecret library is installed.
0707010000003F000081A4000000000000000000000001691F8CFD00000432000000000000000000000000000000000000003800000000kubelogin-0.2.13/docs/book/src/development/releasing.md# Releasing

To make a new release and publish please follow the following steps.

1. Create a branch `publish-x.y.z`
2. Add a section to `CHANGELOG.md` with the header `## [x.y.z]` (N.B: make sure to write the new version in square brackets as the `changelog-reader` action only works if the `CHANGELOG.md` file follows the [Keep a Changelog standard](https://github.com/olivierlacan/keep-a-changelog))
3. Create a new PR, get approval and merge
4. Run the `release` workflow manually from the GH Actions tab

### Sample Changelog content for first release.

For first release using new release and publish using changelog here is a sample:

```

## [0.0.26]

* What is getting released here + @commit

Thanks to whoever was involved, pm.

```

### In Event of Special Case Failures Post Build and Release.

In an event where build and release were successful but publish failed for something else, in that case please make sure we delete the unsucessful release note and release tag, before re-running the release again, this will get release fresh release notes and tag.
07070100000040000081A4000000000000000000000001691F8CFD0000083E000000000000000000000000000000000000002A00000000kubelogin-0.2.13/docs/book/src/install.md# Installation

## Download from Release

Copy the latest [Releases](https://github.com/Azure/kubelogin/releases) to shell's search path.

## Homebrew

```sh
# install
brew install Azure/kubelogin/kubelogin

# upgrade
brew update
brew upgrade Azure/kubelogin/kubelogin
```

## Linux

### Azure Linux 3

```sh
tdnf install -y kubelogin
```

### Using azure cli

https://learn.microsoft.com/en-us/cli/azure/aks?view=azure-cli-latest#az-aks-install-cli

```sh
# install (May require using the command ‘sudo’)
az aks install-cli
```

## Container image

```sh
docker pull ghcr.io/azure/kubelogin:latest # or by release tag
```

### Using [asdf](https://asdf-vm.com/)

_asdf and the asdf-kubelogin plugin are not maintained by Microsoft._

```sh
# install
asdf plugin add kubelogin
asdf install kubelogin latest
asdf set kubelogin latest

# upgrade
asdf update
asdf plugin update kubelogin
asdf install kubelogin latest
asdf set kubelogin latest
```

### Using [mise](https://github.com/jdx/mise)

```sh
mise use -g azure-kubelogin@latest
```

## Windows

### Using winget

From Powershell:

```powershell
winget install --id=Kubernetes.kubectl  -e
winget install --id=Microsoft.Azure.Kubelogin  -e
```

### Using scoop

This package is not maintained by Microsoft.

From Powershell:

```powershell
scoop install kubectl azure-kubelogin
```

### Using chocolatey

This package is not maintained by Microsoft.

From Powershell:

```powershell
choco install kubernetes-cli azure-kubelogin
```

### Using azure cli

From Powershell:

```powershell
az aks install-cli
$targetDir="$env:USERPROFILE\.azure-kubelogin"
$oldPath = [System.Environment]::GetEnvironmentVariable("Path","User")
$oldPathArray=($oldPath) -split ";"
if(-Not($oldPathArray -Contains "$targetDir")) {
    write-host "Permanently adding $targetDir to User Path"
    $newPath = "$oldPath;$targetDir" -replace ";+", ";"
    [System.Environment]::SetEnvironmentVariable("Path",$newPath,"User")
    $env:Path = [System.Environment]::GetEnvironmentVariable("Path","User"),[System.Environment]::GetEnvironmentVariable("Path","Machine") -join ";"
}
```
07070100000041000081A4000000000000000000000001691F8CFD0000000F000000000000000000000000000000000000002F00000000kubelogin-0.2.13/docs/book/src/installation.md# Installation
07070100000042000081A4000000000000000000000001691F8CFD00000478000000000000000000000000000000000000002F00000000kubelogin-0.2.13/docs/book/src/known-issues.md# Known Issues

* [Maximum 200 groups will be included in the Azure AD JWT](https://docs.microsoft.com/en-us/azure/active-directory/hybrid/how-to-connect-fed-group-claims). 
For more than 200 groups, consider using [Application Roles](https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-add-app-roles-in-azure-ad-apps)
* Groups created in Azure AD can only be included by their ObjectID and not name, as [`sAMAccountName` is only available for groups synchronized from Active Directory](https://docs.microsoft.com/en-us/azure/active-directory/hybrid/how-to-connect-fed-group-claims#group-claims-for-applications-migrating-from-ad-fs-and-other-identity-providers)
* [`kubelogin` may not work with MSI when run in Azure Container Instance](https://github.com/Azure/kubelogin/issues/79)
* On AKS, [service principal](./concepts/login-modes/sp.md) login mode will only work with managed AAD, not legacy AAD.
* [Device code](./concepts/login-modes/devicecode.md) login mode does not work when Conditional Access policy is configured on Azure AD tenant.
Use [web browser interactive](./concepts/login-modes/interactive.md) instead.
07070100000043000081A4000000000000000000000001691F8CFD00000437000000000000000000000000000000000000002E00000000kubelogin-0.2.13/docs/book/src/maintenance.md## How to Release

To make a new release and publish please follow the following steps.

1. Create a branch `publish-x.y.z`
2. Add a section to `CHANGELOG.md` with the header `## [x.y.z]` (N.B: make sure to write the new version in square brackets as the `changelog-reader` action only works if the `CHANGELOG.md` file follows the [Keep a Changelog standard](https://github.com/olivierlacan/keep-a-changelog))
3. Create a new PR, get approval and merge
4. Run the `release` workflow manually from the GH Actions tab

### Sample Changelog content for first release.

For first release using new release and publish using changelog here is a sample:

```

## [0.0.26]

* What is getting released here + @commit

Thanks to whoever was involved, pm.

```

### In Event of Special Case Failures Post Build and Release.

In an event where build and release were successful but publish failed for something else, in that case please make sure we delete the unsucessful release note and release tag, before re-running the release again, this will get release fresh release notes and tag.07070100000044000081A4000000000000000000000001691F8CFD000001F6000000000000000000000000000000000000002E00000000kubelogin-0.2.13/docs/book/src/quick-start.md# Quick Start

After `kubelogin` is installed, do the following on Azure AD enabled AKS clusters

## Using Azure CLI login mode

```sh
az login

# by default, this command merges the kubeconfig into ${HOME}/.kube/config
az aks get-credentials -g ${RESOURCE_GROUP_NAME} -n ${AKS_NAME}


# kubelogin by default will use the kubeconfig from ${KUBECONFIG}. Specify --kubeconfig to override
# this converts to use azurecli login mode
kubelogin convert-kubeconfig -l azurecli

# voila!
kubectl get nodes
```
07070100000045000041ED000000000000000000000002691F8CFD00000000000000000000000000000000000000000000002600000000kubelogin-0.2.13/docs/book/src/topics07070100000046000081A4000000000000000000000001691F8CFD0000004D000000000000000000000000000000000000002900000000kubelogin-0.2.13/docs/book/src/topics.md# Topics

This section documents different usages of `kubelogin` in details.
07070100000047000081A4000000000000000000000001691F8CFD000005B3000000000000000000000000000000000000003600000000kubelogin-0.2.13/docs/book/src/topics/environments.md# Using in different environments

`kubelogin` supports Azure Environments:

- AzurePublicCloud (default value)
- AzureChinaCloud
- AzureUSGovernmentCloud
- AzureStackCloud

You can specify `--environment` in `kubelogin convert-kubeconfig`.

When using `AzureStackCloud` you will need to specify the actual endpoints in a config file, and set the environment variable `AZURE_ENVIRONMENT_FILEPATH` to that file.

The configuration parameters of this file:

```json
{
  "name": "AzureStackCloud",
  "managementPortalURL": "...",
  "publishSettingsURL": "...",
  "serviceManagementEndpoint": "...",
  "resourceManagerEndpoint": "...",
  "activeDirectoryEndpoint": "...",
  "galleryEndpoint": "...",
  "keyVaultEndpoint": "...",
  "graphEndpoint": "...",
  "serviceBusEndpoint": "...",
  "batchManagementEndpoint": "...",
  "storageEndpointSuffix": "...",
  "sqlDatabaseDNSSuffix": "...",
  "trafficManagerDNSSuffix": "...",
  "keyVaultDNSSuffix": "...",
  "serviceBusEndpointSuffix": "...",
  "serviceManagementVMDNSSuffix": "...",
  "resourceManagerVMDNSSuffix": "...",
  "containerRegistryDNSSuffix": "...",
  "cosmosDBDNSSuffix": "...",
  "tokenAudience": "...",
  "resourceIdentifiers": {
    "graph": "...",
    "keyVault": "...",
    "datalake": "...",
    "batch": "...",
    "operationalInsights": "..."
  }
}
```

The full configuration is available in the source code at <https://github.com/Azure/go-autorest/blob/main/autorest/azure/environments.go>.
07070100000048000081A4000000000000000000000001691F8CFD00000700000000000000000000000000000000000000003100000000kubelogin-0.2.13/docs/book/src/topics/jenkins.md# Using kubelogin in Jenkins

In Jenkins, since workspaces are most likely run under `jenkins` user, different login modes may have different configuration requirements to allow multiple builds to run concurrently. When it is not configured properly, there may be clashing in cache or login context that results in `You must be logged in to the server (Unauthorized)` error message.

## Using Azure CLI Login mode

When Azure CLI is installed in Jenkins environment, Azure CLI's config directory likely resides in Jenkins workspace directory. To use the Azure CLI, environment variable `AZURE_CONFIG_DIR` should be specified.

Using kubelogin `convert-kubeconfig` subcommand with `--azure-config-dir`, the generated kubeconfig will configure the environment variable for `get-token` subcommand to find the corresponding Azure config directory. For example,

```sh
stage('Download kubeconfig and convert') {
    steps {
        sh 'az aks get-credentials -g ${RESOURCE_GROUP} -n ${CLUSTER_NAME}'
        sh 'kubelogin convert-kubeconfig -l azurecli --azure-config-dir ${AZURE_CONFIG_DIR:-${WORKSPACE}/.azure}'
    }
}

stage('Run kubectl') {
    steps {
        sh 'kubectl get nodes'
    }
}
```

## Using Device Code, Web Browser, and ROPC Login Modes

Since `kubelogin` by default caches authentication record (a json file containing user identification such as object ID and tenant ID) at `${HOME}/.kube/cache/kubelogin/auth.json` in [device code](../concepts/login-modes/devicecode.md),
[web browser interactive](../concepts/login-modes/interactive.md), and [ropc](../concepts/login-modes/ropc.md) [login modes](../concepts/login-modes.md),
`kubelogin covert-kubeconfig --cache-dir` should be specified to a directory under Jenkins workspace such as `${WORKSPACE}/.kube/cache/kubelogin`.
07070100000049000081A4000000000000000000000001691F8CFD0000074A000000000000000000000000000000000000003600000000kubelogin-0.2.13/docs/book/src/topics/k8s-oidc-aad.md# Setup k8s OIDC Provider using Azure AD

`kubelogin` can be used to authenticate to general kubernetes clusters using AAD as an OIDC provider. 

1. Create an AAD Enterprise Application and the corresponding App Registration. Check the `Allow public client flows` checkbox. 
Configure groups to be included in the response. Take a note of the directory (tenant) ID as `$AAD_TENANT_ID` and the application (client) ID as `$AAD_CLIENT_ID`
1. Configure the API server with the following flags:

   * Issuer URL: `--oidc-issuer-url=https://sts.windows.net/$AAD_TENANT_ID/`
   * Client ID: `--oidc-client-id=$AAD_CLIENT_ID`
   * Username claim: `--oidc-username-claim=upn`

   See the [kubernetes docs for optional flags](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#configuring-the-api-server). For EKS clusters [configure this on the Management Console](https://docs.amazonaws.cn/en_us/eks/latest/userguide/authenticate-oidc-identity-provider.html) or via [terraform](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/eks_identity_provider_config).

3. Configure the [Exec plugin](../concepts/exec-plugin.md) with `kubelogin` to use the application from the first step:

   ```sh
   kubectl config set-credentials "azure-user" \
     --exec-api-version=client.authentication.k8s.io/v1beta1 \
     --exec-command=kubelogin \
     --exec-arg=get-token \
     --exec-arg=--environment \
     --exec-arg=AzurePublicCloud \
     --exec-arg=--server-id \
     --exec-arg=$AAD_CLIENT_ID \
     --exec-arg=--client-id \
     --exec-arg=$AAD_CLIENT_ID \
     --exec-arg=--tenant-id \
     --exec-arg=$AAD_TENANT_ID
   ```

4. Use this credential to connect to the cluster:

   ```
   kubectl config set-context "$CLUSTER_NAME" --cluster="$CLUSTER_NAME" --user=azure-user
   kubectl config use-context "$CLUSTER_NAME"
   ```

0707010000004A000081A4000000000000000000000001691F8CFD0000065E000000000000000000000000000000000000002C00000000kubelogin-0.2.13/docs/book/src/topics/sp.md# Using Service Principal

This section documents the end to end flow to use `kubelogin` to access AKS cluster with a service principal.

## 1. Create a service principal or use an existing one.

```sh
az ad sp create-for-rbac --skip-assignment --name myAKSAutomationServicePrincipal
```

The output is similar to the following example.

```json
{
  "appId": "<spn client id>",
  "displayName": "myAKSAutomationServicePrincipal",
  "name": "http://myAKSAutomationServicePrincipal",
  "password": "<spn secret>",
  "tenant": "<aad tenant id>"
}
```

## 2. Query your service principal AAD Object ID by using the command below.

```sh
az ad sp show --id <spn client id> --query "id"
```

## 3. To configure the role binding on Azure Kubernetes Service, the user in rolebinding should be the SP's Object ID.

For example,

```yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: sp-role-binding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
  - apiGroup: rbac.authorization.k8s.io
    kind: User
    name: <service-principal-object-id>
```

## 4. Use `kubelogin` to convert the kubeconfig

```sh
export KUBECONFIG=/path/to/kubeconfig

kubelogin convert-kubeconfig -l spn

export AAD_SERVICE_PRINCIPAL_CLIENT_ID=<spn client id>
export AAD_SERVICE_PRINCIPAL_CLIENT_SECRET=<spn secret>

kubectl get nodes
```

or write your spn secret permanently into the kubeconfig (not preferred!):

```sh
export KUBECONFIG=/path/to/kubeconfig

kubelogin convert-kubeconfig -l spn --client-id <spn client id> --client-secret <spn secret>

kubectl get nodes
```
0707010000004B000081A4000000000000000000000001691F8CFD0000108F000000000000000000000000000000000000001800000000kubelogin-0.2.13/go.modmodule github.com/Azure/kubelogin

// NOTE: kubelogin follows the same support policy as Go, which supports the last two major versions.
go 1.24.9

require (
	github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0
	github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0
	github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.0
	github.com/Azure/go-autorest/autorest v0.11.29
	github.com/Azure/go-autorest/autorest/adal v0.9.23
	github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1
	github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2
	github.com/golang-jwt/jwt/v4 v4.5.2
	github.com/google/go-cmp v0.6.0
	github.com/google/uuid v1.6.0
	github.com/spf13/cobra v1.8.1
	github.com/spf13/pflag v1.0.5
	github.com/stretchr/testify v1.10.0
	go.uber.org/mock v0.5.0
	golang.org/x/crypto v0.45.0
	golang.org/x/sys v0.38.0
	gopkg.in/dnaeon/go-vcr.v4 v4.0.2
	k8s.io/apimachinery v0.29.3
	k8s.io/cli-runtime v0.29.3
	k8s.io/client-go v0.29.3
	k8s.io/klog/v2 v2.130.1
)

require (
	github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
	github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
	github.com/Azure/go-autorest v14.2.0+incompatible // indirect
	github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
	github.com/Azure/go-autorest/logger v0.2.1 // indirect
	github.com/Azure/go-autorest/tracing v0.6.0 // indirect
	github.com/davecgh/go-spew v1.1.1 // indirect
	github.com/emicklei/go-restful/v3 v3.11.0 // indirect
	github.com/evanphx/json-patch v5.6.0+incompatible // indirect
	github.com/go-errors/errors v1.4.2 // indirect
	github.com/go-logr/logr v1.4.1 // indirect
	github.com/go-openapi/jsonpointer v0.19.6 // indirect
	github.com/go-openapi/jsonreference v0.20.2 // indirect
	github.com/go-openapi/swag v0.22.3 // indirect
	github.com/gogo/protobuf v1.3.2 // indirect
	github.com/golang-jwt/jwt/v5 v5.2.3 // indirect
	github.com/golang/protobuf v1.5.4 // indirect
	github.com/google/btree v1.1.2 // indirect
	github.com/google/gnostic-models v0.6.8 // indirect
	github.com/google/gofuzz v1.2.0 // indirect
	github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
	github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
	github.com/imdario/mergo v0.3.13 // indirect
	github.com/inconshreveable/mousetrap v1.1.0 // indirect
	github.com/josharian/intern v1.0.0 // indirect
	github.com/json-iterator/go v1.1.12 // indirect
	github.com/keybase/go-keychain v0.0.1 // indirect
	github.com/kylelemons/godebug v1.1.0 // indirect
	github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
	github.com/mailru/easyjson v0.7.7 // indirect
	github.com/moby/term v0.0.0-20221205130635-1aeaba878587 // indirect
	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
	github.com/modern-go/reflect2 v1.0.2 // indirect
	github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
	github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
	github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
	github.com/pkg/errors v0.9.1 // indirect
	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.47.0 // indirect
	golang.org/x/oauth2 v0.30.0 // indirect
	golang.org/x/sync v0.18.0 // indirect
	golang.org/x/term v0.37.0 // indirect
	golang.org/x/text v0.31.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
	gopkg.in/yaml.v2 v2.4.0 // indirect
	gopkg.in/yaml.v3 v3.0.1 // indirect
	k8s.io/api v0.29.3 // indirect
	k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect
	k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect
	sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
	sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 // indirect
	sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 // indirect
	sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
	sigs.k8s.io/yaml v1.3.0 // indirect
)
0707010000004C000081A4000000000000000000000001691F8CFD00007CE3000000000000000000000000000000000000001800000000kubelogin-0.2.13/go.sumcloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 h1:nyQWyZvwGTvunIMxi1Y9uXkcyr+I7TeNrr/foo4Kpk8=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0/go.mod h1:l38EPgmsp71HHLq9j7De57JcKOWPyhrsW1Awm1JS6K0=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 h1:B/dfvscEQtew9dVuoxqxrUKKv8Ih2f55PydknDamU+g=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0/go.mod h1:fiPSssYvltE08HJchL04dOy+RD4hgrjph0cwGGMntdI=
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.0 h1:+m0M/LFxN43KvULkDNfdXOgrjtg6UYJPFBJyuEcRCAw=
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.0/go.mod h1:PwOyop78lveYMRs6oCxjiVyBdyCgIYH6XHIVZO9/SFQ=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest/autorest v0.11.29 h1:I4+HL/JDvErx2LjyzaVxllw2lRDB5/BT2Bm4g20iqYw=
github.com/Azure/go-autorest/autorest v0.11.29/go.mod h1:ZtEzC4Jy2JDrZLxvWs8LrBWEBycl1hbT1eknI8MtfAs=
github.com/Azure/go-autorest/autorest/adal v0.9.22/go.mod h1:XuAbAEUv2Tta//+voMI038TrJBqjKam0me7qR+L8Cmk=
github.com/Azure/go-autorest/autorest/adal v0.9.23 h1:Yepx8CvFxwNKpH6ja7RZ+sKX+DWYNldbLiALMC3BTz8=
github.com/Azure/go-autorest/autorest/adal v0.9.23/go.mod h1:5pcMqFkdPhviJdlEy3kC/v1ZLnQl0MH6XA5YCcMhy4c=
github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw=
github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
github.com/Azure/go-autorest/autorest/mocks v0.4.2 h1:PGN4EDXnuQbojHbU0UWoNvmu9AGVwYHG9/fkDYhtAfw=
github.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU=
github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg=
github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs=
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U=
github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v5 v5.2.3 h1:kkGXqQOBSDDWRhWNXTFpqGSCMyh/PLnqUvMGJPDJDs0=
github.com/golang-jwt/jwt/v5 v5.2.3/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec=
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA=
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU=
github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0=
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/moby/term v0.0.0-20221205130635-1aeaba878587 h1:HfkjXDfhgVaN5rmueG8cL8KKeFNecRCXFhaJ2qZ5SKA=
github.com/moby/term v0.0.0-20221205130635-1aeaba878587/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0=
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4=
github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o=
github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0y4=
github.com/redis/go-redis/v9 v9.6.1/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJCQLgE9+RA=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ=
github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.starlark.net v0.0.0-20230525235612-a134d8f9ddca h1:VdD38733bfYv5tUZwEIskMM93VanwNIi5bIKnDrJdEY=
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=
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.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
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=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
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.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
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=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
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.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
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=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
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.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
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.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
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=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
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.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
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=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
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/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=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
k8s.io/api v0.29.3 h1:2ORfZ7+bGC3YJqGpV0KSDDEVf8hdGQ6A03/50vj8pmw=
k8s.io/api v0.29.3/go.mod h1:y2yg2NTyHUUkIoTC+phinTnEa3KFM6RZ3szxt014a80=
k8s.io/apimachinery v0.29.3 h1:2tbx+5L7RNvqJjn7RIuIKu9XTsIZ9Z5wX2G22XAa5EU=
k8s.io/apimachinery v0.29.3/go.mod h1:hx/S4V2PNW4OMg3WizRrHutyB5la0iCUbZym+W0EQIU=
k8s.io/cli-runtime v0.29.3 h1:r68rephmmytoywkw2MyJ+CxjpasJDQY7AGc3XY2iv1k=
k8s.io/cli-runtime v0.29.3/go.mod h1:aqVUsk86/RhaGJwDhHXH0jcdqBrgdF3bZWk4Z9D4mkM=
k8s.io/client-go v0.29.3 h1:R/zaZbEAxqComZ9FHeQwOh3Y1ZUs7FaHKZdQtIc2WZg=
k8s.io/client-go v0.29.3/go.mod h1:tkDisCvgPfiRpxGnOORfkljmS+UrW+WtXAy2fTvXJB0=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780=
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA=
k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI=
k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 h1:XX3Ajgzov2RKUdc5jW3t5jwY7Bo7dcRm+tFxT+NfgY0=
sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3/go.mod h1:9n16EZKMhXBNSiUC5kSdFQJkdH3zbxS/JoO619G1VAY=
sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 h1:W6cLQc5pnqM7vh3b7HvGNfXrJ/xL6BDMS0v1V/HHg5U=
sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3/go.mod h1:JWP1Fj0VWGHyw3YUPjXSQnRnrwezrZSrApfX5S0nIag=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
0707010000004D000041ED000000000000000000000002691F8CFD00000000000000000000000000000000000000000000001600000000kubelogin-0.2.13/hack0707010000004E000081ED000000000000000000000001691F8CFD00000238000000000000000000000000000000000000002C00000000kubelogin-0.2.13/hack/install-mdbook-toc.sh#!/bin/bash

set -o errexit
set -o nounset
set -o pipefail

VERSION=${1}
OUTPUT_PATH=${2}

# Ensure the output folder exists
mkdir -p "${OUTPUT_PATH}"

RELEASE_NAME=""
case "$OSTYPE" in
  darwin*) RELEASE_NAME="x86_64-apple-darwin.tar.gz"  ;;
  linux*)  RELEASE_NAME="x86_64-unknown-linux-gnu.tar.gz" ;;
  *)       echo "No mdBook release available for: $OSTYPE" && exit 1;;
esac

# Download and extract the mdBook release
curl -L "https://github.com/badboy/mdbook-toc/releases/download/${VERSION}/mdbook-toc-${VERSION}-${RELEASE_NAME}" | tar -xvz -C "${OUTPUT_PATH}"
0707010000004F000081ED000000000000000000000001691F8CFD00000233000000000000000000000000000000000000002800000000kubelogin-0.2.13/hack/install-mdbook.sh#!/bin/bash

set -o errexit
set -o nounset
set -o pipefail

VERSION=${1}
OUTPUT_PATH=${2}

# Ensure the output folder exists
mkdir -p "${OUTPUT_PATH}"

RELEASE_NAME=""
case "$OSTYPE" in
  darwin*) RELEASE_NAME="x86_64-apple-darwin.tar.gz"  ;;
  linux*)  RELEASE_NAME="x86_64-unknown-linux-gnu.tar.gz" ;;
  *)       echo "No mdBook release available for: $OSTYPE" && exit 1;;
esac

# Download and extract the mdBook release
curl -L "https://github.com/rust-lang/mdBook/releases/download/${VERSION}/mdbook-${VERSION}-${RELEASE_NAME}" | tar -xvz -C "${OUTPUT_PATH}"
07070100000050000081A4000000000000000000000001691F8CFD000001BF000000000000000000000000000000000000001900000000kubelogin-0.2.13/main.gopackage main

import (
	"flag"
	"os"

	"github.com/Azure/kubelogin/pkg/cmd"
	"github.com/spf13/pflag"
	klog "k8s.io/klog/v2"
)

func main() {
	klog.InitFlags(nil)
	pflag.CommandLine.AddGoFlag(flag.CommandLine.Lookup("v"))
	pflag.CommandLine.AddGoFlag(flag.CommandLine.Lookup("logtostderr"))
	_ = pflag.CommandLine.Set("logtostderr", "true")
	root := cmd.NewRootCmd(loadVersion().String())
	if err := root.Execute(); err != nil {
		os.Exit(1)
	}
}
07070100000051000041ED000000000000000000000002691F8CFD00000000000000000000000000000000000000000000001500000000kubelogin-0.2.13/pkg07070100000052000041ED000000000000000000000002691F8CFD00000000000000000000000000000000000000000000001900000000kubelogin-0.2.13/pkg/cmd07070100000053000081A4000000000000000000000001691F8CFD00000395000000000000000000000000000000000000002400000000kubelogin-0.2.13/pkg/cmd/convert.gopackage cmd

import (
	"github.com/Azure/kubelogin/pkg/internal/converter"
	"github.com/spf13/cobra"
	"k8s.io/client-go/tools/clientcmd"
)

// newConvertCmd provides a cobra command for convert sub command
func newConvertCmd() *cobra.Command {
	o := converter.New()

	cmd := &cobra.Command{
		Use:          "convert-kubeconfig",
		Short:        "convert kubeconfig to use exec auth module",
		SilenceUsage: true,
		RunE: func(c *cobra.Command, args []string) error {
			o.Flags = c.Flags()
			o.UpdateFromEnv()

			if err := o.Validate(); err != nil {
				return err
			}

			pathOptions := clientcmd.NewDefaultPathOptions()
			pathOptions.LoadingRules.ExplicitPath, _ = o.Flags.GetString("kubeconfig")

			if err := converter.Convert(o, pathOptions); err != nil {
				return err
			}
			return nil
		},
		ValidArgsFunction: cobra.NoFileCompletions,
	}

	o.AddFlags(cmd.Flags())
	o.AddCompletions(cmd)

	return cmd
}
07070100000054000081A4000000000000000000000001691F8CFD00000389000000000000000000000000000000000000002B00000000kubelogin-0.2.13/pkg/cmd/removecachedir.gopackage cmd

import (
	"os"

	"github.com/Azure/kubelogin/pkg/internal/token"
	"github.com/spf13/cobra"
	klog "k8s.io/klog/v2"
)

// newRemoveAuthRecordCacheCmd provides a cobra command for removing token cache sub command
func newRemoveAuthRecordCacheCmd() *cobra.Command {
	var authRecordCacheDir string

	cmd := &cobra.Command{
		Use:          "remove-cache-dir",
		Short:        "Remove all cached authentication record from filesystem",
		SilenceUsage: true,
		RunE: func(c *cobra.Command, args []string) error {
			if err := os.RemoveAll(authRecordCacheDir); err != nil {
				klog.V(5).Infof("unable to delete authentication record cache in '%s': %s", authRecordCacheDir, err)
			}
			return nil
		},
		ValidArgsFunction: cobra.NoFileCompletions,
	}

	cmd.Flags().StringVar(&authRecordCacheDir, "cache-dir", token.DefaultAuthRecordCacheDir, "directory to cache authentication record")
	return cmd
}
07070100000055000081A4000000000000000000000001691F8CFD000003E8000000000000000000000000000000000000002D00000000kubelogin-0.2.13/pkg/cmd/removetokencache.gopackage cmd

import (
	"os"

	"github.com/Azure/kubelogin/pkg/internal/token"
	"github.com/spf13/cobra"
	klog "k8s.io/klog/v2"
)

// newRemoveAuthRecordCacheCmd provides a cobra command for removing token cache sub command
func newRemoveAuthRecordCacheCmdDeprecated() *cobra.Command {
	var authRecordCacheDir string

	cmd := &cobra.Command{
		Use:          "remove-tokens",
		Short:        "Remove all cached authentication record from filesystem",
		SilenceUsage: true,
		RunE: func(c *cobra.Command, args []string) error {
			if err := os.RemoveAll(authRecordCacheDir); err != nil {
				klog.V(5).Infof("unable to delete authentication record cache in '%s': %s", authRecordCacheDir, err)
			}
			return nil
		},
		ValidArgsFunction: cobra.NoFileCompletions,
		Deprecated:        "remove-tokens is deprecated, use remove-cache-dir instead",
	}

	cmd.Flags().StringVar(&authRecordCacheDir, "token-cache-dir", token.DefaultAuthRecordCacheDir, "directory to cache authentication record")
	return cmd
}
07070100000056000081A4000000000000000000000001691F8CFD00000259000000000000000000000000000000000000002100000000kubelogin-0.2.13/pkg/cmd/root.gopackage cmd

import (
	"github.com/spf13/cobra"
)

// NewRootCmd provides a cobra root command
func NewRootCmd(version string) *cobra.Command {

	cmd := &cobra.Command{
		Use:          "kubelogin",
		Short:        "login to azure active directory and populate kubeconfig with AAD tokens",
		SilenceUsage: true,
		Version:      version,
		RunE: func(c *cobra.Command, args []string) error {
			return c.Help()
		},
	}

	cmd.AddCommand(newConvertCmd())
	cmd.AddCommand(newTokenCmd())
	cmd.AddCommand(newRemoveAuthRecordCacheCmdDeprecated())
	cmd.AddCommand(newRemoveAuthRecordCacheCmd())

	return cmd
}
07070100000057000081A4000000000000000000000001691F8CFD0000036F000000000000000000000000000000000000002200000000kubelogin-0.2.13/pkg/cmd/token.gopackage cmd

import (
	"context"
	"os"
	"os/signal"

	"github.com/Azure/kubelogin/pkg/internal/token"
	"github.com/spf13/cobra"
)

// newTokenCmd provides a cobra command for convert sub command
func newTokenCmd() *cobra.Command {
	o := token.NewOptions(true)

	cmd := &cobra.Command{
		Use:          "get-token",
		Short:        "get AAD token",
		SilenceUsage: true,
		RunE: func(c *cobra.Command, args []string) error {
			o.UpdateFromEnv()

			ctx := context.Background()
			ctx, cancel := signal.NotifyContext(ctx, os.Interrupt)
			defer cancel()

			if err := o.Validate(); err != nil {
				return err
			}

			plugin, err := token.New(&o)
			if err != nil {
				return err
			}
			if err := plugin.Do(ctx); err != nil {
				return err
			}
			return nil
		},
		ValidArgsFunction: cobra.NoFileCompletions,
	}

	o.AddFlags(cmd.Flags())
	o.AddCompletions(cmd)

	return cmd
}
07070100000058000041ED000000000000000000000002691F8CFD00000000000000000000000000000000000000000000001E00000000kubelogin-0.2.13/pkg/internal07070100000059000041ED000000000000000000000002691F8CFD00000000000000000000000000000000000000000000002800000000kubelogin-0.2.13/pkg/internal/converter0707010000005A000081A4000000000000000000000001691F8CFD00004047000000000000000000000000000000000000003300000000kubelogin-0.2.13/pkg/internal/converter/convert.gopackage converter

import (
	"fmt"
	"strings"

	"k8s.io/client-go/tools/clientcmd"
	"k8s.io/client-go/tools/clientcmd/api"
	klog "k8s.io/klog/v2"

	"github.com/Azure/kubelogin/pkg/internal/token"
)

const (
	azureAuthProvider = "azure"
	cfgClientID       = "client-id"
	cfgApiserverID    = "apiserver-id"
	cfgTenantID       = "tenant-id"
	cfgEnvironment    = "environment"
	cfgConfigMode     = "config-mode"

	argClientID                          = "--client-id"
	argServerID                          = "--server-id"
	argTenantID                          = "--tenant-id"
	argEnvironment                       = "--environment"
	argClientSecret                      = "--client-secret"
	argClientCert                        = "--client-certificate"
	argClientCertPassword                = "--client-certificate-password"
	argIsLegacy                          = "--legacy"
	argUsername                          = "--username"
	argPassword                          = "--password"
	argLoginMethod                       = "--login"
	argIdentityResourceID                = "--identity-resource-id"
	argAuthorityHost                     = "--authority-host"
	argFederatedTokenFile                = "--federated-token-file"
	argTokenCacheDir                     = "--token-cache-dir"
	argAuthRecordCacheDir                = "--cache-dir"
	argIsPoPTokenEnabled                 = "--pop-enabled"
	argPoPTokenClaims                    = "--pop-claims"
	argDisableEnvironmentOverride        = "--disable-environment-override"
	argRedirectURL                       = "--redirect-url"
	argLoginHint                         = "--login-hint"
	argAzurePipelinesServiceConnectionID = "--azure-pipelines-service-connection-id"

	flagAzureConfigDir                    = "azure-config-dir"
	flagClientID                          = "client-id"
	flagContext                           = "context"
	flagServerID                          = "server-id"
	flagTenantID                          = "tenant-id"
	flagEnvironment                       = "environment"
	flagClientSecret                      = "client-secret"
	flagClientCert                        = "client-certificate"
	flagClientCertPassword                = "client-certificate-password"
	flagIsLegacy                          = "legacy"
	flagUsername                          = "username"
	flagPassword                          = "password"
	flagLoginMethod                       = "login"
	flagIdentityResourceID                = "identity-resource-id"
	flagAuthorityHost                     = "authority-host"
	flagFederatedTokenFile                = "federated-token-file"
	flagTokenCacheDir                     = "token-cache-dir"
	flagAuthRecordCacheDir                = "cache-dir"
	flagIsPoPTokenEnabled                 = "pop-enabled"
	flagPoPTokenClaims                    = "pop-claims"
	flagDisableEnvironmentOverride        = "disable-environment-override"
	flagRedirectURL                       = "redirect-url"
	flagLoginHint                         = "login-hint"
	flagAzurePipelinesServiceConnectionID = "azure-pipelines-service-connection-id"

	execName        = "kubelogin"
	getTokenCommand = "get-token"
	execAPIVersion  = "client.authentication.k8s.io/v1beta1"
	execInstallHint = `
kubelogin is not installed which is required to connect to AAD enabled cluster.

To learn more, please go to https://azure.github.io/kubelogin/
`

	azureConfigDir = "AZURE_CONFIG_DIR"
)

func getArgValues(o Options, authInfo *api.AuthInfo) (
	argServerIDVal,
	argClientIDVal,
	argEnvironmentVal,
	argTenantIDVal,
	argAuthRecordCacheDirVal,
	argPoPTokenClaimsVal,
	argRedirectURLVal,
	argLoginHintVal string,
	argIsLegacyConfigModeVal,
	argIsPoPTokenEnabledVal bool,
) {
	if authInfo == nil {
		return
	}

	isLegacyAuthProvider := isLegacyAzureAuth(authInfo)

	if o.isSet(flagEnvironment) {
		argEnvironmentVal = o.TokenOptions.Environment
	} else if isLegacyAuthProvider {
		if x, ok := authInfo.AuthProvider.Config[cfgEnvironment]; ok {
			argEnvironmentVal = x
		}
	} else {
		argEnvironmentVal = getExecArg(authInfo, argEnvironment)
	}

	if o.isSet(flagTenantID) {
		argTenantIDVal = o.TokenOptions.TenantID
	} else if isLegacyAuthProvider {
		if x, ok := authInfo.AuthProvider.Config[cfgTenantID]; ok {
			argTenantIDVal = x
		}
	} else {
		argTenantIDVal = getExecArg(authInfo, argTenantID)
	}

	if o.isSet(flagClientID) {
		argClientIDVal = o.TokenOptions.ClientID
	} else if isLegacyAuthProvider {
		if x, ok := authInfo.AuthProvider.Config[cfgClientID]; ok {
			argClientIDVal = x
		}
	} else {
		argClientIDVal = getExecArg(authInfo, argClientID)
	}

	if o.isSet(flagServerID) {
		argServerIDVal = o.TokenOptions.ServerID
	} else if isLegacyAuthProvider {
		if x, ok := authInfo.AuthProvider.Config[cfgApiserverID]; ok {
			argServerIDVal = x
		}
	} else {
		argServerIDVal = getExecArg(authInfo, argServerID)
	}

	if o.isSet(flagIsLegacy) && o.TokenOptions.IsLegacy {
		argIsLegacyConfigModeVal = true
	} else if isLegacyAuthProvider {
		if x := authInfo.AuthProvider.Config[cfgConfigMode]; x == "" || x == "0" {
			argIsLegacyConfigModeVal = true
		}
	} else {
		if found := getExecBoolArg(authInfo, argIsLegacy); found {
			argIsLegacyConfigModeVal = true
		}
	}

	if o.isSet(flagAuthRecordCacheDir) || o.isSet(flagTokenCacheDir) {
		argAuthRecordCacheDirVal = o.TokenOptions.AuthRecordCacheDir
	} else {
		if val := getExecArg(authInfo, argAuthRecordCacheDir); val != "" {
			argAuthRecordCacheDirVal = val
		} else {
			argAuthRecordCacheDirVal = getExecArg(authInfo, argTokenCacheDir)
		}
	}

	if o.isSet(flagIsPoPTokenEnabled) {
		argIsPoPTokenEnabledVal = o.TokenOptions.IsPoPTokenEnabled
	} else {
		if found := getExecBoolArg(authInfo, argIsPoPTokenEnabled); found {
			argIsPoPTokenEnabledVal = true
		}
	}

	if o.isSet(flagPoPTokenClaims) {
		argPoPTokenClaimsVal = o.TokenOptions.PoPTokenClaims
	} else {
		argPoPTokenClaimsVal = getExecArg(authInfo, argPoPTokenClaims)
	}

	if o.isSet(flagRedirectURL) {
		argRedirectURLVal = o.TokenOptions.RedirectURL
	} else {
		argRedirectURLVal = getExecArg(authInfo, argRedirectURL)
	}

	if o.isSet(flagLoginHint) {
		argLoginHintVal = o.TokenOptions.LoginHint
	} else {
		argLoginHintVal = getExecArg(authInfo, argLoginHint)
	}

	return
}

func isLegacyAzureAuth(authInfoPtr *api.AuthInfo) (ok bool) {
	if authInfoPtr == nil {
		return
	}
	if authInfoPtr.AuthProvider == nil {
		return
	}
	return authInfoPtr.AuthProvider.Name == azureAuthProvider
}

func isExecUsingkubelogin(authInfoPtr *api.AuthInfo) (ok bool) {
	if authInfoPtr == nil {
		return
	}
	if authInfoPtr.Exec == nil {
		return
	}
	lowerc := strings.ToLower(authInfoPtr.Exec.Command)
	return strings.Contains(lowerc, "kubelogin")
}

func Convert(o Options, pathOptions *clientcmd.PathOptions) error {
	clientConfig := o.configFlags.ToRawKubeConfigLoader()
	var kubeconfigs []string

	klog.V(5).Info(o.ToString())

	if clientConfig.ConfigAccess() != nil {
		if clientConfig.ConfigAccess().GetExplicitFile() != "" {
			kubeconfigs = append(kubeconfigs, clientConfig.ConfigAccess().GetExplicitFile())
		} else {
			kubeconfigs = append(kubeconfigs, clientConfig.ConfigAccess().GetLoadingPrecedence()...)
		}
	}

	klog.V(5).Infof("Loading kubeconfig from %s", strings.Join(kubeconfigs, ":"))

	config, err := clientConfig.RawConfig()
	if err != nil {
		return fmt.Errorf("unable to load kubeconfig: %s", err)
	}

	targetAuthInfo := ""

	if o.context != "" {
		if config.Contexts[o.context] == nil {
			return fmt.Errorf("no context exists with the name: %q", o.context)
		}
		targetAuthInfo = config.Contexts[o.context].AuthInfo
	}

	for name, authInfo := range config.AuthInfos {

		if targetAuthInfo != "" && name != targetAuthInfo {
			continue
		}

		klog.V(5).Infof("context: %q", name)

		//  is it legacy aad auth or is it exec using kubelogin?
		if !isExecUsingkubelogin(authInfo) && !isLegacyAzureAuth(authInfo) {
			continue
		}

		klog.V(5).Info("converting...")

		argServerIDVal,
			argClientIDVal,
			argEnvironmentVal,
			argTenantIDVal,
			argAuthRecordCacheDirVal,
			argPoPTokenClaimsVal,
			argRedirectURLVal,
			argLoginHintVal,
			isLegacyConfigMode,
			isPoPTokenEnabled := getArgValues(o, authInfo)
		exec := &api.ExecConfig{
			Command: execName,
			Args: []string{
				getTokenCommand,
			},
			APIVersion:  execAPIVersion,
			InstallHint: execInstallHint,
		}

		// Preserve any existing install hint
		if authInfo.Exec != nil && authInfo.Exec.InstallHint != "" {
			exec.InstallHint = authInfo.Exec.InstallHint
		}

		exec.Args = append(exec.Args, argLoginMethod, o.TokenOptions.LoginMethod)

		// all login methods require --server-id specified
		if argServerIDVal == "" {
			return fmt.Errorf("%s is required", argServerID)
		}
		exec.Args = append(exec.Args, argServerID, argServerIDVal)

		if argAuthRecordCacheDirVal != "" {
			exec.Args = append(exec.Args, argAuthRecordCacheDir, argAuthRecordCacheDirVal)
		}

		switch o.TokenOptions.LoginMethod {
		case token.AzureDeveloperCLILogin:
			if o.isSet(flagTenantID) {
				exec.Args = append(exec.Args, argTenantID, o.TokenOptions.TenantID)
			}

		case token.AzureCLILogin:

			if o.azureConfigDir != "" {
				exec.Env = append(exec.Env, api.ExecEnvVar{Name: azureConfigDir, Value: o.azureConfigDir})
			}

			// when convert to azurecli login, tenantID from the input kubeconfig will be disregarded and
			// will have to come from explicit flag `--tenant-id`.
			// this is because azure cli logged in using MSI does not allow specifying tenant ID
			// see https://github.com/Azure/kubelogin/issues/123#issuecomment-1209652342
			if o.isSet(flagTenantID) {
				exec.Args = append(exec.Args, argTenantID, o.TokenOptions.TenantID)
			}

		case token.DeviceCodeLogin:

			if argClientIDVal == "" {
				return fmt.Errorf("%s is required", argClientID)
			}

			exec.Args = append(exec.Args, argClientID, argClientIDVal)

			if argTenantIDVal == "" {
				return fmt.Errorf("%s is required", argTenantID)
			}

			exec.Args = append(exec.Args, argTenantID, argTenantIDVal)

			if argEnvironmentVal != "" {
				// environment is optional
				exec.Args = append(exec.Args, argEnvironment, argEnvironmentVal)
			}

			if isLegacyConfigMode {
				exec.Args = append(exec.Args, argIsLegacy)
			}

		case token.InteractiveLogin:

			if argClientIDVal == "" {
				return fmt.Errorf("%s is required", argClientID)
			}

			exec.Args = append(exec.Args, argClientID, argClientIDVal)

			if argTenantIDVal == "" {
				return fmt.Errorf("%s is required", argTenantID)
			}

			exec.Args = append(exec.Args, argTenantID, argTenantIDVal)

			if argEnvironmentVal != "" {
				// environment is optional
				exec.Args = append(exec.Args, argEnvironment, argEnvironmentVal)
			}

			// PoP token flags are optional but must be provided together
			exec.Args, err = validatePoPClaims(exec.Args, isPoPTokenEnabled, argPoPTokenClaims, argPoPTokenClaimsVal)
			if err != nil {
				return err
			}

			if argRedirectURLVal != "" {
				exec.Args = append(exec.Args, argRedirectURL, argRedirectURLVal)
			}

			if argLoginHintVal != "" {
				exec.Args = append(exec.Args, argLoginHint, argLoginHintVal)
			}

		case token.ServicePrincipalLogin:

			if argClientIDVal == "" {
				return fmt.Errorf("%s is required", argClientID)
			}

			exec.Args = append(exec.Args, argClientID, argClientIDVal)

			if argTenantIDVal == "" {
				return fmt.Errorf("%s is required", argTenantID)
			}

			exec.Args = append(exec.Args, argTenantID, argTenantIDVal)

			if argEnvironmentVal != "" {
				// environment is optional
				exec.Args = append(exec.Args, argEnvironment, argEnvironmentVal)
			}

			if o.isSet(flagClientSecret) {
				exec.Args = append(exec.Args, argClientSecret, o.TokenOptions.ClientSecret)
			}

			if o.isSet(flagClientCert) {
				exec.Args = append(exec.Args, argClientCert, o.TokenOptions.ClientCert)
			}

			if o.isSet(flagClientCertPassword) {
				exec.Args = append(exec.Args, argClientCertPassword, o.TokenOptions.ClientCertPassword)
			}

			if isLegacyConfigMode {
				exec.Args = append(exec.Args, argIsLegacy)
			}

			// PoP token flags are optional but must be provided together
			exec.Args, err = validatePoPClaims(exec.Args, isPoPTokenEnabled, argPoPTokenClaims, argPoPTokenClaimsVal)
			if err != nil {
				return err
			}

			if o.isSet(flagDisableEnvironmentOverride) {
				exec.Args = append(exec.Args, argDisableEnvironmentOverride)
			}

		case token.MSILogin:

			if o.isSet(flagClientID) {
				exec.Args = append(exec.Args, argClientID, o.TokenOptions.ClientID)
			} else if o.isSet(flagIdentityResourceID) {
				exec.Args = append(exec.Args, argIdentityResourceID, o.TokenOptions.IdentityResourceID)
			}

		case token.ROPCLogin:

			if argClientIDVal == "" {
				return fmt.Errorf("%s is required", argClientID)
			}

			exec.Args = append(exec.Args, argClientID, argClientIDVal)

			if argTenantIDVal == "" {
				return fmt.Errorf("%s is required", argTenantID)
			}

			exec.Args = append(exec.Args, argTenantID, argTenantIDVal)

			if argEnvironmentVal != "" {
				// environment is optional
				exec.Args = append(exec.Args, argEnvironment, argEnvironmentVal)
			}

			if o.isSet(flagUsername) {
				exec.Args = append(exec.Args, argUsername, o.TokenOptions.Username)
			}

			if o.isSet(flagPassword) {
				exec.Args = append(exec.Args, argPassword, o.TokenOptions.Password)
			}

			if isLegacyConfigMode {
				exec.Args = append(exec.Args, argIsLegacy)
			}

			exec.Args, err = validatePoPClaims(exec.Args, isPoPTokenEnabled, argPoPTokenClaims, argPoPTokenClaimsVal)
			if err != nil {
				return err
			}

		case token.WorkloadIdentityLogin:

			if o.isSet(flagClientID) {
				exec.Args = append(exec.Args, argClientID, o.TokenOptions.ClientID)
			}

			if o.isSet(flagTenantID) {
				exec.Args = append(exec.Args, argTenantID, o.TokenOptions.TenantID)
			}

			if o.isSet(flagAuthorityHost) {
				exec.Args = append(exec.Args, argAuthorityHost, o.TokenOptions.AuthorityHost)
			}

			if o.isSet(flagFederatedTokenFile) {
				exec.Args = append(exec.Args, argFederatedTokenFile, o.TokenOptions.FederatedTokenFile)
			}

		case token.AzurePipelinesLogin:

			if argTenantIDVal == "" {
				return fmt.Errorf("%s is required", argTenantID)
			}

			exec.Args = append(exec.Args, argTenantID, argTenantIDVal)

			if o.isSet(flagAzurePipelinesServiceConnectionID) {
				exec.Args = append(exec.Args, argAzurePipelinesServiceConnectionID, o.TokenOptions.AzurePipelinesServiceConnectionID)
			}
		}

		authInfo.Exec = exec
		authInfo.AuthProvider = nil
	}
	err = clientcmd.ModifyConfig(pathOptions, config, true)
	return err
}

// get the item in Exec.Args[] right after someArg
func getExecArg(authInfoPtr *api.AuthInfo, someArg string) (resultStr string) {
	if someArg == "" {
		return
	}
	if authInfoPtr == nil || authInfoPtr.Exec == nil || authInfoPtr.Exec.Args == nil {
		return
	}
	if len(authInfoPtr.Exec.Args) < 1 {
		return
	}
	for i := range authInfoPtr.Exec.Args {
		if authInfoPtr.Exec.Args[i] == someArg {
			if len(authInfoPtr.Exec.Args) > i+1 {
				return authInfoPtr.Exec.Args[i+1]
			}
		}
	}
	return
}

func getExecBoolArg(authInfoPtr *api.AuthInfo, someArg string) bool {
	if someArg == "" {
		return false
	}
	if authInfoPtr == nil || authInfoPtr.Exec == nil || authInfoPtr.Exec.Args == nil {
		return false
	}
	if len(authInfoPtr.Exec.Args) < 1 {
		return false
	}
	for i := range authInfoPtr.Exec.Args {
		if authInfoPtr.Exec.Args[i] == someArg {
			return true
		}
	}
	return false
}

// If enabling PoP token support, users must provide both "--pop-enabled" and "--pop-claims" flags together.
// If either is provided without the other, validation should throw an error, otherwise the get-token command
// will fail under the hood.
func validatePoPClaims(args []string, isPopTokenEnabled bool, popTokenClaimsFlag, popTokenClaimsVal string) ([]string, error) {
	if isPopTokenEnabled && popTokenClaimsVal == "" {
		// pop-enabled and pop-claims must be provided together
		return args, fmt.Errorf("%s is required when specifying %s", argPoPTokenClaims, argIsPoPTokenEnabled)
	}

	if popTokenClaimsVal != "" && !isPopTokenEnabled {
		// pop-enabled and pop-claims must be provided together
		return args, fmt.Errorf("%s is required when specifying %s", argIsPoPTokenEnabled, argPoPTokenClaims)
	}

	if isPopTokenEnabled && popTokenClaimsVal != "" {
		args = append(args, argIsPoPTokenEnabled)
		args = append(args, popTokenClaimsFlag, popTokenClaimsVal)
	}

	return args, nil
}
0707010000005B000081A4000000000000000000000001691F8CFD0000C3C3000000000000000000000000000000000000003800000000kubelogin-0.2.13/pkg/internal/converter/convert_test.gopackage converter

import (
	"os"
	"path/filepath"
	"testing"

	"github.com/Azure/kubelogin/pkg/internal/token"
	"github.com/spf13/pflag"
	"k8s.io/cli-runtime/pkg/genericclioptions"
	"k8s.io/client-go/tools/clientcmd"
	clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
)

func TestConvert(t *testing.T) {
	const (
		clusterName1       = "aks1"
		clusterName2       = "aks2"
		envName            = "foo"
		serverID           = "serverID"
		clientID           = "clientID"
		spClientID         = "spClientID"
		tenantID           = "tenantID"
		clientSecret       = "foosecret"
		clientCert         = "/tmp/clientcert"
		clientCertPassword = "clientcertsecret"
		username           = "foo123"
		password           = "foobar"
		loginMethod        = "devicecode"
		identityResourceID = "/msi/resource/id"
		authorityHost      = "https://login.microsoftonline.com/"
		federatedTokenFile = "/tmp/file"
		authRecordCacheDir = "/tmp/token_dir"
		azureCLIDir        = "/tmp/foo"
		redirectURL        = "http://localhost:8000"
		usernameHint       = "username"
	)
	testData := []struct {
		name                string
		authProviderConfig  map[string]string
		overrideFlags       map[string]string
		expectedArgs        []string
		execArgItems        []string
		command             string
		expectedExecName    string
		installHint         string
		expectedInstallHint string
		expectedError       string
		expectedEnv         []clientcmdapi.ExecEnvVar
	}{
		{
			name: "non azure kubeconfig",
		},
		{
			name:             "non azure kubeconfig in exec format with install hint",
			command:          "foo",
			expectedExecName: "foo",
			execArgItems: []string{
				"--bar",
			},
			expectedArgs: []string{
				"--bar",
			},
			installHint:         "foo install hint",
			expectedInstallHint: "foo install hint",
		},
		{
			name: "using legacy azure auth to convert to msi",
			authProviderConfig: map[string]string{
				cfgEnvironment: envName,
				cfgApiserverID: serverID,
				cfgClientID:    clientID,
				cfgTenantID:    tenantID,
				cfgConfigMode:  "0",
			},
			overrideFlags: map[string]string{
				flagLoginMethod: token.MSILogin,
			},
			expectedArgs: []string{
				getTokenCommand,
				argServerID, serverID,
				argLoginMethod, token.MSILogin,
			},
		},
		{
			name: "using legacy azure auth to convert to msi will overwrite install hint",
			authProviderConfig: map[string]string{
				cfgEnvironment: envName,
				cfgApiserverID: serverID,
				cfgClientID:    clientID,
				cfgTenantID:    tenantID,
				cfgConfigMode:  "0",
			},
			overrideFlags: map[string]string{
				flagLoginMethod: token.MSILogin,
			},
			expectedArgs: []string{
				getTokenCommand,
				argServerID, serverID,
				argLoginMethod, token.MSILogin,
			},
			installHint: "Overwrite this install hint",
		},
		{
			name: "using legacy azure auth to convert to msi with client-id override",
			authProviderConfig: map[string]string{
				cfgEnvironment: envName,
				cfgApiserverID: serverID,
				cfgClientID:    clientID,
				cfgTenantID:    tenantID,
				cfgConfigMode:  "0",
			},
			overrideFlags: map[string]string{
				flagLoginMethod: token.MSILogin,
				flagClientID:    "msi-client-id",
			},
			expectedArgs: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, "msi-client-id",
				argLoginMethod, token.MSILogin,
			},
		},
		{
			name: "using legacy azure auth to convert to workload identity",
			authProviderConfig: map[string]string{
				cfgEnvironment: envName,
				cfgApiserverID: serverID,
				cfgClientID:    clientID,
				cfgTenantID:    tenantID,
				cfgConfigMode:  "0",
			},
			overrideFlags: map[string]string{
				flagLoginMethod: token.WorkloadIdentityLogin,
			},
			expectedArgs: []string{
				getTokenCommand,
				argServerID, serverID,
				argLoginMethod, token.WorkloadIdentityLogin,
			},
		},
		{
			name: "using legacy azure auth to convert to workload identity with overrides",
			authProviderConfig: map[string]string{
				cfgEnvironment: envName,
				cfgApiserverID: serverID,
				cfgClientID:    clientID,
				cfgTenantID:    tenantID,
				cfgConfigMode:  "0",
			},
			overrideFlags: map[string]string{
				flagLoginMethod:        token.WorkloadIdentityLogin,
				flagClientID:           spClientID,
				flagTenantID:           tenantID,
				flagAuthorityHost:      authorityHost,
				flagFederatedTokenFile: federatedTokenFile,
			},
			expectedArgs: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, spClientID,
				argTenantID, tenantID,
				argAuthorityHost, authorityHost,
				argFederatedTokenFile, federatedTokenFile,
				argLoginMethod, token.WorkloadIdentityLogin,
			},
		},
		{
			name: "using legacy azure auth to convert to spn without setting environment",
			authProviderConfig: map[string]string{
				cfgApiserverID: serverID,
				cfgClientID:    clientID,
				cfgTenantID:    tenantID,
				cfgConfigMode:  "1",
			},
			overrideFlags: map[string]string{
				flagLoginMethod: token.ServicePrincipalLogin,
				flagClientID:    spClientID,
			},
			expectedArgs: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, spClientID,
				argTenantID, tenantID,
				argLoginMethod, token.ServicePrincipalLogin,
			},
		},
		{
			name: "using legacy azure auth to convert to spn with clientSecret",
			authProviderConfig: map[string]string{
				cfgEnvironment: envName,
				cfgApiserverID: serverID,
				cfgClientID:    clientID,
				cfgTenantID:    tenantID,
				cfgConfigMode:  "1",
			},
			overrideFlags: map[string]string{
				flagLoginMethod:  token.ServicePrincipalLogin,
				flagClientID:     spClientID,
				flagClientSecret: clientSecret,
			},
			expectedArgs: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, spClientID,
				argClientSecret, clientSecret,
				argTenantID, tenantID,
				argEnvironment, envName,
				argLoginMethod, token.ServicePrincipalLogin,
			},
		},
		{
			name: "using legacy azure auth to convert to spn with clientCert",
			authProviderConfig: map[string]string{
				cfgEnvironment: envName,
				cfgApiserverID: serverID,
				cfgClientID:    clientID,
				cfgTenantID:    tenantID,
				cfgConfigMode:  "1",
			},
			overrideFlags: map[string]string{
				flagLoginMethod: token.ServicePrincipalLogin,
				flagClientID:    spClientID,
				flagClientCert:  clientCert,
			},
			expectedArgs: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, spClientID,
				argClientCert, clientCert,
				argTenantID, tenantID,
				argEnvironment, envName,
				argLoginMethod, token.ServicePrincipalLogin,
			},
		},
		{
			name: "using legacy azure auth to convert to spn with password-protected clientCert",
			authProviderConfig: map[string]string{
				cfgEnvironment: envName,
				cfgApiserverID: serverID,
				cfgClientID:    clientID,
				cfgTenantID:    tenantID,
				cfgConfigMode:  "1",
			},
			overrideFlags: map[string]string{
				flagLoginMethod:        token.ServicePrincipalLogin,
				flagClientID:           spClientID,
				flagClientCert:         clientCert,
				flagClientCertPassword: clientCertPassword,
			},
			expectedArgs: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, spClientID,
				argClientCert, clientCert,
				argClientCertPassword, clientCertPassword,
				argTenantID, tenantID,
				argEnvironment, envName,
				argLoginMethod, token.ServicePrincipalLogin,
			},
		},
		{
			name: "using legacy azure auth to convert to ropc",
			authProviderConfig: map[string]string{
				cfgEnvironment: envName,
				cfgApiserverID: serverID,
				cfgClientID:    clientID,
				cfgTenantID:    tenantID,
				cfgConfigMode:  "1",
			},
			overrideFlags: map[string]string{
				flagLoginMethod: token.ROPCLogin,
			},
			expectedArgs: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argEnvironment, envName,
				argLoginMethod, token.ROPCLogin,
			},
		},
		{
			name: "using legacy azure auth to convert to ropc with username and password",
			authProviderConfig: map[string]string{
				cfgEnvironment: envName,
				cfgApiserverID: serverID,
				cfgClientID:    clientID,
				cfgTenantID:    tenantID,
				cfgConfigMode:  "1",
			},
			overrideFlags: map[string]string{
				flagLoginMethod: token.ROPCLogin,
				flagUsername:    username,
				flagPassword:    password,
			},
			expectedArgs: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, clientID,
				argUsername, username,
				argPassword, password,
				argTenantID, tenantID,
				argEnvironment, envName,
				argLoginMethod, token.ROPCLogin,
			},
		},
		{
			name: "using legacy azure auth to convert to azurecli",
			authProviderConfig: map[string]string{
				cfgEnvironment: envName,
				cfgApiserverID: serverID,
				cfgClientID:    clientID,
				cfgTenantID:    tenantID,
				cfgConfigMode:  "1",
			},
			overrideFlags: map[string]string{
				flagLoginMethod: token.AzureCLILogin,
			},
			expectedArgs: []string{
				getTokenCommand,
				argServerID, serverID,
				argLoginMethod, token.AzureCLILogin,
			},
		},
		{
			name: "using legacy azure auth to convert to azd",
			authProviderConfig: map[string]string{
				cfgEnvironment: envName,
				cfgApiserverID: serverID,
				cfgClientID:    clientID,
				cfgTenantID:    tenantID,
				cfgConfigMode:  "1",
			},
			overrideFlags: map[string]string{
				flagLoginMethod: token.AzureDeveloperCLILogin,
			},
			expectedArgs: []string{
				getTokenCommand,
				argServerID, serverID,
				argLoginMethod, token.AzureDeveloperCLILogin,
			},
		},
		{
			name: "using legacy azure auth to convert to azurecli with --tenant-id override",
			authProviderConfig: map[string]string{
				cfgEnvironment: envName,
				cfgApiserverID: serverID,
				cfgClientID:    clientID,
				cfgTenantID:    tenantID,
				cfgConfigMode:  "1",
			},
			overrideFlags: map[string]string{
				flagLoginMethod: token.AzureCLILogin,
				flagTenantID:    tenantID,
			},
			expectedArgs: []string{
				getTokenCommand,
				argServerID, serverID,
				argLoginMethod, token.AzureCLILogin,
				argTenantID, tenantID,
			},
		},
		{
			name: "using legacy azure auth to convert to azd with --tenant-id override",
			authProviderConfig: map[string]string{
				cfgEnvironment: envName,
				cfgApiserverID: serverID,
				cfgClientID:    clientID,
				cfgTenantID:    tenantID,
				cfgConfigMode:  "1",
			},
			overrideFlags: map[string]string{
				flagLoginMethod: token.AzureDeveloperCLILogin,
				flagTenantID:    tenantID,
			},
			expectedArgs: []string{
				getTokenCommand,
				argServerID, serverID,
				argLoginMethod, token.AzureDeveloperCLILogin,
				argTenantID, tenantID,
			},
		},
		{
			name: "using legacy azure auth to convert to azurecli with --token-cache-dir override",
			authProviderConfig: map[string]string{
				cfgEnvironment: envName,
				cfgApiserverID: serverID,
				cfgClientID:    clientID,
				cfgTenantID:    tenantID,
				cfgConfigMode:  "1",
			},
			overrideFlags: map[string]string{
				flagLoginMethod:   token.AzureCLILogin,
				flagTokenCacheDir: authRecordCacheDir,
			},
			expectedArgs: []string{
				getTokenCommand,
				argServerID, serverID,
				argLoginMethod, token.AzureCLILogin,
				argAuthRecordCacheDir, authRecordCacheDir,
			},
		},
		{
			name: "using legacy azure auth to convert to devicecode with redundant arguments",
			authProviderConfig: map[string]string{
				cfgEnvironment: envName,
				cfgApiserverID: serverID,
				cfgClientID:    clientID,
				cfgTenantID:    tenantID,
				cfgConfigMode:  "0",
			},
			overrideFlags: map[string]string{
				flagEnvironment:        envName,
				flagServerID:           serverID,
				flagClientID:           clientID,
				flagTenantID:           tenantID,
				flagClientSecret:       clientSecret,
				flagClientCert:         clientCert,
				flagClientCertPassword: clientCertPassword,
				flagUsername:           username,
				flagPassword:           password,
				flagLoginMethod:        loginMethod,
			},
			expectedArgs: []string{
				getTokenCommand,
				argEnvironment, envName,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argIsLegacy,
				argLoginMethod, loginMethod,
			},
		},
		{
			name: "using legacy azure auth with configMode: \"1\" to convert to devicecode with --legacy",
			authProviderConfig: map[string]string{
				cfgConfigMode: "1",
			},
			overrideFlags: map[string]string{
				flagEnvironment: envName,
				flagServerID:    serverID,
				flagClientID:    clientID,
				flagTenantID:    tenantID,
				flagLoginMethod: loginMethod,
				flagIsLegacy:    "true",
			},
			expectedArgs: []string{
				getTokenCommand,
				argEnvironment, envName,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argIsLegacy,
				argLoginMethod, loginMethod,
			},
		},
		{
			name: "using legacy azure auth to convert without --login should default to devicecode",
			authProviderConfig: map[string]string{
				cfgEnvironment: envName,
				cfgApiserverID: serverID,
				cfgClientID:    clientID,
				cfgTenantID:    tenantID,
			},
			expectedArgs: []string{
				getTokenCommand,
				argEnvironment, envName,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argIsLegacy,
				argLoginMethod, token.DeviceCodeLogin,
			},
		},
		{
			name: "using legacy azure auth with configMode: \"0\" to convert without --login should default to devicecode",
			authProviderConfig: map[string]string{
				cfgEnvironment: envName,
				cfgApiserverID: serverID,
				cfgClientID:    clientID,
				cfgTenantID:    tenantID,
				cfgConfigMode:  "0",
			},
			expectedArgs: []string{
				getTokenCommand,
				argEnvironment, envName,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argIsLegacy,
				argLoginMethod, token.DeviceCodeLogin,
			},
		},
		{
			name: "using legacy azure auth with configMode: \"1\" to convert without --login should result in devicecode without --legacy",
			authProviderConfig: map[string]string{
				cfgEnvironment: envName,
				cfgApiserverID: serverID,
				cfgClientID:    clientID,
				cfgTenantID:    tenantID,
				cfgConfigMode:  "1",
			},
			expectedArgs: []string{
				getTokenCommand,
				argEnvironment, envName,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argLoginMethod, token.DeviceCodeLogin,
			},
		},
		{
			name: "with exec format kubeconfig, convert from azurecli to azurecli",
			execArgItems: []string{
				getTokenCommand,
				argEnvironment, envName,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argLoginMethod, token.AzureCLILogin,
			},
			overrideFlags: map[string]string{
				flagLoginMethod: token.AzureCLILogin,
			},
			expectedArgs: []string{
				getTokenCommand,
				argServerID, serverID,
				argLoginMethod, token.AzureCLILogin,
			},
			command: execName,
		},
		{
			name: "with exec format kubeconfig, convert from azurecli to azurecli with existing install hint",
			execArgItems: []string{
				getTokenCommand,
				argEnvironment, envName,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argLoginMethod, token.AzureCLILogin,
			},
			overrideFlags: map[string]string{
				flagLoginMethod: token.AzureCLILogin,
			},
			expectedArgs: []string{
				getTokenCommand,
				argServerID, serverID,
				argLoginMethod, token.AzureCLILogin,
			},
			command:             execName,
			installHint:         "Preserve this install hint",
			expectedInstallHint: "Preserve this install hint",
		},
		{
			name: "with exec format kubeconfig, convert from azurecli to azurecli with --tenant-id",
			execArgItems: []string{
				getTokenCommand,
				argEnvironment, envName,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argLoginMethod, token.AzureCLILogin,
			},
			overrideFlags: map[string]string{
				flagLoginMethod: token.AzureCLILogin,
				flagTenantID:    tenantID,
			},
			expectedArgs: []string{
				getTokenCommand,
				argServerID, serverID,
				argLoginMethod, token.AzureCLILogin,
				argTenantID, tenantID,
			},
			command: execName,
		},
		{
			name: "with exec format kubeconfig, convert from azurecli to azurecli, with envName as overrides",
			execArgItems: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argLoginMethod, token.AzureCLILogin,
			},
			overrideFlags: map[string]string{
				flagLoginMethod: token.AzureCLILogin,
				flagEnvironment: envName,
			},
			expectedArgs: []string{
				getTokenCommand,
				argServerID, serverID,
				argLoginMethod, token.AzureCLILogin,
			},
			command: execName,
		},
		{
			name: "with exec format kubeconfig, convert from azurecli to azurecli, with args as overrides",
			execArgItems: []string{
				getTokenCommand,
			},
			overrideFlags: map[string]string{
				flagLoginMethod: token.AzureCLILogin,
				flagServerID:    serverID,
				flagClientID:    clientID,
				flagTenantID:    tenantID,
				flagEnvironment: envName,
			},
			expectedArgs: []string{
				getTokenCommand,
				argServerID, serverID,
				argLoginMethod, token.AzureCLILogin,
				argTenantID, tenantID,
			},
			command: execName,
		},
		{
			name: "with exec format kubeconfig, convert from azurecli to devicecode",
			execArgItems: []string{
				getTokenCommand,
				argServerID, serverID,
				argLoginMethod, token.AzureCLILogin,
			},
			overrideFlags: map[string]string{
				flagClientID:    clientID,
				flagTenantID:    tenantID,
				flagLoginMethod: token.DeviceCodeLogin,
			},
			expectedArgs: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argLoginMethod, token.DeviceCodeLogin,
			},
			command: execName,
		},
		{
			name: "with exec format kubeconfig, convert from azurecli to devicecode with existing install hint",
			execArgItems: []string{
				getTokenCommand,
				argServerID, serverID,
				argLoginMethod, token.AzureCLILogin,
			},
			overrideFlags: map[string]string{
				flagClientID:    clientID,
				flagTenantID:    tenantID,
				flagLoginMethod: token.DeviceCodeLogin,
			},
			expectedArgs: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argLoginMethod, token.DeviceCodeLogin,
			},
			command:             execName,
			installHint:         "Preserve this install hint",
			expectedInstallHint: "Preserve this install hint",
		},
		{
			name: "with exec format kubeconfig, convert from azurecli to devicecode, with args as overrides",
			execArgItems: []string{
				getTokenCommand,
				argLoginMethod, token.AzureCLILogin,
			},
			overrideFlags: map[string]string{
				flagLoginMethod: token.DeviceCodeLogin,
				flagServerID:    serverID,
				flagClientID:    clientID,
				flagTenantID:    tenantID,
				flagEnvironment: envName,
			},
			expectedArgs: []string{
				getTokenCommand,
				argEnvironment, envName,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argLoginMethod, token.DeviceCodeLogin,
			},
			command: execName,
		},
		{
			name: "with exec format kubeconfig, convert from devicecode to devicecode without override",
			execArgItems: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argEnvironment, envName,
				argLoginMethod, token.DeviceCodeLogin,
			},
			expectedArgs: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argEnvironment, envName,
				argLoginMethod, token.DeviceCodeLogin,
			},
			command: execName,
		},
		{
			name: "with exec format kubeconfig, convert from devicecode to devicecode with --legacy",
			execArgItems: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argEnvironment, envName,
				argLoginMethod, token.DeviceCodeLogin,
			},
			overrideFlags: map[string]string{
				flagIsLegacy: "true",
			},
			expectedArgs: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argEnvironment, envName,
				argIsLegacy,
				argLoginMethod, token.DeviceCodeLogin,
			},
			command: execName,
		},
		{
			name: "with exec format kubeconfig using devicecode and --legacy, convert to devicecode should still have --legacy",
			execArgItems: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argEnvironment, envName,
				argLoginMethod, token.DeviceCodeLogin,
				argIsLegacy,
			},
			overrideFlags: map[string]string{
				flagLoginMethod: token.DeviceCodeLogin,
			},
			expectedArgs: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argEnvironment, envName,
				argIsLegacy,
				argLoginMethod, token.DeviceCodeLogin,
			},
			command: execName,
		},
		{
			name: "with exec format kubeconfig, convert from devicecode to azurecli",
			execArgItems: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argEnvironment, envName,
				argLoginMethod, token.DeviceCodeLogin,
			},
			overrideFlags: map[string]string{
				flagLoginMethod: token.AzureCLILogin,
			},
			expectedArgs: []string{
				getTokenCommand,
				argServerID, serverID,
				argLoginMethod, token.AzureCLILogin,
			},
			command: execName,
		},
		{
			name: "with exec format kubeconfig, convert from devicecode to azurecli with --token-cache-dir override",
			execArgItems: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argEnvironment, envName,
				argLoginMethod, token.DeviceCodeLogin,
			},
			overrideFlags: map[string]string{
				flagLoginMethod:   token.AzureCLILogin,
				flagTokenCacheDir: authRecordCacheDir,
			},
			expectedArgs: []string{
				getTokenCommand,
				argServerID, serverID,
				argLoginMethod, token.AzureCLILogin,
				argAuthRecordCacheDir, authRecordCacheDir,
			},
			command: execName,
		},
		{
			name: "with exec format kubeconfig, convert from devicecode to azurecli with --cache-dir",
			execArgItems: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argEnvironment, envName,
				argLoginMethod, token.DeviceCodeLogin,
				argAuthRecordCacheDir, authRecordCacheDir,
			},
			overrideFlags: map[string]string{
				flagLoginMethod: token.AzureCLILogin,
			},
			expectedArgs: []string{
				getTokenCommand,
				argServerID, serverID,
				argLoginMethod, token.AzureCLILogin,
				argAuthRecordCacheDir, authRecordCacheDir,
			},
			command: execName,
		},
		{
			name: "with exec format kubeconfig, convert from devicecode to azurecli with --cache-dir override",
			execArgItems: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argEnvironment, envName,
				argLoginMethod, token.DeviceCodeLogin,
			},
			overrideFlags: map[string]string{
				flagLoginMethod:        token.AzureCLILogin,
				flagAuthRecordCacheDir: authRecordCacheDir,
			},
			expectedArgs: []string{
				getTokenCommand,
				argServerID, serverID,
				argLoginMethod, token.AzureCLILogin,
				argAuthRecordCacheDir, authRecordCacheDir,
			},
			command: execName,
		},
		{
			name: "with exec format kubeconfig already having --token-cache-dir, convert from devicecode to azurecli",
			execArgItems: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argTokenCacheDir, authRecordCacheDir,
				argEnvironment, envName,
				argLoginMethod, token.DeviceCodeLogin,
			},
			overrideFlags: map[string]string{
				flagLoginMethod: token.AzureCLILogin,
			},
			expectedArgs: []string{
				getTokenCommand,
				argServerID, serverID,
				argLoginMethod, token.AzureCLILogin,
				argAuthRecordCacheDir, authRecordCacheDir,
			},
			command: execName,
		},
		{
			name: "with exec format kubeconfig, convert from devicecode to spn",
			execArgItems: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argEnvironment, envName,
				argLoginMethod, token.DeviceCodeLogin,
			},
			overrideFlags: map[string]string{
				flagLoginMethod: token.ServicePrincipalLogin,
			},
			expectedArgs: []string{
				getTokenCommand,
				argEnvironment, envName,
				argServerID, serverID,
				argTenantID, tenantID,
				argClientID, clientID,
				argLoginMethod, token.ServicePrincipalLogin,
			},
			command: execName,
		},
		{
			name: "with exec format kubeconfig, convert from devicecode to spn without setting environment",
			execArgItems: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argLoginMethod, token.DeviceCodeLogin,
			},
			overrideFlags: map[string]string{
				flagLoginMethod: token.ServicePrincipalLogin,
			},
			expectedArgs: []string{
				getTokenCommand,
				argServerID, serverID,
				argTenantID, tenantID,
				argClientID, clientID,
				argLoginMethod, token.ServicePrincipalLogin,
			},
			command: execName,
		},
		{
			name: "with exec format kubeconfig, convert from devicecode to spn with clientID",
			execArgItems: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argEnvironment, envName,
				argLoginMethod, token.DeviceCodeLogin,
			},
			overrideFlags: map[string]string{
				flagLoginMethod: token.ServicePrincipalLogin,
				flagClientID:    spClientID,
			},
			expectedArgs: []string{
				getTokenCommand,
				argEnvironment, envName,
				argServerID, serverID,
				argClientID, spClientID,
				argTenantID, tenantID,
				argLoginMethod, token.ServicePrincipalLogin,
			},
			command: execName,
		},
		{
			name: "with exec format kubeconfig, convert from devicecode to spn with --legacy",
			execArgItems: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argEnvironment, envName,
				argLoginMethod, token.DeviceCodeLogin,
			},
			overrideFlags: map[string]string{
				flagLoginMethod: token.ServicePrincipalLogin,
				flagClientID:    spClientID,
				flagIsLegacy:    "true",
			},
			expectedArgs: []string{
				getTokenCommand,
				argEnvironment, envName,
				argServerID, serverID,
				argClientID, spClientID,
				argTenantID, tenantID,
				argIsLegacy,
				argLoginMethod, token.ServicePrincipalLogin,
			},
			command: execName,
		},
		{
			name: "with exec format kubeconfig, convert from devicecode to msi",
			execArgItems: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argEnvironment, envName,
				argLoginMethod, token.DeviceCodeLogin,
			},
			overrideFlags: map[string]string{
				flagLoginMethod: token.MSILogin,
			},
			expectedArgs: []string{
				getTokenCommand,
				argServerID, serverID,
				argLoginMethod, token.MSILogin,
			},
			command: execName,
		},
		{
			name: "with exec format kubeconfig, convert from devicecode to msi with clientID override",
			execArgItems: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argEnvironment, envName,
				argLoginMethod, token.DeviceCodeLogin,
			},
			overrideFlags: map[string]string{
				flagLoginMethod: token.MSILogin,
				flagClientID:    spClientID,
			},
			expectedArgs: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, spClientID,
				argLoginMethod, token.MSILogin,
			},
			command: execName,
		},
		{
			name: "with exec format kubeconfig, convert from devicecode to msi with identity-resource-id override",
			execArgItems: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argEnvironment, envName,
				argLoginMethod, token.DeviceCodeLogin,
			},
			overrideFlags: map[string]string{
				flagLoginMethod:        token.MSILogin,
				flagIdentityResourceID: identityResourceID,
			},
			expectedArgs: []string{
				getTokenCommand,
				argServerID, serverID,
				argIdentityResourceID, identityResourceID,
				argLoginMethod, token.MSILogin,
			},
			command: execName,
		},
		{
			name: "with exec format kubeconfig, convert from devicecode to ropc",
			execArgItems: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argEnvironment, envName,
				argLoginMethod, token.DeviceCodeLogin,
			},
			overrideFlags: map[string]string{
				flagLoginMethod: token.ROPCLogin,
			},
			expectedArgs: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argEnvironment, envName,
				argLoginMethod, token.ROPCLogin,
			},
			command: execName,
		},
		{
			name: "with exec format kubeconfig, convert from devicecode to ropc with --legacy",
			execArgItems: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argEnvironment, envName,
				argLoginMethod, token.DeviceCodeLogin,
			},
			overrideFlags: map[string]string{
				flagLoginMethod: token.ROPCLogin,
				flagIsLegacy:    "true",
			},
			expectedArgs: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argEnvironment, envName,
				argIsLegacy,
				argLoginMethod, token.ROPCLogin,
			},
			command: execName,
		},
		{
			name: "with exec format kubeconfig, convert from devicecode to ropc with username and password",
			execArgItems: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argEnvironment, envName,
				argLoginMethod, token.DeviceCodeLogin,
			},
			overrideFlags: map[string]string{
				flagLoginMethod: token.ROPCLogin,
				flagUsername:    username,
				flagPassword:    password,
			},
			expectedArgs: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, clientID,
				argUsername, username,
				argPassword, password,
				argTenantID, tenantID,
				argEnvironment, envName,
				argLoginMethod, token.ROPCLogin,
			},
			command: execName,
		},
		{
			name: "with exec format kubeconfig, convert from devicecode to workload identity",
			execArgItems: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argEnvironment, envName,
				argLoginMethod, token.DeviceCodeLogin,
			},
			overrideFlags: map[string]string{
				flagLoginMethod: token.WorkloadIdentityLogin,
			},
			expectedArgs: []string{
				getTokenCommand,
				argServerID, serverID,
				argLoginMethod, token.WorkloadIdentityLogin,
			},
			command: execName,
		},
		{
			name: "with exec format kubeconfig, convert from devicecode to workload identity with override",
			execArgItems: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argEnvironment, envName,
				argLoginMethod, token.DeviceCodeLogin,
			},
			overrideFlags: map[string]string{
				flagLoginMethod:        token.WorkloadIdentityLogin,
				flagClientID:           spClientID,
				flagTenantID:           tenantID,
				flagAuthorityHost:      authorityHost,
				flagFederatedTokenFile: federatedTokenFile,
			},
			expectedArgs: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, spClientID,
				argTenantID, tenantID,
				argAuthorityHost, authorityHost,
				argFederatedTokenFile, federatedTokenFile,
				argLoginMethod, token.WorkloadIdentityLogin,
			},
			command: execName,
		},
		{
			name: "with exec format kubeconfig, convert from devicecode to interactive",
			execArgItems: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argEnvironment, envName,
				argLoginMethod, token.DeviceCodeLogin,
			},
			overrideFlags: map[string]string{
				flagLoginMethod: token.InteractiveLogin,
			},
			expectedArgs: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argEnvironment, envName,
				argLoginMethod, token.InteractiveLogin,
			},
			command: execName,
		},
		{
			name: "with exec format kubeconfig, convert from devicecode to interactive without setting environment",
			execArgItems: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argLoginMethod, token.DeviceCodeLogin,
			},
			overrideFlags: map[string]string{
				flagLoginMethod: token.InteractiveLogin,
			},
			expectedArgs: []string{
				getTokenCommand,
				argServerID, serverID,
				argTenantID, tenantID,
				argClientID, clientID,
				argLoginMethod, token.InteractiveLogin,
			},
			command: execName,
		},
		{
			name: "with exec format kubeconfig, convert from devicecode to interactive with override",
			execArgItems: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argEnvironment, envName,
				argLoginMethod, token.DeviceCodeLogin,
			},
			overrideFlags: map[string]string{
				flagLoginMethod: token.InteractiveLogin,
				flagServerID:    serverID,
				flagClientID:    clientID,
				flagTenantID:    tenantID,
				flagEnvironment: envName,
			},
			expectedArgs: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argEnvironment, envName,
				argLoginMethod, token.InteractiveLogin,
			},
			command: execName,
		},
		{
			name: "with exec format kubeconfig, convert from devicecode to interactive with redirect url override",
			execArgItems: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argEnvironment, envName,
				argLoginMethod, token.DeviceCodeLogin,
			},
			overrideFlags: map[string]string{
				flagLoginMethod: token.InteractiveLogin,
				flagServerID:    serverID,
				flagClientID:    clientID,
				flagTenantID:    tenantID,
				flagEnvironment: envName,
				flagRedirectURL: redirectURL,
			},
			expectedArgs: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argEnvironment, envName,
				argLoginMethod, token.InteractiveLogin,
				argRedirectURL, redirectURL,
			},
			command: execName,
		},
		{
			name: "with exec format kubeconfig, convert from devicecode to interactive with login hint override",
			execArgItems: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argEnvironment, envName,
				argLoginMethod, token.DeviceCodeLogin,
			},
			overrideFlags: map[string]string{
				flagLoginMethod: token.InteractiveLogin,
				flagServerID:    serverID,
				flagClientID:    clientID,
				flagTenantID:    tenantID,
				flagEnvironment: envName,
				flagLoginHint:   usernameHint,
			},
			expectedArgs: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argEnvironment, envName,
				argLoginMethod, token.InteractiveLogin,
				argLoginHint, usernameHint,
			},
			command: execName,
		},
		{
			name: "convert with context specified, auth info not specified by the context should not be changed",
			authProviderConfig: map[string]string{
				cfgEnvironment: envName,
				cfgApiserverID: serverID,
				cfgClientID:    clientID,
				cfgTenantID:    tenantID,
				cfgConfigMode:  "0",
			},
			overrideFlags: map[string]string{
				flagLoginMethod: token.MSILogin,
				flagContext:     clusterName1,
			},
			expectedArgs: []string{
				getTokenCommand,
				argServerID, serverID,
				argLoginMethod, token.MSILogin,
			},
		},
		{
			name: "convert with non-existent context specified, Convert should return error",
			authProviderConfig: map[string]string{
				cfgEnvironment: envName,
				cfgApiserverID: serverID,
				cfgClientID:    clientID,
				cfgTenantID:    tenantID,
				cfgConfigMode:  "0",
			},
			overrideFlags: map[string]string{
				flagLoginMethod: token.MSILogin,
				flagContext:     "badContext",
			},
			expectedError: "no context exists with the name: \"badContext\"",
		},
		{
			name: "with --azure-config-dir specified, exec.Env should be set accordingly",
			authProviderConfig: map[string]string{
				cfgEnvironment: envName,
				cfgApiserverID: serverID,
				cfgClientID:    clientID,
				cfgTenantID:    tenantID,
				cfgConfigMode:  "0",
			},
			overrideFlags: map[string]string{
				flagLoginMethod:    token.AzureCLILogin,
				flagContext:        clusterName1,
				flagAzureConfigDir: azureCLIDir,
			},
			expectedArgs: []string{
				getTokenCommand,
				argServerID, serverID,
				argLoginMethod, token.AzureCLILogin,
			},
			expectedEnv: []clientcmdapi.ExecEnvVar{
				{
					Name:  azureConfigDir,
					Value: azureCLIDir,
				},
			},
		},
		{
			name: "with exec format kubeconfig, convert from devicecode to interactive with only pop-enabled specified, Convert should return error",
			execArgItems: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argEnvironment, envName,
				argLoginMethod, token.DeviceCodeLogin,
				argIsPoPTokenEnabled,
			},
			overrideFlags: map[string]string{
				flagLoginMethod: token.InteractiveLogin,
			},
			command:       execName,
			expectedError: "--pop-claims is required when specifying --pop-enabled",
		},
		{
			name: "with exec format kubeconfig, convert from devicecode to interactive with only pop-claims specified, Convert should return error",
			execArgItems: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argEnvironment, envName,
				argLoginMethod, token.DeviceCodeLogin,
				argPoPTokenClaims, "u=testhost",
			},
			overrideFlags: map[string]string{
				flagLoginMethod: token.InteractiveLogin,
			},
			command:       execName,
			expectedError: "--pop-enabled is required when specifying --pop-claims",
		},
		{
			name: "with exec format kubeconfig, convert from devicecode to interactive with pop-enabled and pop-claims",
			execArgItems: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argEnvironment, envName,
				argLoginMethod, token.DeviceCodeLogin,
				argIsPoPTokenEnabled,
				argPoPTokenClaims, "u=testhost, 1=2",
			},
			overrideFlags: map[string]string{
				flagLoginMethod: token.InteractiveLogin,
			},
			expectedArgs: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argEnvironment, envName,
				argLoginMethod, token.InteractiveLogin,
				argIsPoPTokenEnabled,
				argPoPTokenClaims, "u=testhost, 1=2",
			},
			command: execName,
		},
		{
			name: "with exec format kubeconfig, convert from devicecode to ropc with only pop-enabled specified, Convert should return error",
			execArgItems: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argEnvironment, envName,
				argLoginMethod, token.DeviceCodeLogin,
				argIsPoPTokenEnabled,
			},
			overrideFlags: map[string]string{
				flagLoginMethod: token.ROPCLogin,
			},
			command:       execName,
			expectedError: "--pop-claims is required when specifying --pop-enabled",
		},
		{
			name: "with exec format kubeconfig, convert from devicecode to ropc with only pop-claims specified, Convert should return error",
			execArgItems: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argEnvironment, envName,
				argLoginMethod, token.DeviceCodeLogin,
				argPoPTokenClaims, "u=testhost",
			},
			overrideFlags: map[string]string{
				flagLoginMethod: token.ROPCLogin,
			},
			command:       execName,
			expectedError: "--pop-enabled is required when specifying --pop-claims",
		},
		{
			name: "with exec format kubeconfig, convert from devicecode to ropc with pop-enabled and pop-claims as flags",
			execArgItems: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argEnvironment, envName,
				argLoginMethod, token.DeviceCodeLogin,
			},
			overrideFlags: map[string]string{
				flagLoginMethod:       token.ROPCLogin,
				flagIsPoPTokenEnabled: "true",
				flagPoPTokenClaims:    "u=testhost, 1=2",
			},
			expectedArgs: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argEnvironment, envName,
				argLoginMethod, token.ROPCLogin,
				argIsPoPTokenEnabled,
				argPoPTokenClaims, "u=testhost, 1=2",
			},
			command: execName,
		},
		{
			name: "with exec format kubeconfig, convert from devicecode to spn with pop-enabled and pop-claims as flags",
			execArgItems: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argEnvironment, envName,
				argLoginMethod, token.DeviceCodeLogin,
			},
			overrideFlags: map[string]string{
				flagLoginMethod:       token.ServicePrincipalLogin,
				flagIsPoPTokenEnabled: "true",
				flagPoPTokenClaims:    "u=testhost, 1=2",
			},
			expectedArgs: []string{
				getTokenCommand,
				argEnvironment, envName,
				argServerID, serverID,
				argTenantID, tenantID,
				argClientID, clientID,
				argLoginMethod, token.ServicePrincipalLogin,
				argIsPoPTokenEnabled,
				argPoPTokenClaims, "u=testhost, 1=2",
			},
			command: execName,
		},
		{
			name: "with exec format kubeconfig, convert from azurecli to devicecode with pop-enabled and pop-claims, expect pop args to be ignored",
			execArgItems: []string{
				getTokenCommand,
				argServerID, serverID,
				argLoginMethod, token.AzureCLILogin,
				argIsPoPTokenEnabled,
				argPoPTokenClaims, "u=testhost, 1=2",
			},
			overrideFlags: map[string]string{
				flagClientID:    clientID,
				flagTenantID:    tenantID,
				flagLoginMethod: token.DeviceCodeLogin,
			},
			expectedArgs: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argLoginMethod, token.DeviceCodeLogin,
			},
			command: execName,
		},
		{
			name: "test with exec format kubeconfig, convert from devicecode to spn with environment override flag disabled.",
			execArgItems: []string{
				getTokenCommand,
				argServerID, serverID,
				argClientID, clientID,
				argTenantID, tenantID,
				argEnvironment, envName,
				argLoginMethod, token.DeviceCodeLogin,
			},
			overrideFlags: map[string]string{
				flagLoginMethod:                token.ServicePrincipalLogin,
				flagDisableEnvironmentOverride: "true",
			},
			expectedArgs: []string{
				getTokenCommand,
				argEnvironment, envName,
				argServerID, serverID,
				argTenantID, tenantID,
				argClientID, clientID,
				argLoginMethod, token.ServicePrincipalLogin,
				argDisableEnvironmentOverride,
			},
			command: execName,
		},
	}
	rootTmpDir, err := os.MkdirTemp("", "kubelogin-test")
	if err != nil {
		t.Fatalf("unable to create temp dir: %s", err)
	}
	defer os.RemoveAll(rootTmpDir)
	for _, data := range testData {
		t.Run(data.name, func(t *testing.T) {
			var authProviderName string
			tmpDir, err := os.MkdirTemp(rootTmpDir, "config")
			if err != nil {
				t.Fatalf("%s", err)
			}
			if data.expectedArgs != nil {
				authProviderName = azureAuthProvider
			}
			kubeconfigFile := filepath.Join(tmpDir, "config")

			config := createValidTestConfigs(
				clusterName1,
				clusterName2,
				data.command,
				authProviderName,
				data.authProviderConfig,
				data.execArgItems,
				data.installHint,
			)
			fs := &pflag.FlagSet{}
			o := Options{
				Flags: fs,
				configFlags: genericclioptions.NewTestConfigFlags().
					WithClientConfig(clientcmd.NewNonInteractiveClientConfig(*config, clusterName1, &clientcmd.ConfigOverrides{}, nil)),
			}
			o.AddFlags(fs)

			for k, v := range data.overrideFlags {
				if err := o.setFlag(k, v); err != nil {
					t.Fatalf("unable to add flag: %s, err: %s", k, err)
				}
			}

			pathOptions := clientcmd.PathOptions{
				ExplicitFileFlag: "kubeconfig",
				LoadingRules: &clientcmd.ClientConfigLoadingRules{
					ExplicitPath: kubeconfigFile,
				},
			}
			err = Convert(o, &pathOptions)
			if data.expectedError == "" && err != nil {
				t.Fatalf("Unexpected error from Convert: %v", err)
			} else if data.expectedError != "" {
				if err == nil || err.Error() != data.expectedError {
					t.Fatalf("Expected error: %q, but got: %q", data.expectedError, err)
				}
			} else {
				// only need to validate fields if we're not expecting an error
				if o.context != "" {
					// when --context is specified, convert-kubeconfig will convert only the targeted context
					// hence, we expect the second auth info not to change
					validate(t, clusterName1, config.AuthInfos[clusterName1], data.expectedArgs, data.expectedExecName, data.expectedInstallHint, data.expectedEnv)
					validateAuthInfoThatShouldNotChange(t, clusterName2, config.AuthInfos[clusterName2], data.authProviderConfig)
				} else {
					// when --context is not specified, convert-kubeconfig will convert every auth info in the kubeconfig
					// hence, we expect the second auth info to be converted in the same way as the first one
					validate(t, clusterName1, config.AuthInfos[clusterName1], data.expectedArgs, data.expectedExecName, data.expectedInstallHint, data.expectedEnv)
					validate(t, clusterName2, config.AuthInfos[clusterName2], data.expectedArgs, data.expectedExecName, data.expectedInstallHint, data.expectedEnv)
				}
			}
		})
	}
}

func createValidTestConfigs(
	name1, name2, commandName, authProviderName string,
	authProviderConfig map[string]string,
	execArgItems []string,
	installHint string,
) *clientcmdapi.Config {
	const server = "https://anything.com:8080"

	config := clientcmdapi.NewConfig()
	for _, name := range []string{name1, name2} {
		config.Clusters[name] = &clientcmdapi.Cluster{
			Server: server,
		}

		if authProviderConfig == nil && execArgItems != nil {
			config.AuthInfos[name] = &clientcmdapi.AuthInfo{
				Exec: &clientcmdapi.ExecConfig{
					Args:        execArgItems,
					Command:     commandName,
					InstallHint: installHint,
				},
			}
		} else {
			config.AuthInfos[name] = &clientcmdapi.AuthInfo{
				AuthProvider: &clientcmdapi.AuthProviderConfig{
					Name:   authProviderName,
					Config: authProviderConfig,
				},
			}
		}

		config.Contexts[name] = &clientcmdapi.Context{
			Cluster:  name,
			AuthInfo: name,
		}
	}
	config.CurrentContext = name1

	return config
}

func validate(
	t *testing.T,
	clusterName string,
	authInfo *clientcmdapi.AuthInfo,
	expectedArgs []string,
	expectedExecName string,
	expectedInstallHint string,
	expectedEnv []clientcmdapi.ExecEnvVar,
) {
	if expectedArgs == nil {
		if authInfo.AuthProvider == nil {
			t.Fatalf("[context:%s]: %s", clusterName, "auth provider should not be reset")
		}
		if authInfo.Exec != nil {
			t.Fatalf("[context:%s]: %s", clusterName, "plugin should not be set")
		}
		return
	}

	if authInfo.AuthProvider != nil {
		t.Fatalf("[context:%s]: %s", clusterName, "auth provider should be reset")
	}
	exec := authInfo.Exec
	if exec == nil {
		t.Fatalf("[context:%s]: %s", clusterName, "unable to find exec plugin")
	}

	// default to the kubelogin exec name
	if expectedExecName == "" {
		expectedExecName = execName
	}

	if exec.Command != expectedExecName {
		t.Fatalf("[context:%s]: expected exec command: %s, actual: %s", clusterName, expectedExecName, exec.Command)
	}

	// default to the kubelogin install hint
	if expectedInstallHint == "" {
		expectedInstallHint = execInstallHint
	}

	if exec.InstallHint != expectedInstallHint {
		t.Fatalf("[context:%s]: expected install hint: %s, actual: %s", clusterName, expectedInstallHint, exec.InstallHint)
	}

	// Only validate the API version and first arg if exec is using kubelogin
	if exec.Command == execName {
		if exec.APIVersion != execAPIVersion {
			t.Fatalf("[context:%s]: expected API Version: %s, actual: %s", clusterName, execAPIVersion, exec.APIVersion)
		}

		if exec.Args[0] != getTokenCommand {
			t.Fatalf("[context:%s]: expected %s as first argument. actual: %s", clusterName, getTokenCommand, exec.Args[0])
		}
	}

	if len(exec.Args) != len(expectedArgs) {
		t.Fatalf("[context:%s]: expected exec args: %v, actual: %v", clusterName, expectedArgs, exec.Args)
	}
	for _, v := range expectedArgs {
		if !contains(exec.Args, v) {
			t.Fatalf("[context:%s]: expected exec arg: %s not found in %v", clusterName, v, exec.Args)
		}
	}
	if len(expectedEnv) != len(exec.Env) {
		t.Fatalf("[context:%s]: expected Env has %d entries, got %d", clusterName, len(expectedEnv), len(exec.Env))
	}
	for i, v := range expectedEnv {
		if exec.Env[i] != v {
			t.Fatalf("[context:%s]: for exec.Env, expected %q at index %d, got %q", clusterName, v, i, exec.Env[i])
		}
	}
}

func validateAuthInfoThatShouldNotChange(
	t *testing.T,
	clusterName string,
	authInfo *clientcmdapi.AuthInfo,
	authProviderConfig map[string]string,
) {
	if authInfo.AuthProvider == nil {
		t.Fatalf("[context:%s]: %s", clusterName, "auth provider should not be reset")
	}
	for k, v := range authInfo.AuthProvider.Config {
		if authProviderConfig[k] != v {
			t.Fatalf("[context:%s]: %s=%s does not match with input %s=%s", clusterName, k, v, k, authProviderConfig[k])
		}
	}
	for k, v := range authProviderConfig {
		if authInfo.AuthProvider.Config[k] != v {
			t.Fatalf("[context:%s]: %s=%s does not match with output %s=%s", clusterName, k, v, k, authInfo.AuthProvider.Config[k])
		}
	}
}

func (o *Options) setFlag(key, value string) error {
	return o.Flags.Set(key, value)
}

func contains(a []string, x string) bool {
	for _, n := range a {
		if x == n {
			return true
		}
	}
	return false
}
0707010000005C000081A4000000000000000000000001691F8CFD00000A3A000000000000000000000000000000000000003300000000kubelogin-0.2.13/pkg/internal/converter/options.gopackage converter

import (
	"fmt"

	"github.com/Azure/kubelogin/pkg/internal/token"
	"github.com/spf13/cobra"
	"github.com/spf13/pflag"
	"k8s.io/cli-runtime/pkg/genericclioptions"
)

type Options struct {
	Flags        *pflag.FlagSet
	configFlags  genericclioptions.RESTClientGetter
	TokenOptions token.Options
	// context is the kubeconfig context name
	context        string
	azureConfigDir string
}

func stringptr(str string) *string { return &str }

func New() Options {
	configFlags := &genericclioptions.ConfigFlags{
		KubeConfig: stringptr(""),
	}
	return Options{configFlags: configFlags}
}

func (o *Options) AddFlags(fs *pflag.FlagSet) {
	o.TokenOptions = token.NewOptions(true)
	if cf, ok := o.configFlags.(*genericclioptions.ConfigFlags); ok {
		cf.AddFlags(fs)
	}
	fs.StringVar(&o.context, flagContext, "", "The name of the kubeconfig context to use")
	fs.StringVar(&o.azureConfigDir, flagAzureConfigDir, "", "Azure CLI config path")
	o.TokenOptions.AddFlags(fs)
}

func (o *Options) Validate() error {
	return o.TokenOptions.Validate()
}

func (o *Options) UpdateFromEnv() {
	o.TokenOptions.UpdateFromEnv()
}

func (o *Options) ToString() string {
	return fmt.Sprintf("Context: %s, %s", o.context, o.TokenOptions.ToString())
}

func (o *Options) isSet(name string) bool {
	found := false
	o.Flags.Visit(func(f *pflag.Flag) {
		if f.Name == name {
			found = true
		}
	})
	return found
}

func (o *Options) AddCompletions(cmd *cobra.Command) {
	_ = cmd.RegisterFlagCompletionFunc(flagContext, completeContexts(o))
	_ = cmd.MarkFlagDirname(flagAzureConfigDir)
	_ = cmd.MarkFlagFilename("kubeconfig", "")

	o.TokenOptions.AddCompletions(cmd)

	cmd.Flags().VisitAll(func(flag *pflag.Flag) {
		// Set a default completion function if none was set. We don't look
		// up if it does already have one set, because Cobra does this for
		// us, and returns an error (which we ignore for this reason).
		_ = cmd.RegisterFlagCompletionFunc(flag.Name, cobra.NoFileCompletions)
	})
}

func completeContexts(o *Options) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
	return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
		clientConfig := o.configFlags.ToRawKubeConfigLoader()
		config, err := clientConfig.RawConfig()
		if err != nil {
			cobra.CompDebugln(fmt.Sprintf("unable to load kubeconfig: %s", err), false)
		}

		contexts := make([]string, 0, len(config.Contexts))
		for name := range config.Contexts {
			contexts = append(contexts, name)
		}

		return contexts, cobra.ShellCompDirectiveNoFileComp
	}
}
0707010000005D000081A4000000000000000000000001691F8CFD00000125000000000000000000000000000000000000003800000000kubelogin-0.2.13/pkg/internal/converter/options_test.gopackage converter

import (
	"testing"

	"github.com/spf13/pflag"
)

func TestOptions(t *testing.T) {
	o := New()
	o.AddFlags(&pflag.FlagSet{})
	o.UpdateFromEnv()
	o.TokenOptions.ServerID = "server-id"
	if err := o.Validate(); err != nil {
		t.Fatalf("option validation failed: %s", err)
	}
}
0707010000005E000041ED000000000000000000000002691F8CFD00000000000000000000000000000000000000000000002200000000kubelogin-0.2.13/pkg/internal/env0707010000005F000081A4000000000000000000000001691F8CFD00000747000000000000000000000000000000000000002F00000000kubelogin-0.2.13/pkg/internal/env/variables.gopackage env

const (
	// env vars
	LoginMethod                        = "AAD_LOGIN_METHOD"
	KubeloginROPCUsername              = "AAD_USER_PRINCIPAL_NAME"
	KubeloginROPCPassword              = "AAD_USER_PRINCIPAL_PASSWORD"
	KubeloginClientID                  = "AAD_SERVICE_PRINCIPAL_CLIENT_ID"
	KubeloginClientSecret              = "AAD_SERVICE_PRINCIPAL_CLIENT_SECRET"
	KubeloginClientCertificatePath     = "AAD_SERVICE_PRINCIPAL_CLIENT_CERTIFICATE"
	KubeloginClientCertificatePassword = "AAD_SERVICE_PRINCIPAL_CLIENT_CERTIFICATE_PASSWORD"

	// env vars used by Terraform
	TerraformClientID                  = "ARM_CLIENT_ID"
	TerraformClientSecret              = "ARM_CLIENT_SECRET"
	TerraformClientCertificatePath     = "ARM_CLIENT_CERTIFICATE_PATH"
	TerraformClientCertificatePassword = "ARM_CLIENT_CERTIFICATE_PASSWORD"
	TerraformTenantID                  = "ARM_TENANT_ID"

	// env vars following azure sdk naming convention
	AzureAuthorityHost             = "AZURE_AUTHORITY_HOST"
	AzureClientCertificatePassword = "AZURE_CLIENT_CERTIFICATE_PASSWORD"
	AzureClientCertificatePath     = "AZURE_CLIENT_CERTIFICATE_PATH"
	AzureClientID                  = "AZURE_CLIENT_ID"
	AzureClientSecret              = "AZURE_CLIENT_SECRET"
	AzureFederatedTokenFile        = "AZURE_FEDERATED_TOKEN_FILE"
	AzurePassword                  = "AZURE_PASSWORD"
	AzureTenantID                  = "AZURE_TENANT_ID"
	AzureUsername                  = "AZURE_USERNAME"

	// env vars used by Azure Pipelines
	SystemAccessToken    = "SYSTEM_ACCESSTOKEN"
	SystemOIDCRequestURI = "SYSTEM_OIDCREQUESTURI"

	// env vars used by Azure Pipelines service connections
	AzureSubscriptionTenantID            = "AZURESUBSCRIPTION_TENANT_ID"
	AzureSubscriptionServiceConnectionID = "AZURESUBSCRIPTION_SERVICE_CONNECTION_ID"
	AzureSubscriptionClientID            = "AZURESUBSCRIPTION_CLIENT_ID"
)
07070100000060000041ED000000000000000000000002691F8CFD00000000000000000000000000000000000000000000002200000000kubelogin-0.2.13/pkg/internal/pop07070100000061000081A4000000000000000000000001691F8CFD000011ED000000000000000000000000000000000000003100000000kubelogin-0.2.13/pkg/internal/pop/authnscheme.go// Disclaimer: The PoPAuthenticationScheme implementation of the MSAL AuthenticationScheme
// interface is intended for the usage of Azure Arc.

package pop

import (
	"crypto/sha256"
	"encoding/base64"
	"fmt"
	"strings"
	"time"

	"github.com/google/uuid"
)

// type of a PoP token, as opposed to "JWT" for a regular bearer token
const popTokenType = "pop"

// PoPAuthenticationScheme is a PoP token implementation of the MSAL AuthenticationScheme interface
// used by the Azure Arc Platform team.
// This implementation will only use the passed-in u-claim (representing the ARM ID of the
// cluster/host); other claims passed in during a PoP token request will be disregarded
type PoPAuthenticationScheme struct {
	// host is the u claim we will add on the pop token
	Host   string
	PoPKey PoPKey
}

// TokenRequestParams returns the params to use when sending a request for a PoP token
func (as *PoPAuthenticationScheme) TokenRequestParams() map[string]string {
	return map[string]string{
		"token_type": popTokenType,
		"req_cnf":    as.PoPKey.ReqCnf(),
	}
}

// KeyID returns the key used to sign the PoP token
func (as *PoPAuthenticationScheme) KeyID() string {
	return as.PoPKey.KeyID()
}

// FormatAccessToken takes an access token, formats it as a PoP token,
// and returns it as a base-64 encoded string
func (as *PoPAuthenticationScheme) FormatAccessToken(accessToken string) (string, error) {
	timestamp := time.Now().Unix()
	nonce := uuid.NewString()
	nonce = strings.ReplaceAll(nonce, "-", "")

	return as.FormatAccessTokenWithOptions(accessToken, nonce, timestamp)
}

// FormatAccessTokenWithOptions takes an access token, nonce, and timestamp, formats
// the token as a PoP token containing the given fields, and returns it as a
// base-64 encoded string
func (as *PoPAuthenticationScheme) FormatAccessTokenWithOptions(accessToken, nonce string, timestamp int64) (string, error) {
	header := header{
		typ: popTokenType,
		alg: as.PoPKey.Alg(),
		kid: as.PoPKey.KeyID(),
	}
	payload := payload{
		at:    accessToken,
		ts:    timestamp,
		host:  as.Host,
		jwk:   as.PoPKey.JWK(),
		nonce: nonce,
	}

	popAccessToken, err := createPoPAccessToken(header, payload, as.PoPKey)
	if err != nil {
		return "", fmt.Errorf("error formatting PoP token: %w", err)
	}
	return popAccessToken.ToBase64(), nil
}

// AccessTokenType returns the PoP access token type
func (as *PoPAuthenticationScheme) AccessTokenType() string {
	return popTokenType
}

// type representing the header of a PoP access token
type header struct {
	typ string
	alg string
	kid string
}

// ToString returns a string representation of a header object
func (h *header) ToString() string {
	return fmt.Sprintf(`{"typ":"%s","alg":"%s","kid":"%s"}`, h.typ, h.alg, h.kid)
}

// ToBase64 returns a base-64 encoded string representation of a header object
func (h *header) ToBase64() string {
	return base64.RawURLEncoding.EncodeToString([]byte(h.ToString()))
}

// type representing the payload of a PoP token
type payload struct {
	at    string
	ts    int64
	host  string
	jwk   string
	nonce string
}

// ToString returns a string representation of a payload object
func (p *payload) ToString() string {
	return fmt.Sprintf(`{"at":"%s","ts":%d,"u":"%s","cnf":{"jwk":%s},"nonce":"%s"}`, p.at, p.ts, p.host, p.jwk, p.nonce)
}

// ToBase64 returns a base-64 encoded representation of a payload object
func (p *payload) ToBase64() string {
	return base64.RawURLEncoding.EncodeToString([]byte(p.ToString()))
}

// type representing the signature of a PoP token
type signature struct {
	sig []byte
}

// ToBase64 returns a base-64 encoded representation of a signature object
func (s *signature) ToBase64() string {
	return base64.RawURLEncoding.EncodeToString(s.sig)
}

// type representing a PoP access token
type popAccessToken struct {
	Header    header
	Payload   payload
	Signature signature
}

// given a header, payload, and PoP key, creates the signature for the token and returns
// a PoPAccessToken object representing the signed token
func createPoPAccessToken(h header, p payload, popKey PoPKey) (*popAccessToken, error) {
	token := &popAccessToken{
		Header:  h,
		Payload: p,
	}
	h256 := sha256.Sum256([]byte(h.ToBase64() + "." + p.ToBase64()))
	sig, err := popKey.Sign(h256[:])
	if err != nil {
		return nil, err
	}
	token.Signature = signature{
		sig: sig,
	}
	return token, nil
}

// ToBase64 returns a base-64 encoded representation of a PoP access token
func (p *popAccessToken) ToBase64() string {
	return fmt.Sprintf("%s.%s.%s", p.Header.ToBase64(), p.Payload.ToBase64(), p.Signature.ToBase64())
}
07070100000062000081A4000000000000000000000001691F8CFD00000F73000000000000000000000000000000000000003600000000kubelogin-0.2.13/pkg/internal/pop/authnscheme_test.gopackage pop

import (
	"crypto/rand"
	"crypto/rsa"
	"math"
	"strings"
	"testing"
	"time"

	"github.com/golang-jwt/jwt/v4"
	"github.com/google/uuid"
)

func TestAuthnScheme(t *testing.T) {
	t.Run("FormatAccessTokenWithOptions should return a correctly formatted PoP token", func(t *testing.T) {
		accessToken := uuid.NewString()
		timestamp := time.Now().Unix()
		nonce := uuid.NewString()
		nonce = strings.ReplaceAll(nonce, "-", "")
		host := "testresource"
		popKey, err := GetSwPoPKey()
		if err != nil {
			t.Errorf("expected no error but got: %s", err)
		}
		authnScheme := &PoPAuthenticationScheme{
			Host:   host,
			PoPKey: popKey,
		}

		formatted, err := authnScheme.FormatAccessTokenWithOptions(accessToken, nonce, timestamp)
		if err != nil {
			t.Errorf("expected no error but got: %s", err)
		}
		claims := jwt.MapClaims{}
		parsed, _ := jwt.ParseWithClaims(formatted, &claims, func(token *jwt.Token) (interface{}, error) {
			return authnScheme.PoPKey.KeyID(), nil
		})
		if claims["at"] != accessToken {
			t.Errorf("expected access token: %s but got: %s", accessToken, claims["at"])
		}
		if claims["u"] != host {
			t.Errorf("expected u-claim value: %s but got: %s", host, claims["u"])
		}
		ts := int64(math.Round(claims["ts"].(float64)))
		if ts != timestamp {
			t.Errorf("expected timestamp value: %d but got: %d", timestamp, ts)
		}
		if claims["nonce"] != nonce {
			t.Errorf("expected nonce value: %s but got: %s", nonce, claims["nonce"])
		}
		if parsed.Header["typ"] != popTokenType {
			t.Errorf("expected token type: %s but got: %s", popTokenType, parsed.Header["typ"])
		}
		if parsed.Header["alg"] != authnScheme.PoPKey.Alg() {
			t.Errorf("expected token alg: %s but got: %s", authnScheme.PoPKey.Alg(), parsed.Header["alg"])
		}
		if parsed.Header["kid"] != authnScheme.KeyID() {
			t.Errorf("expected token kid: %s but got: %s", authnScheme.PoPKey.KeyID(), parsed.Header["kid"])
		}

		header := header{
			typ: popTokenType,
			alg: authnScheme.PoPKey.Alg(),
			kid: authnScheme.PoPKey.KeyID(),
		}
		payload := payload{
			at:    accessToken,
			ts:    timestamp,
			host:  host,
			jwk:   authnScheme.PoPKey.JWK(),
			nonce: nonce,
		}
		popAccessToken, err := createPoPAccessToken(header, payload, authnScheme.PoPKey)
		if err != nil {
			t.Errorf("expected no error but got: %s", err)
		}
		if parsed.Signature != popAccessToken.Signature.ToBase64() {
			t.Errorf("expected token signature: %s but got: %s", popAccessToken.Signature.ToBase64(), parsed.Signature)
		}
	})

	t.Run("TokenRequestParams should return correct token_type and req_cnf claims", func(t *testing.T) {
		host := "testresource"
		rsaKey, err := rsa.GenerateKey(rand.Reader, 2048)
		if err != nil {
			t.Errorf("expected no error generating RSA key but got: %s", err)
		}
		popKey, err := GetSwPoPKeyWithRSAKey(rsaKey)
		if err != nil {
			t.Errorf("expected no error but got: %s", err)
		}
		authnScheme := &PoPAuthenticationScheme{
			Host:   host,
			PoPKey: popKey,
		}
		tokenRequestParams := authnScheme.TokenRequestParams()

		// validate token type
		if tokenRequestParams["token_type"] != "pop" {
			t.Errorf("expected req_cnf: %s but got: %s", "pop", tokenRequestParams["token_type"])
		}

		// validate req_cnf
		eB64, nB64 := getRSAKeyExponentAndModulus(popKey.key)
		jwktp := computeJWKThumbprint(eB64, nB64)
		expectedReqCnf := getReqCnf(jwktp)
		if tokenRequestParams["req_cnf"] != expectedReqCnf {
			t.Errorf("expected req_cnf: %s but got: %s", expectedReqCnf, tokenRequestParams["req_cnf"])
		}
	})

	t.Run("AccessTokenType should return correct type", func(t *testing.T) {
		host := "testresource"
		popKey, err := GetSwPoPKey()
		if err != nil {
			t.Errorf("expected no error but got: %s", err)
		}
		authnScheme := &PoPAuthenticationScheme{
			Host:   host,
			PoPKey: popKey,
		}

		if authnScheme.AccessTokenType() != "pop" {
			t.Errorf("expected req_cnf: %s but got: %s", "pop", authnScheme.AccessTokenType())
		}
	})
}
07070100000063000041ED000000000000000000000002691F8CFD00000000000000000000000000000000000000000000002800000000kubelogin-0.2.13/pkg/internal/pop/cache07070100000064000081A4000000000000000000000001691F8CFD00001343000000000000000000000000000000000000003100000000kubelogin-0.2.13/pkg/internal/pop/cache/cache.gopackage cache

import (
	"bytes"
	"context"
	"crypto/rand"
	"fmt"
	"os"
	"path/filepath"
	"sync"
	"time"

	"github.com/AzureAD/microsoft-authentication-extensions-for-go/cache/accessor"
	"github.com/AzureAD/microsoft-authentication-library-for-go/apps/cache"
)

const popTokenCacheFileName = "pop_tokens.cache"

var (
	// once ensures storage capability is tested only once per process
	once = &sync.Once{}
	// storageError caches the result of the storage capability test
	storageError error
	// testStorage performs a round-trip test of storage functionality
	// This follows the Azure SDK pattern - https://github.com/Azure/azure-sdk-for-go/blob/main/sdk/azidentity/cache/cache.go
	testStorage = func() {
		const errFmt = "persistent PoP cache storage isn't available due to error %q"

		// Use random content to prevent conflicts with concurrent processes
		randomBytes := make([]byte, 8)
		_, err := rand.Read(randomBytes)
		if err != nil {
			storageError = fmt.Errorf(errFmt, fmt.Errorf("failed to generate random test data: %w", err))
			return
		}
		testContent := append([]byte("storage-test-"), randomBytes...)

		// Use a dedicated test path that won't interfere with actual cache
		testPath := filepath.Join(os.TempDir(), "kubelogin-pop-cache-storage-test")
		acc, err := storage(testPath)
		if err != nil {
			storageError = fmt.Errorf(errFmt, err)
			return
		}

		ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
		defer cancel()

		// Test Write
		if err = acc.Write(ctx, testContent); err != nil {
			storageError = fmt.Errorf(errFmt, err)
			return
		}

		// Test Read
		readContent, err := acc.Read(ctx)
		if err != nil {
			storageError = fmt.Errorf(errFmt, err)
			return
		}

		// Verify content matches
		if !bytes.Equal(testContent, readContent) {
			storageError = fmt.Errorf(errFmt, "storage read/write validation failed")
			return
		}

		// Test Deletion
		err = acc.Delete(ctx)
		if err != nil {
			storageError = fmt.Errorf(errFmt, err)
			return
		}
	}
)

// getPoPCacheFilePath returns the file path for the PoP token cache.
// This is separate from the authentication record cache file.
func getPoPCacheFilePath(cacheDir string) string {
	return filepath.Join(cacheDir, popTokenCacheFileName)
}

// Cache implements the MSAL cache.ExportReplace interface using our platform-specific PoP cache.
// This provides secure, persistent PoP token storage without depending on libsecret on Linux.
// Cache provides a unified interface for PoP token caching following azidentity patterns.
type Cache struct {
	accessor accessor.Accessor
}

// NewCache creates a new MSAL cache provider using custom platform-specific PoP cache.
// This implementation provides secure storage on all platforms without external dependencies like libsecret on Linux.
// Following the azidentity pattern, this proactively tests storage capability before creating the cache.
func NewCache(cacheDir string) (*Cache, error) {
	cachePath := getPoPCacheFilePath(cacheDir)

	// Test storage capability once per process
	once.Do(testStorage)
	if storageError != nil {
		return nil, storageError
	}

	acc, err := storage(cachePath)
	if err != nil {
		return nil, fmt.Errorf("failed to create PoP cache storage: %w", err)
	}

	return &Cache{
		accessor: acc,
	}, nil
}

// Export saves the current PoP token cache state to platform-specific secure storage.
// This method is called by MSAL to persist PoP tokens across application restarts.
func (c *Cache) Export(ctx context.Context, marshaler cache.Marshaler, hints cache.ExportHints) error {
	// Get the cache data from the marshaler
	data, err := marshaler.Marshal()
	if err != nil {
		return fmt.Errorf("failed to marshal PoP cache data: %w", err)
	}

	return c.accessor.Write(ctx, data)
}

// Replace loads PoP token cache data from platform-specific secure storage and restores it into MSAL's in-memory cache.
// This method is called by MSAL during initialization to restore previously cached PoP tokens from persistent storage.
func (c *Cache) Replace(ctx context.Context, unmarshaler cache.Unmarshaler, hints cache.ReplaceHints) error {
	data, err := c.accessor.Read(ctx)
	if err != nil {
		// If cache doesn't exist, initialize with empty cache
		return unmarshaler.Unmarshal([]byte("{}"))
	}

	// If no data exists (file doesn't exist or is empty), initialize with empty cache
	if len(data) == 0 {
		return unmarshaler.Unmarshal([]byte("{}"))
	}

	return unmarshaler.Unmarshal(data)
}

// Clear removes all PoP token data from the cache.
func (c *Cache) Clear(ctx context.Context) error {
	return c.accessor.Delete(ctx)
}

// NewSecureAccessor creates a new platform-specific secure storage accessor.
// This can be used for storing other sensitive data like RSA private keys
// using the same encrypted storage infrastructure as the PoP token cache.
func NewSecureAccessor(cachePath string) (accessor.Accessor, error) {
	return storage(cachePath)
}
07070100000065000081A4000000000000000000000001691F8CFD00002A10000000000000000000000000000000000000003600000000kubelogin-0.2.13/pkg/internal/pop/cache/cache_test.gopackage cache

import (
	"bytes"
	"context"
	"crypto/rand"
	"fmt"
	"os"
	"path/filepath"
	"testing"
	"time"

	"github.com/AzureAD/microsoft-authentication-library-for-go/apps/cache"
	"github.com/google/uuid"
	"github.com/stretchr/testify/require"
)

var ctx = context.Background()

// mockMarshaler implements cache.Marshaler for testing
type mockMarshaler struct {
	data []byte
	err  error
}

func (m *mockMarshaler) Marshal() ([]byte, error) {
	return m.data, m.err
}

// mockUnmarshaler implements cache.Unmarshaler for testing
type mockUnmarshaler struct {
	data []byte
	err  error
}

func (m *mockUnmarshaler) Unmarshal(data []byte) error {
	m.data = data
	return m.err
}

func TestNewCache(t *testing.T) {
	tests := []struct {
		name     string
		cacheDir string
		wantErr  bool
	}{
		{
			name:     "valid cache directory",
			cacheDir: t.TempDir(),
			wantErr:  false,
		},
		{
			name:     "empty cache directory",
			cacheDir: "",
			wantErr:  false, // should still work with empty dir
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			cache, err := NewCache(tt.cacheDir)
			if tt.wantErr {
				require.Error(t, err)
				require.Nil(t, cache)
			} else {
				require.NoError(t, err)
				require.NotNil(t, cache)
				require.NotNil(t, cache.accessor)
			}
		})
	}
}

func TestCache_ExportReplace(t *testing.T) {
	tempDir := t.TempDir()
	c, err := NewCache(tempDir)
	require.NoError(t, err)

	testData := []byte(`{"access_tokens": {"key1": "value1"}, "refresh_tokens": {"key2": "value2"}}`)

	// Test Export
	marshaler := &mockMarshaler{data: testData}
	err = c.Export(ctx, marshaler, cache.ExportHints{})
	require.NoError(t, err)

	// Test Replace
	unmarshaler := &mockUnmarshaler{}
	err = c.Replace(ctx, unmarshaler, cache.ReplaceHints{})
	require.NoError(t, err)
	require.Equal(t, testData, unmarshaler.data)
}

func TestCache_ExportReplaceEmpty(t *testing.T) {
	tempDir := t.TempDir()
	c, err := NewCache(tempDir)
	require.NoError(t, err)

	// Test Replace on empty cache - should get empty JSON since no data exists
	unmarshaler := &mockUnmarshaler{}
	err = c.Replace(ctx, unmarshaler, cache.ReplaceHints{})
	require.NoError(t, err)
	require.Equal(t, []byte("{}"), unmarshaler.data)
}

func TestCache_ExportMarshalError(t *testing.T) {
	tempDir := t.TempDir()
	c, err := NewCache(tempDir)
	require.NoError(t, err)

	expectedErr := fmt.Errorf("marshal error")
	marshaler := &mockMarshaler{err: expectedErr}

	err = c.Export(ctx, marshaler, cache.ExportHints{})
	require.Error(t, err)
	require.Contains(t, err.Error(), "failed to marshal PoP cache data")
}

func TestCache_ReplaceUnmarshalError(t *testing.T) {
	tempDir := t.TempDir()
	c, err := NewCache(tempDir)
	require.NoError(t, err)

	// First export some data
	testData := []byte(`{"access_tokens": {"key1": "value1"}}`)
	marshaler := &mockMarshaler{data: testData}
	err = c.Export(ctx, marshaler, cache.ExportHints{})
	require.NoError(t, err)

	// Then try to replace with an unmarshaler that returns error
	expectedErr := fmt.Errorf("unmarshal error")
	unmarshaler := &mockUnmarshaler{err: expectedErr}

	err = c.Replace(ctx, unmarshaler, cache.ReplaceHints{})
	require.Error(t, err)
	require.Equal(t, expectedErr, err)
}

func TestCache_Clear(t *testing.T) {
	tempDir := t.TempDir()
	c, err := NewCache(tempDir)
	require.NoError(t, err)

	// Export some data first
	testData := []byte(`{"access_tokens": {"key1": "value1"}}`)
	marshaler := &mockMarshaler{data: testData}
	err = c.Export(ctx, marshaler, cache.ExportHints{})
	require.NoError(t, err)

	// Clear the cache
	err = c.Clear(ctx)
	require.NoError(t, err)

	// Verify cache is empty - after delete, should get empty JSON
	unmarshaler := &mockUnmarshaler{}
	err = c.Replace(ctx, unmarshaler, cache.ReplaceHints{})
	require.NoError(t, err)
	require.Equal(t, []byte("{}"), unmarshaler.data)
}

func TestCache_MultipleProcessSimulation(t *testing.T) {
	tempDir := t.TempDir()

	// Multiple kubelogin processes (simulated as goroutines) - each using the SAME cache directory (like real users would)
	// This tests the Linux keyring's process isolation and file system behavior
	const numProcesses = 3
	done := make(chan error, numProcesses)

	for i := 0; i < numProcesses; i++ {
		go func(processID int) {
			// Each "process" creates its own cache instance but uses the same cache directory
			// This simulates multiple kubelogin processes run by same user
			c, err := NewCache(tempDir)
			if err != nil {
				done <- fmt.Errorf("process %d: failed to create cache: %w", processID, err)
				return
			}

			// Each process exports its own tokens
			testData := []byte(fmt.Sprintf(`{"access_tokens": {"process_%d": "token_%d"}}`, processID, processID))
			marshaler := &mockMarshaler{data: testData}

			err = c.Export(ctx, marshaler, cache.ExportHints{})
			if err != nil {
				done <- fmt.Errorf("process %d: export failed: %w", processID, err)
				return
			}

			// Each process should be able to read back some valid data
			// (might be from this process or another due to last-write-wins behavior)
			unmarshaler := &mockUnmarshaler{}
			err = c.Replace(ctx, unmarshaler, cache.ReplaceHints{})
			if err != nil {
				done <- fmt.Errorf("process %d: replace failed: %w", processID, err)
				return
			}

			// Verify we got valid JSON (the exact content may vary due to concurrent writes)
			if len(unmarshaler.data) == 0 || !bytes.HasPrefix(unmarshaler.data, []byte("{")) {
				done <- fmt.Errorf("process %d: invalid data format: %s", processID, unmarshaler.data)
				return
			}

			done <- nil
		}(i)
	}

	// Wait for all "processes" to complete successfully
	for i := 0; i < numProcesses; i++ {
		select {
		case err := <-done:
			require.NoError(t, err)
		case <-time.After(10 * time.Second):
			t.Fatal("timeout waiting for process simulation")
		}
	}

	// Final verification: ensure the cache is in a consistent state
	c, err := NewCache(tempDir)
	require.NoError(t, err)

	unmarshaler := &mockUnmarshaler{}
	err = c.Replace(ctx, unmarshaler, cache.ReplaceHints{})
	require.NoError(t, err)

	// Should have valid JSON from one of the processes
	require.True(t, bytes.HasPrefix(unmarshaler.data, []byte("{")))
}

func TestCache_Isolation(t *testing.T) {
	// Test that different cache instances with different names are isolated
	tempDir := t.TempDir()

	cache1, err := NewCache(filepath.Join(tempDir, "cache1"))
	require.NoError(t, err)

	cache2, err := NewCache(filepath.Join(tempDir, "cache2"))
	require.NoError(t, err)

	// Export different data to each cache
	testData1 := []byte(`{"access_tokens": {"cache1": "data1"}}`)
	marshaler1 := &mockMarshaler{data: testData1}
	err = cache1.Export(ctx, marshaler1, cache.ExportHints{})
	require.NoError(t, err)

	testData2 := []byte(`{"access_tokens": {"cache2": "data2"}}`)
	marshaler2 := &mockMarshaler{data: testData2}
	err = cache2.Export(ctx, marshaler2, cache.ExportHints{})
	require.NoError(t, err)

	// Verify each cache has its own data
	unmarshaler1 := &mockUnmarshaler{}
	err = cache1.Replace(ctx, unmarshaler1, cache.ReplaceHints{})
	require.NoError(t, err)
	require.Equal(t, testData1, unmarshaler1.data)

	unmarshaler2 := &mockUnmarshaler{}
	err = cache2.Replace(ctx, unmarshaler2, cache.ReplaceHints{})
	require.NoError(t, err)
	require.Equal(t, testData2, unmarshaler2.data)
}

func TestGetPoPCacheFilePath(t *testing.T) {
	tests := []struct {
		name     string
		cacheDir string
		expected string
	}{
		{
			name:     "unix path",
			cacheDir: "/home/user/.cache/kubelogin",
			expected: "/home/user/.cache/kubelogin/pop_tokens.cache",
		},
		{
			name:     "relative path",
			cacheDir: "cache",
			expected: "cache/pop_tokens.cache",
		},
		{
			name:     "empty path",
			cacheDir: "",
			expected: "pop_tokens.cache",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := getPoPCacheFilePath(tt.cacheDir)
			require.Equal(t, tt.expected, result)
		})
	}
}

func TestNewSecureAccessor(t *testing.T) {
	tempDir := t.TempDir()
	cachePath := filepath.Join(tempDir, "test.cache")

	accessor, err := NewSecureAccessor(cachePath)
	require.NoError(t, err)
	require.NotNil(t, accessor)

	// Test basic operations
	testData := []byte("test secure data")

	err = accessor.Write(ctx, testData)
	require.NoError(t, err)

	readData, err := accessor.Read(ctx)
	require.NoError(t, err)
	require.Equal(t, testData, readData)

	err = accessor.Delete(ctx)
	require.NoError(t, err)

	// Verify data is deleted
	readData, err = accessor.Read(ctx)
	require.NoError(t, err)
	require.Nil(t, readData)
}

func TestStorageRoundTrip(t *testing.T) {
	tempDir := t.TempDir()
	uniqueName := uuid.NewString()
	cachePath := filepath.Join(tempDir, uniqueName)

	accessor, err := storage(cachePath)
	require.NoError(t, err)

	// Generate random test data
	testData := make([]byte, 256)
	_, err = rand.Read(testData)
	require.NoError(t, err)

	// Test write
	err = accessor.Write(ctx, testData)
	require.NoError(t, err)

	// Test read
	readData, err := accessor.Read(ctx)
	require.NoError(t, err)
	require.Equal(t, testData, readData)

	// Verify file exists and is encrypted (content should be different from original)
	if fileContent, err := os.ReadFile(cachePath); err == nil {
		require.NotEqual(t, testData, fileContent, "file content should be encrypted")
		require.Greater(t, len(fileContent), 0, "encrypted file should not be empty")
	}

	// Test delete
	err = accessor.Delete(ctx)
	require.NoError(t, err)

	// Verify file is deleted
	_, err = os.Stat(cachePath)
	require.True(t, os.IsNotExist(err), "cache file should be deleted")

	// Read after delete should return nil
	readData, err = accessor.Read(ctx)
	require.NoError(t, err)
	require.Nil(t, readData)
}

func TestStorageEmptyData(t *testing.T) {
	tempDir := t.TempDir()
	uniqueName := uuid.NewString()
	cachePath := filepath.Join(tempDir, uniqueName)

	accessor, err := storage(cachePath)
	require.NoError(t, err)

	// Test writing empty data
	err = accessor.Write(ctx, []byte{})
	require.NoError(t, err)

	// Test reading empty data
	readData, err := accessor.Read(ctx)
	require.NoError(t, err)
	require.Nil(t, readData)

	// Test writing nil data
	err = accessor.Write(ctx, nil)
	require.NoError(t, err)

	readData, err = accessor.Read(ctx)
	require.NoError(t, err)
	require.Nil(t, readData)
}

func TestStorageNonExistentFile(t *testing.T) {
	tempDir := t.TempDir()
	uniqueName := uuid.NewString()
	cachePath := filepath.Join(tempDir, uniqueName)

	accessor, err := storage(cachePath)
	require.NoError(t, err)

	// Reading non-existent file should return nil, not error
	readData, err := accessor.Read(ctx)
	require.NoError(t, err)
	require.Nil(t, readData)

	// Deleting non-existent file should not error
	err = accessor.Delete(ctx)
	require.NoError(t, err)
}
07070100000066000081A4000000000000000000000001691F8CFD0000020C000000000000000000000000000000000000003200000000kubelogin-0.2.13/pkg/internal/pop/cache/darwin.go//go:build darwin && cgo

package cache

import (
	"path/filepath"

	"github.com/AzureAD/microsoft-authentication-extensions-for-go/cache/accessor"
)

// storage creates a platform-specific accessor for macOS for MSAL cache
func storage(cachePath string) (accessor.Accessor, error) {
	// Use the filename from cachePath as the account identifier
	accountName := filepath.Base(cachePath)
	// Use "kubelogin-pop" as the service name in macOS Keychain
	return accessor.New("kubelogin-pop", accessor.WithAccount(accountName))
}
07070100000067000041ED000000000000000000000002691F8CFD00000000000000000000000000000000000000000000003100000000kubelogin-0.2.13/pkg/internal/pop/cache/internal07070100000068000041ED000000000000000000000002691F8CFD00000000000000000000000000000000000000000000003800000000kubelogin-0.2.13/pkg/internal/pop/cache/internal/aescbc07070100000069000081A4000000000000000000000001691F8CFD00000CEB000000000000000000000000000000000000004200000000kubelogin-0.2.13/pkg/internal/pop/cache/internal/aescbc/aescbc.gopackage aescbc

import (
	"bytes"
	"crypto"
	"crypto/aes"
	"crypto/cipher"
	"crypto/hmac"
	"encoding/binary"
	"errors"
	"hash"
)

// AES-CBC with HMAC-SHA2 encryption implementation for secure cache storage.
//
// This implementation is copied from the Azure SDK to avoid dependency on internal packages
// while maintaining compatibility with the azidentity cache encryption format.
// Reference: https://github.com/Azure/azure-sdk-for-go/blob/main/sdk/azidentity/cache/internal/aescbc/aescbc.go

// AESCBCHMACSHA2 implements AES_CBC_HMAC_SHA2 as defined in https://tools.ietf.org/html/rfc7518#section-5.2.2
type AESCBCHMACSHA2 struct {
	Alg            string
	encKey, macKey []byte
	hasher         func() hash.Hash
	tLen           int
}

type EncryptResult struct {
	Ciphertext, Tag []byte
}

// NewAES128CBCHMACSHA256 returns an implementation of AES_128_CBC_HMAC_SHA_256
// (https://tools.ietf.org/html/rfc7518#section-5.2.3)
func NewAES128CBCHMACSHA256(key []byte) (*AESCBCHMACSHA2, error) {
	if len(key) != 32 {
		return nil, errors.New("key must be 32 bytes")
	}
	cp := make([]byte, 32)
	copy(cp, key)
	return newAESCBCHMACSHA2("A128CBC-HS256", cp, crypto.SHA256.New)
}

func newAESCBCHMACSHA2(alg string, k []byte, hasher func() hash.Hash) (*AESCBCHMACSHA2, error) {
	return &AESCBCHMACSHA2{
		Alg:    alg,
		encKey: k[len(k)/2:],
		hasher: hasher,
		macKey: k[:len(k)/2],
		tLen:   len(k) / 2,
	}, nil
}

func (a *AESCBCHMACSHA2) Decrypt(iv, ciphertext, additionalData, tag []byte) ([]byte, error) {
	expected := a.tag(iv, ciphertext, additionalData)
	if !hmac.Equal(tag, expected) {
		return nil, errors.New("decryption failed")
	}
	block, err := aes.NewCipher(a.encKey)
	if err != nil {
		return nil, err
	}
	out := make([]byte, len(ciphertext))
	cipher.NewCBCDecrypter(block, iv).CryptBlocks(out, ciphertext)
	return unpad(out)
}

func (a *AESCBCHMACSHA2) Encrypt(iv, plaintext, additionalData []byte) (EncryptResult, error) {
	result := EncryptResult{}
	block, err := aes.NewCipher(a.encKey)
	if err != nil {
		return result, err
	}
	plaintext = pad(plaintext)
	result.Ciphertext = make([]byte, len(plaintext))
	cipher.NewCBCEncrypter(block, iv).CryptBlocks(result.Ciphertext, plaintext)
	result.Tag = a.tag(iv, result.Ciphertext, additionalData)
	return result, nil
}

func (a *AESCBCHMACSHA2) tag(iv, ciphertext, aad []byte) []byte {
	h := hmac.New(a.hasher, a.macKey)
	h.Write(aad)
	h.Write(iv)
	h.Write(ciphertext)
	// aadBits is AL from step 4 of https://datatracker.ietf.org/doc/html/rfc7518#section-5.2.2.1
	aadBits := make([]byte, 8)
	aadLen := uint64(len(aad))
	binary.BigEndian.PutUint64(aadBits, aadLen*8)
	h.Write(aadBits)
	return h.Sum(nil)[:a.tLen]
}

// pad adds PKCS#7 padding (https://datatracker.ietf.org/doc/html/rfc5652#section-6.3)
func pad(b []byte) []byte {
	n := aes.BlockSize - (len(b) % aes.BlockSize)
	padding := bytes.Repeat([]byte{byte(n)}, n)
	return append(b, padding...)
}

// unpad checks and removes PKCS#7 padding
func unpad(b []byte) ([]byte, error) {
	l := len(b)
	if l == 0 {
		return nil, nil
	}
	n := int(b[l-1])
	if n < 1 || n > aes.BlockSize || l%aes.BlockSize != 0 {
		return nil, errors.New("decryption failed")
	}
	for i := l - n; i < len(b); i++ {
		if b[i] != byte(n) {
			return nil, errors.New("decryption failed")
		}
	}
	return b[:l-n], nil
}
0707010000006A000081A4000000000000000000000001691F8CFD00001080000000000000000000000000000000000000004700000000kubelogin-0.2.13/pkg/internal/pop/cache/internal/aescbc/aescbc_test.gopackage aescbc

import (
	"testing"

	"github.com/stretchr/testify/require"
)

func TestAESCBCHMAC(t *testing.T) {
	for _, test := range []struct {
		aad, key, plaintext, iv, ciphertext, tag []byte
		name                                     string
	}{
		{
			// https://datatracker.ietf.org/doc/html/rfc7516#appendix-B
			name:       "RFC7516",
			aad:        []byte{101, 121, 74, 104, 98, 71, 99, 105, 79, 105, 74, 66, 77, 84, 73, 52, 83, 49, 99, 105, 76, 67, 74, 108, 98, 109, 77, 105, 79, 105, 74, 66, 77, 84, 73, 52, 81, 48, 74, 68, 76, 85, 104, 84, 77, 106, 85, 50, 73, 110, 48},
			ciphertext: []byte{40, 57, 83, 181, 119, 33, 133, 148, 198, 185, 243, 24, 152, 230, 6, 75, 129, 223, 127, 19, 210, 82, 183, 230, 168, 33, 215, 104, 143, 112, 56, 102},
			iv:         []byte{3, 22, 60, 12, 43, 67, 104, 105, 108, 108, 105, 99, 111, 116, 104, 101},
			key:        []byte{4, 211, 31, 197, 84, 157, 252, 254, 11, 100, 157, 250, 63, 170, 106, 206, 107, 124, 212, 45, 111, 107, 9, 219, 200, 177, 0, 240, 143, 156, 44, 207},
			plaintext:  []byte{76, 105, 118, 101, 32, 108, 111, 110, 103, 32, 97, 110, 100, 32, 112, 114, 111, 115, 112, 101, 114, 46},
			tag:        []byte{83, 73, 191, 98, 104, 205, 211, 128, 201, 189, 199, 133, 32, 38, 194, 85},
		},
		{
			// https://datatracker.ietf.org/doc/html/rfc7518#appendix-B.1
			name:       "RFC7518",
			aad:        []byte{0x54, 0x68, 0x65, 0x20, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x20, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x6c, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x41, 0x75, 0x67, 0x75, 0x73, 0x74, 0x65, 0x20, 0x4b, 0x65, 0x72, 0x63, 0x6b, 0x68, 0x6f, 0x66, 0x66, 0x73},
			ciphertext: []byte{0xc8, 0x0e, 0xdf, 0xa3, 0x2d, 0xdf, 0x39, 0xd5, 0xef, 0x00, 0xc0, 0xb4, 0x68, 0x83, 0x42, 0x79, 0xa2, 0xe4, 0x6a, 0x1b, 0x80, 0x49, 0xf7, 0x92, 0xf7, 0x6b, 0xfe, 0x54, 0xb9, 0x03, 0xa9, 0xc9, 0xa9, 0x4a, 0xc9, 0xb4, 0x7a, 0xd2, 0x65, 0x5c, 0x5f, 0x10, 0xf9, 0xae, 0xf7, 0x14, 0x27, 0xe2, 0xfc, 0x6f, 0x9b, 0x3f, 0x39, 0x9a, 0x22, 0x14, 0x89, 0xf1, 0x63, 0x62, 0xc7, 0x03, 0x23, 0x36, 0x09, 0xd4, 0x5a, 0xc6, 0x98, 0x64, 0xe3, 0x32, 0x1c, 0xf8, 0x29, 0x35, 0xac, 0x40, 0x96, 0xc8, 0x6e, 0x13, 0x33, 0x14, 0xc5, 0x40, 0x19, 0xe8, 0xca, 0x79, 0x80, 0xdf, 0xa4, 0xb9, 0xcf, 0x1b, 0x38, 0x4c, 0x48, 0x6f, 0x3a, 0x54, 0xc5, 0x10, 0x78, 0x15, 0x8e, 0xe5, 0xd7, 0x9d, 0xe5, 0x9f, 0xbd, 0x34, 0xd8, 0x48, 0xb3, 0xd6, 0x95, 0x50, 0xa6, 0x76, 0x46, 0x34, 0x44, 0x27, 0xad, 0xe5, 0x4b, 0x88, 0x51, 0xff, 0xb5, 0x98, 0xf7, 0xf8, 0x00, 0x74, 0xb9, 0x47, 0x3c, 0x82, 0xe2, 0xdb},
			iv:         []byte{0x1a, 0xf3, 0x8c, 0x2d, 0xc2, 0xb9, 0x6f, 0xfd, 0xd8, 0x66, 0x94, 0x09, 0x23, 0x41, 0xbc, 0x04},
			key:        []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f},
			plaintext:  []byte{0x41, 0x20, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x20, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x62, 0x65, 0x20, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x62, 0x65, 0x20, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x69, 0x74, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x62, 0x65, 0x20, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x74, 0x6f, 0x20, 0x66, 0x61, 0x6c, 0x6c, 0x20, 0x69, 0x6e, 0x74, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x68, 0x61, 0x6e, 0x64, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x65, 0x6e, 0x65, 0x6d, 0x79, 0x20, 0x77, 0x69, 0x74, 0x68, 0x6f, 0x75, 0x74, 0x20, 0x69, 0x6e, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x6e, 0x69, 0x65, 0x6e, 0x63, 0x65},
			tag:        []byte{0x65, 0x2c, 0x3f, 0xa3, 0x6b, 0x0a, 0x7c, 0x5b, 0x32, 0x19, 0xfa, 0xb3, 0xa3, 0x0b, 0xc1, 0xc4},
		},
	} {
		t.Run(test.name, func(t *testing.T) {
			a, err := NewAES128CBCHMACSHA256(test.key)
			require.NoError(t, err)

			result, err := a.Encrypt(test.iv, test.plaintext, test.aad)
			require.NoError(t, err)
			require.Equal(t, test.ciphertext, result.Ciphertext)
			require.Equal(t, test.tag, result.Tag)

			actual, err := a.Decrypt(test.iv, result.Ciphertext, test.aad, result.Tag)
			require.NoError(t, err)
			require.Equal(t, test.plaintext, actual)
		})
	}
}
0707010000006B000041ED000000000000000000000002691F8CFD00000000000000000000000000000000000000000000003500000000kubelogin-0.2.13/pkg/internal/pop/cache/internal/jwe0707010000006C000081A4000000000000000000000001691F8CFD00000C03000000000000000000000000000000000000003C00000000kubelogin-0.2.13/pkg/internal/pop/cache/internal/jwe/jwe.gopackage jwe

import (
	"bytes"
	"crypto/rand"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"

	aescbc "github.com/Azure/kubelogin/pkg/internal/pop/cache/internal/aescbc"
)

// JWE implements a subset of JSON Web Encryption (https://datatracker.ietf.org/doc/html/rfc7516).
// It supports only direct encryption (https://datatracker.ietf.org/doc/html/rfc7518#section-4.5)
// with A128CBC-HS256 and de/serializes only the compact format.
//
// This implementation is copied from the Azure SDK to avoid dependency on internal packages
// while maintaining compatibility with the azidentity cache encryption format.
// Reference: https://github.com/Azure/azure-sdk-for-go/blob/main/sdk/azidentity/cache/internal/jwe/jwe.go
type JWE struct {
	Ciphertext, IV, Tag []byte
	Header              Header
}

type Header struct {
	Alg string `json:"alg"`
	Enc string `json:"enc"`
	KID string `json:"kid"`
}

func Encrypt(plaintext []byte, kid string, alg *aescbc.AESCBCHMACSHA2) (JWE, error) {
	iv := make([]byte, 16)
	_, err := rand.Read(iv)
	if err != nil {
		return JWE{}, err
	}
	result, err := alg.Encrypt(iv, plaintext, nil)
	if err != nil {
		return JWE{}, err
	}
	return JWE{
		Ciphertext: result.Ciphertext,
		Header: Header{
			Alg: "dir",
			Enc: alg.Alg,
			KID: kid,
		},
		IV:  iv,
		Tag: result.Tag,
	}, nil
}

// ParseCompactFormat deserializes the compact format as returned by [JWE.Serialize]
func ParseCompactFormat(b []byte) (JWE, error) {
	s := bytes.Split(b, []byte("."))
	if len(s) != 5 {
		return JWE{}, errors.New("incorrectly formatted JWE")
	}
	hdr, err := decode(s[0])
	if err != nil {
		return JWE{}, err
	}
	h := Header{}
	err = json.Unmarshal(hdr, &h)
	if err != nil {
		return JWE{}, err
	}
	iv, err := decode(s[2])
	if err != nil {
		return JWE{}, err
	}
	ciphertext, err := decode(s[3])
	if err != nil {
		return JWE{}, err
	}
	tag, err := decode(s[4])
	if err != nil {
		return JWE{}, err
	}
	return JWE{Header: h, IV: iv, Ciphertext: ciphertext, Tag: tag}, nil
}

func (j *JWE) Decrypt(key []byte) ([]byte, error) {
	if j.Header.Alg != "dir" {
		return nil, fmt.Errorf("unsupported content encryption algorithm %q", j.Header.Alg)
	}
	alg, err := aescbc.NewAES128CBCHMACSHA256(key)
	if err != nil {
		return nil, err
	}
	if j.Header.Enc != alg.Alg {
		return nil, fmt.Errorf("unsupported encryption algorithm %q", j.Header.Enc)
	}
	return alg.Decrypt(j.IV, j.Ciphertext, nil, j.Tag)
}

// Serialize the JWE to compact format
func (j *JWE) Serialize() (string, error) {
	hdr, err := json.Marshal(j.Header)
	if err != nil {
		return "", err
	}
	return fmt.Sprintf(
		// second segment (encrypted key) is empty because direct encryption doesn't wrap a key
		"%s..%s.%s.%s",
		base64.RawURLEncoding.EncodeToString(hdr),
		base64.RawURLEncoding.EncodeToString(j.IV),
		base64.RawURLEncoding.EncodeToString(j.Ciphertext),
		base64.RawURLEncoding.EncodeToString(j.Tag),
	), nil
}

func decode(b []byte) ([]byte, error) {
	dst := make([]byte, base64.RawURLEncoding.DecodedLen(len(b)))
	n, err := base64.RawURLEncoding.Decode(dst, b)
	return dst[:n], err
}
0707010000006D000081A4000000000000000000000001691F8CFD000005DA000000000000000000000000000000000000004100000000kubelogin-0.2.13/pkg/internal/pop/cache/internal/jwe/jwe_test.gopackage jwe

import (
	"crypto/rand"
	"encoding/base64"
	"encoding/json"
	"strings"
	"testing"

	aescbc "github.com/Azure/kubelogin/pkg/internal/pop/cache/internal/aescbc"
	"github.com/stretchr/testify/require"
)

func TestEncryptParseDecrypt(t *testing.T) {
	plaintext := []byte("plaintext")
	kid := "42"
	key := make([]byte, 32)
	_, err := rand.Read(key)
	require.NoError(t, err)
	alg, err := aescbc.NewAES128CBCHMACSHA256(key)
	require.NoError(t, err)

	j, err := Encrypt(plaintext, kid, alg)
	require.NoError(t, err)

	s, err := j.Serialize()
	require.NoError(t, err)
	segments := strings.Split(s, ".")
	require.Len(t, segments, 5, "compact format has 5 segments")

	p, err := ParseCompactFormat([]byte(s))
	require.NoError(t, err)
	require.Equal(t, j, p)

	h, err := base64.RawURLEncoding.DecodeString(segments[0])
	require.NoError(t, err, segments[0])
	hdr := Header{}
	require.NoError(t, json.Unmarshal(h, &hdr))
	require.Equal(t, alg.Alg, hdr.Enc)
	require.Equal(t, "dir", hdr.Alg)
	require.Equal(t, kid, hdr.KID)

	require.Empty(t, segments[1])

	iv, err := base64.RawURLEncoding.DecodeString(segments[2])
	require.NoError(t, err)
	require.Len(t, iv, 16)

	ciphertext, err := base64.RawURLEncoding.DecodeString(segments[3])
	require.NoError(t, err)
	require.Len(t, ciphertext, 16)

	tag, err := base64.RawURLEncoding.DecodeString(segments[4])
	require.NoError(t, err)
	require.Len(t, tag, 16)

	actual, err := j.Decrypt(key)
	require.NoError(t, err)
	require.Equal(t, actual, plaintext)
}
0707010000006E000081A4000000000000000000000001691F8CFD00001B4E000000000000000000000000000000000000003100000000kubelogin-0.2.13/pkg/internal/pop/cache/linux.go//go:build linux

// Linux-specific PoP cache implementation using kernel keyrings for secure storage.
// This implementation is adapted from the Azure SDK azidentity cache to eliminate the
// dependency on libsecret while maintaining secure token storage on Linux systems.
//
// The implementation uses Linux kernel keyrings to store encryption keys securely
// in memory, with encrypted cache data persisted to disk. This provides:
// - No external dependencies (no libsecret required)
// - Secure key storage that survives process restarts but not system reboots
// - Encrypted cache files with keys protected by the kernel keyring system
//
// Reference: https://github.com/Azure/azure-sdk-for-go/blob/main/sdk/azidentity/cache/linux.go
package cache

import (
	"context"
	"crypto/rand"
	"errors"
	"fmt"
	"os"
	"path/filepath"

	aescbc "github.com/Azure/kubelogin/pkg/internal/pop/cache/internal/aescbc"
	"github.com/Azure/kubelogin/pkg/internal/pop/cache/internal/jwe"
	"github.com/AzureAD/microsoft-authentication-extensions-for-go/cache/accessor"
	"golang.org/x/sys/unix"
)

const (
	keySize = 32
	userKey = "user"
)

// keyring encrypts cache data with a key stored on the user keyring and writes the encrypted
// data to a file. The encryption key, and thus the data, is lost when the system shuts down.
type keyring struct {
	description, file string
	key               []byte
	keyID, ringID     int
}

// storage creates a platform-specific accessor for Linux
func storage(cachePath string) (accessor.Accessor, error) {
	return newKeyring(cachePath)
}

func newKeyring(p string) (*keyring, error) {
	// the user keyring is available to all processes owned by the user whereas the user
	// *session* keyring is available only to processes in the current session i.e. shell
	ringID, err := unix.KeyctlGetKeyringID(unix.KEY_SPEC_USER_KEYRING, true)
	if err != nil {
		return nil, fmt.Errorf("couldn't get the user keyring due to error %q", err)
	}
	// Link the session keyring to the user keyring so the process possesses any key[ring] it links
	// to the user keyring and thereby has permission to read/write/search them (see the "Possession"
	// section of the keyrings man page). This step isn't always necessary but in some cases prevents
	// weirdness such as a process adding keys it can't read. Ignore errors because failure here
	// doesn't guarantee this process can't perform all required operations on the user keyring.
	if sessionID, err := unix.KeyctlGetKeyringID(unix.KEY_SPEC_SESSION_KEYRING, true); err == nil {
		_, _ = unix.KeyctlInt(unix.KEYCTL_LINK, ringID, sessionID, 0, 0)
	}
	// Attempt to link a persistent keyring to the user keyring. This keyring is persistent in that
	// its linked keys survive all the user's login sessions being deleted but like all user keys,
	// they exist only in memory and are therefore lost on system shutdown. If the attempt fails
	// (some systems don't support persistent keyrings) continue with the user keyring.
	if persistentRing, err := unix.KeyctlInt(unix.KEYCTL_GET_PERSISTENT, -1, ringID, 0, 0); err == nil {
		ringID = persistentRing
	}
	// Use the actual filename as the keyring description to ensure each file has its own encryption key
	description := filepath.Base(p)
	return &keyring{description: description, file: p, ringID: ringID}, nil
}

func (k *keyring) Delete(context.Context) error {
	if k.keyID != 0 && k.ringID != 0 {
		_, err := unix.KeyctlInt(unix.KEYCTL_UNLINK, k.keyID, k.ringID, 0, 0)
		if err != nil && !isKeyInvalidOrNotFound(err) {
			return fmt.Errorf("failed to delete cache data due to error %q", err)
		}
	}
	err := os.Remove(k.file)
	if errors.Is(err, os.ErrNotExist) {
		return nil
	}
	return err
}

func (k *keyring) Read(context.Context) ([]byte, error) {
	b, err := os.ReadFile(k.file)
	if err != nil {
		if errors.Is(err, os.ErrNotExist) {
			return nil, nil
		}
		return nil, fmt.Errorf("failed to read cache data due to error %q", err)
	}
	if len(b) == 0 {
		return nil, nil
	}
	j, err := jwe.ParseCompactFormat(b)
	if err != nil {
		return nil, fmt.Errorf("couldn't parse cache data due to error %q", err)
	}
	plaintext, err := k.decrypt(j)
	return plaintext, err
}

func (k *keyring) Write(_ context.Context, data []byte) error {
	if len(data) == 0 {
		return nil
	}
	j, err := k.encrypt(data)
	if err != nil {
		return err
	}
	content, err := j.Serialize()
	if err != nil {
		return fmt.Errorf("couldn't serialize cache data due to error %q", err)
	}
	err = os.WriteFile(k.file, []byte(content), 0600)
	if errors.Is(err, os.ErrNotExist) {
		err = os.MkdirAll(filepath.Dir(k.file), 0700)
		if err == nil {
			err = os.WriteFile(k.file, []byte(content), 0600)
		}
	}
	return err
}

func (k *keyring) createKey() ([]byte, error) {
	// allocate an extra byte because keyring payloads must have a null terminator
	key := make([]byte, keySize+1)
	_, err := rand.Read(key)
	if err != nil {
		return nil, fmt.Errorf("couldn't create cache encryption key due to error %q", err)
	}
	key[keySize] = 0
	id, err := unix.AddKey(userKey, k.description, key, k.ringID)
	if err != nil {
		return nil, fmt.Errorf("couldn't store cache encryption key due to error %q", err)
	}
	k.key = key[:keySize]
	k.keyID = id
	return k.key, nil
}

func (k *keyring) decrypt(j jwe.JWE) ([]byte, error) {
	for tries := 0; tries < 2; tries++ {
		key, err := k.getKey()
		if err != nil {
			if err == unix.ENOKEY {
				return nil, nil
			}
			return nil, err
		}
		plaintext, err := j.Decrypt(key)
		if err == nil {
			return plaintext, nil
		}
		// try again, getting the key from the keyring first in case it was overwritten
		// by the user (with keyctl) or another process (in a Write() race)
		k.key = nil
		k.keyID = 0
	}
	// data is unreadable; the next Write will overwrite the file
	return nil, nil
}

func (k *keyring) encrypt(data []byte) (jwe.JWE, error) {
	key, err := k.getKey()
	if isKeyInvalidOrNotFound(err) {
		key, err = k.createKey()
	}
	if err != nil {
		return jwe.JWE{}, fmt.Errorf("couldn't get cache encryption key due to error %q", err)
	}
	alg, err := aescbc.NewAES128CBCHMACSHA256(key)
	if err != nil {
		return jwe.JWE{}, err
	}
	return jwe.Encrypt(data, fmt.Sprint(k.keyID), alg)
}

func (k *keyring) getKey() ([]byte, error) {
	if k.key != nil {
		// we created, or got, the key earlier
		return k.key, nil
	}
	if k.keyID == 0 {
		// search for a key matching the description i.e. the cache name
		keyID, err := unix.KeyctlSearch(k.ringID, userKey, k.description, 0)
		if err != nil {
			return nil, err
		}
		k.keyID = keyID
	}
	pl := make([]byte, keySize+1) // extra byte for the payload's null terminator
	_, err := unix.KeyctlBuffer(unix.KEYCTL_READ, k.keyID, pl, 0)
	if err != nil {
		return nil, err
	}
	k.key = pl[:keySize]
	return k.key, nil
}

func isKeyInvalidOrNotFound(err error) bool {
	return errors.Is(err, unix.EKEYEXPIRED) || errors.Is(err, unix.EKEYREVOKED) || errors.Is(err, unix.ENOENT) || errors.Is(err, unix.ENOKEY)
}

var _ accessor.Accessor = (*keyring)(nil)
0707010000006F000081A4000000000000000000000001691F8CFD00001D54000000000000000000000000000000000000003600000000kubelogin-0.2.13/pkg/internal/pop/cache/linux_test.go//go:build linux

package cache

import (
	"fmt"
	"os"
	"path/filepath"
	"testing"

	"github.com/google/uuid"
	"github.com/stretchr/testify/require"
)

func TestKeyExistsButNotFile(t *testing.T) {
	expected := []byte(t.Name())
	uniqueName := uuid.NewString()

	// Create a keyring accessor
	a, err := storage(uniqueName)
	require.NoError(t, err)

	// Write some data that's different from expected
	err = a.Write(ctx, append([]byte("not"), expected...))
	require.NoError(t, err)

	// Clean up keyring at end of test
	t.Cleanup(func() { require.NoError(t, a.Delete(ctx)) })

	// Remove the cache file but leave the keyring key
	kr := a.(*keyring)
	require.NoError(t, os.Remove(kr.file))

	// Create a new keyring instance with the same description
	// This should find the existing key but no file
	b, err := newKeyring(uniqueName)
	require.NoError(t, err)

	// Read should return nil since file doesn't exist
	data, err := b.Read(ctx)
	require.NoError(t, err)
	require.Nil(t, data)

	// Write should succeed and create a new file
	err = b.Write(ctx, expected)
	require.NoError(t, err)

	// Read should now return the expected data
	data, err = b.Read(ctx)
	require.NoError(t, err)
	require.Equal(t, expected, data)
}

func TestNewKeyring(t *testing.T) {
	tests := []struct {
		desc     string
		name     string
		expected []byte
	}{
		{
			desc:     "empty cache",
			name:     "",
			expected: nil,
		},
		{
			desc:     "non-empty cache",
			name:     "",
			expected: nil, // New cache should be empty
		},
		{
			desc:     "cache with existing encrypted file",
			name:     t.Name(),
			expected: nil, // Should return nil for corrupted/lost key scenario
		},
	}

	for _, test := range tests {
		t.Run(test.desc, func(t *testing.T) {
			name := test.name
			if name == "" {
				// Use UUID to ensure file and key don't exist
				name = uuid.NewString()
			} else {
				// Create a corrupted cache file to simulate lost key scenario
				tempDir := t.TempDir()
				p := filepath.Join(tempDir, name)
				err := os.MkdirAll(filepath.Dir(p), 0600)
				require.NoError(t, err)

				// Write some encrypted-looking data that can't be decrypted
				corruptedData := []byte("eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0..gPRNjqd4HcrlFxJdEEaFeA.Pqpr_IYG7e1lt6KPoE0v_A.i9h5iJWw9bT217I5M2Ufrg")
				err = os.WriteFile(p, corruptedData, 0600)
				require.NoError(t, err)
				name = p
			}

			k, err := newKeyring(name)
			require.NoError(t, err)
			require.NotNil(t, k)

			// Read should return nil for empty or corrupted cache
			actual, err := k.Read(ctx)
			require.NoError(t, err)
			require.Equal(t, test.expected, actual)

			// Clean up
			t.Cleanup(func() {
				if k.keyID != 0 {
					k.Delete(ctx)
				}
			})

			if test.name == "" {
				// Test that we can write to an empty cache
				testData := []byte("test write to empty cache")
				err = k.Write(ctx, testData)
				require.NoError(t, err)

				actual, err = k.Read(ctx)
				require.NoError(t, err)
				require.Equal(t, testData, actual)
			}
		})
	}
}

func TestKeyringDescription(t *testing.T) {
	// Test that different paths result in different keyring descriptions
	// This ensures each cache file gets its own encryption key
	testPaths := []string{
		"/tmp/cache1/pop_tokens.cache",
		"/tmp/cache2/tokens.cache",    // Different filename
		"/different/path/auth.cache",  // Different filename
		"relative/path/session.cache", // Different filename
	}

	descriptions := make(map[string]bool)

	for _, path := range testPaths {
		k, err := newKeyring(path)
		require.NoError(t, err)

		// Verify description is the filename
		expectedDesc := filepath.Base(path)
		require.Equal(t, expectedDesc, k.description)

		// Verify each path gets a unique description
		require.False(t, descriptions[k.description], "description %q should be unique", k.description)
		descriptions[k.description] = true
	}
}

func TestKeyringRoundTrip(t *testing.T) {
	uniqueName := uuid.NewString()
	k, err := newKeyring(uniqueName)
	require.NoError(t, err)

	testData := []byte("test keyring round trip data with special chars: éñ中文🚀")

	// Test write
	err = k.Write(ctx, testData)
	require.NoError(t, err)

	// Test read
	readData, err := k.Read(ctx)
	require.NoError(t, err)
	require.Equal(t, testData, readData)

	// Test that file exists and is encrypted
	if k.file != "" {
		fileContent, err := os.ReadFile(k.file)
		require.NoError(t, err)
		require.NotEqual(t, testData, fileContent, "file should be encrypted")
		require.Greater(t, len(fileContent), len(testData), "encrypted content should be longer")
	}

	// Test delete
	err = k.Delete(ctx)
	require.NoError(t, err)

	// Verify file is deleted
	if k.file != "" {
		_, err = os.Stat(k.file)
		require.True(t, os.IsNotExist(err), "file should be deleted")
	}

	// Read after delete should return nil
	readData, err = k.Read(ctx)
	require.NoError(t, err)
	require.Nil(t, readData)
}

func TestKeyringEmptyData(t *testing.T) {
	uniqueName := uuid.NewString()
	k, err := newKeyring(uniqueName)
	require.NoError(t, err)

	t.Cleanup(func() { k.Delete(ctx) })

	// Test writing empty data
	err = k.Write(ctx, []byte{})
	require.NoError(t, err)

	// Read should return nil for empty data
	readData, err := k.Read(ctx)
	require.NoError(t, err)
	require.Nil(t, readData)

	// Test writing nil data
	err = k.Write(ctx, nil)
	require.NoError(t, err)

	readData, err = k.Read(ctx)
	require.NoError(t, err)
	require.Nil(t, readData)
}

func TestKeyringNonExistentFile(t *testing.T) {
	uniqueName := uuid.NewString()
	k, err := newKeyring(uniqueName)
	require.NoError(t, err)

	// Reading non-existent file should return nil
	readData, err := k.Read(ctx)
	require.NoError(t, err)
	require.Nil(t, readData)

	// Deleting non-existent file/key should not error
	err = k.Delete(ctx)
	require.NoError(t, err)
}

func TestKeyringProcessIsolation(t *testing.T) {
	// Test that different keyring descriptions (representing different cache files)
	// don't interfere with each other - simulating multiple kubelogin processes
	// with different cache files
	baseName := uuid.NewString()

	keyrings := make([]*keyring, 3)
	testData := make([][]byte, 3)

	// Create multiple keyrings with different names (different cache files)
	for i := 0; i < 3; i++ {
		name := fmt.Sprintf("%s_%d", baseName, i)
		k, err := newKeyring(name)
		require.NoError(t, err)
		keyrings[i] = k

		testData[i] = []byte(fmt.Sprintf("process_%d_data", i))
	}

	// Each keyring should be able to store and retrieve its own data
	for i, k := range keyrings {
		err := k.Write(ctx, testData[i])
		require.NoError(t, err)
	}

	// Verify each keyring can read back its own data correctly
	for i, k := range keyrings {
		readData, err := k.Read(ctx)
		require.NoError(t, err)
		require.Equal(t, testData[i], readData)
	}

	// Clean up
	for _, k := range keyrings {
		err := k.Delete(ctx)
		require.NoError(t, err)
	}
}

func TestKeyringDirectoryCreation(t *testing.T) {
	tempDir := t.TempDir()

	// Test with nested directory that doesn't exist
	nestedPath := filepath.Join(tempDir, "deep", "nested", "path", "cache.data")
	k, err := newKeyring(nestedPath)
	require.NoError(t, err)

	testData := []byte("test directory creation")
	err = k.Write(ctx, testData)
	require.NoError(t, err)

	// Verify directory was created
	require.DirExists(t, filepath.Dir(nestedPath))

	// Verify data can be read back
	readData, err := k.Read(ctx)
	require.NoError(t, err)
	require.Equal(t, testData, readData)

	// Clean up
	err = k.Delete(ctx)
	require.NoError(t, err)
}
07070100000070000081A4000000000000000000000001691F8CFD00000119000000000000000000000000000000000000003300000000kubelogin-0.2.13/pkg/internal/pop/cache/windows.go//go:build windows

package cache

import (
	"github.com/AzureAD/microsoft-authentication-extensions-for-go/cache/accessor"
)

// storage creates a platform-specific accessor for Windows
func storage(cachePath string) (accessor.Accessor, error) {
	return accessor.New(cachePath)
}
07070100000071000081A4000000000000000000000001691F8CFD00000F80000000000000000000000000000000000000003700000000kubelogin-0.2.13/pkg/internal/pop/msal_confidential.gopackage pop

import (
	"context"
	"fmt"
	"net/http"

	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
	"github.com/AzureAD/microsoft-authentication-library-for-go/apps/cache"
	"github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential"
)

type MsalClientOptions struct {
	Authority                string
	ClientID                 string
	TenantID                 string
	DisableInstanceDiscovery bool
	Options                  azcore.ClientOptions
}

// ConfidentialClientOptions holds options for creating a confidential client
type ConfidentialClientOptions struct {
	Cache cache.ExportReplace
}

// ConfidentialClientOption defines a functional option for configuring a confidential client
type ConfidentialClientOption func(*ConfidentialClientOptions)

// WithCustomCacheConfidential adds a custom cache to the confidential client
func WithCustomCacheConfidential(cache cache.ExportReplace) ConfidentialClientOption {
	return func(opts *ConfidentialClientOptions) {
		opts.Cache = cache
	}
}

// NewConfidentialClient creates a new confidential client with default options
func NewConfidentialClient(
	cred confidential.Credential,
	msalOptions *MsalClientOptions,
	options ...ConfidentialClientOption,
) (confidential.Client, error) {
	if msalOptions == nil {
		return confidential.Client{}, fmt.Errorf("unable to create confidential client: msalClientOptions is empty")
	}

	// Apply custom options
	clientOpts := &ConfidentialClientOptions{}
	for _, option := range options {
		option(clientOpts)
	}

	// Build confidential options
	var confOptions []confidential.Option
	confOptions = append(confOptions,
		confidential.WithX5C(),
		confidential.WithInstanceDiscovery(!msalOptions.DisableInstanceDiscovery),
	)

	// Add HTTP client if present in msalOptions
	if msalOptions.Options.Transport != nil {
		client, ok := msalOptions.Options.Transport.(*http.Client)
		if !ok {
			return confidential.Client{}, fmt.Errorf("unable to create confidential client: msalOptions.Options.Transport is not an *http.Client")
		}
		confOptions = append(confOptions,
			confidential.WithHTTPClient(client),
		)
	}

	// Add cache if specified
	if clientOpts.Cache != nil {
		confOptions = append(confOptions, confidential.WithCache(clientOpts.Cache))
	}

	client, err := confidential.New(
		msalOptions.Authority,
		msalOptions.ClientID,
		cred,
		confOptions...,
	)

	if err != nil {
		return confidential.Client{}, fmt.Errorf("unable to create confidential client: %w", err)
	}

	return client, nil
}

// AcquirePoPTokenConfidential acquires a PoP token using MSAL's confidential login flow.
// It first tries to acquire a token silently from cache, and only falls back to credential-based login if needed.
// Uses the provided PoP key for token acquisition and caching.
// This flow does not require user interaction as the credentials for the request have already been provided.
func AcquirePoPTokenConfidential(
	ctx context.Context,
	popClaims map[string]string,
	scopes []string,
	client confidential.Client,
	tenantID string,
	popKey PoPKey,
) (string, int64, error) {

	authnScheme := &PoPAuthenticationScheme{
		Host:   popClaims["u"],
		PoPKey: popKey,
	}

	// Try silent token acquisition first
	result, err := client.AcquireTokenSilent(
		ctx,
		scopes,
		confidential.WithAuthenticationScheme(authnScheme),
		confidential.WithTenantID(tenantID),
	)
	if err == nil {
		return result.AccessToken, result.ExpiresOn.Unix(), nil
	}

	// Silent acquisition failed - proceed to credential-based acquisition
	// Note: For confidential clients (service principals), MSAL will handle cache updates automatically
	result, err = client.AcquireTokenByCredential(
		ctx,
		scopes,
		confidential.WithAuthenticationScheme(authnScheme),
		confidential.WithTenantID(tenantID),
	)
	if err != nil {
		return "", -1, fmt.Errorf("failed to create service principal PoP token using credential: %w", err)
	}

	return result.AccessToken, result.ExpiresOn.Unix(), nil
}
07070100000072000081A4000000000000000000000001691F8CFD000011EF000000000000000000000000000000000000003C00000000kubelogin-0.2.13/pkg/internal/pop/msal_confidential_test.gopackage pop

import (
	"context"
	"fmt"
	"os"
	"testing"

	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
	"github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud"
	"github.com/Azure/kubelogin/pkg/internal/testutils"
	"github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential"
	"github.com/golang-jwt/jwt/v4"
)

type confidentialTokenVars struct {
	clientID     string
	clientSecret string
	resourceID   string
	tenantID     string
	cloud        cloud.Configuration
	popClaims    map[string]string
}

func TestAcquirePoPTokenConfidential(t *testing.T) {
	pEnv := &confidentialTokenVars{
		clientID:     os.Getenv(testutils.ClientID),
		clientSecret: os.Getenv(testutils.ClientSecret),
		resourceID:   os.Getenv(testutils.ResourceID),
		tenantID:     os.Getenv(testutils.TenantID),
	}
	// Use defaults if environmental variables are empty
	if pEnv.clientID == "" {
		pEnv.clientID = testutils.TestClientID
	}
	if pEnv.clientSecret == "" {
		pEnv.clientSecret = testutils.ClientSecret
	}
	if pEnv.resourceID == "" {
		pEnv.resourceID = testutils.TestServerID
	}
	if pEnv.tenantID == "" {
		pEnv.tenantID = testutils.TestTenantID
	}
	ctx := context.Background()
	scopes := []string{pEnv.resourceID + "/.default"}
	authority := "https://login.microsoftonline.com/" + pEnv.tenantID
	var expectedToken string
	var token string
	expectedTokenType := "pop"
	testCase := []struct {
		cassetteName  string
		p             *confidentialTokenVars
		expectedError error
		useSecret     bool
	}{
		{
			// Test using bad client secret
			cassetteName: "AcquirePoPTokenConfidentialFromBadSecretVCR",
			p: &confidentialTokenVars{
				clientID:     pEnv.clientID,
				clientSecret: testutils.BadSecret,
				resourceID:   pEnv.resourceID,
				tenantID:     pEnv.tenantID,
				popClaims:    map[string]string{"u": "testhost"},
				cloud: cloud.Configuration{
					ActiveDirectoryAuthorityHost: authority,
				},
			},
			expectedError: fmt.Errorf("failed to create service principal PoP token using credential"),
			useSecret:     true,
		},
		{
			// Test using service principal secret value to get PoP token
			cassetteName: "AcquirePoPTokenConfidentialWithSecretVCR",
			p: &confidentialTokenVars{
				clientID:     pEnv.clientID,
				clientSecret: pEnv.clientSecret,
				resourceID:   pEnv.resourceID,
				tenantID:     pEnv.tenantID,
				popClaims:    map[string]string{"u": "testhost"},
				cloud: cloud.Configuration{
					ActiveDirectoryAuthorityHost: authority,
				},
			},
			expectedError: nil,
			useSecret:     true,
		},
	}

	for _, tc := range testCase {
		t.Run(tc.cassetteName, func(t *testing.T) {
			if tc.expectedError == nil {
				expectedToken = testutils.TestToken
			}
			vcrRecorder, err := testutils.GetVCRHttpClient(fmt.Sprintf("testdata/%s", tc.cassetteName), pEnv.tenantID)
			if err != nil {
				t.Fatalf("failed to create vcr recorder: %s", err)
			}

			cred, err := confidential.NewCredFromSecret(tc.p.clientSecret)
			if err != nil {
				t.Errorf("expected no error creating credential but got: %s", err)
			}

			MsalClientOptions := &MsalClientOptions{
				Authority: authority,
				ClientID:  tc.p.clientID,
				TenantID:  tc.p.tenantID,
				Options: azcore.ClientOptions{
					Cloud:     cloud.AzurePublic,
					Transport: vcrRecorder.GetDefaultClient(),
				},
				DisableInstanceDiscovery: false,
			}

			client, err := NewConfidentialClient(cred, MsalClientOptions)
			if err != nil {
				t.Errorf("expected no error creating client but got: %s", err)
			}

			popKey, err := GetSwPoPKeyPersistent("/tmp/test_cache")
			if err != nil {
				t.Errorf("expected no error getting PoP key but got: %s", err)
			}

			token, _, err = AcquirePoPTokenConfidential(
				ctx,
				tc.p.popClaims,
				scopes,
				client,
				tc.p.tenantID,
				popKey,
			)
			defer vcrRecorder.Stop()
			if tc.expectedError != nil {
				if !testutils.ErrorContains(err, tc.expectedError.Error()) {
					t.Errorf("expected error %s, but got %s", tc.expectedError.Error(), err)
				}
			} else if err != nil {
				t.Errorf("expected no error, but got: %s", err)
			} else {
				if token == "" {
					t.Error("expected valid token, but received empty token.")
				}
				claims := jwt.MapClaims{}
				parsed, _ := jwt.ParseWithClaims(token, &claims, nil)
				if claims["at"] != expectedToken {
					t.Errorf("unexpected token returned (expected %s, but got %s)", expectedToken, claims["at"])
				}
				if parsed.Header["typ"] != expectedTokenType {
					t.Errorf("unexpected token returned (expected %s, but got %s)", expectedTokenType, parsed.Header["typ"])
				}
			}
		})
	}
}
07070100000073000081A4000000000000000000000001691F8CFD00001BA3000000000000000000000000000000000000003100000000kubelogin-0.2.13/pkg/internal/pop/msal_public.gopackage pop

import (
	"context"
	"fmt"
	"net/http"

	"github.com/AzureAD/microsoft-authentication-library-for-go/apps/cache"
	"github.com/AzureAD/microsoft-authentication-library-for-go/apps/public"
)

// PublicClientOptions holds options for creating a public client
type PublicClientOptions struct {
	Cache cache.ExportReplace
}

// PublicClientOption defines a functional option for configuring a public client
type PublicClientOption func(*PublicClientOptions)

// WithCustomCachePublic adds a custom cache to the confidential client
func WithCustomCachePublic(cache cache.ExportReplace) PublicClientOption {
	return func(opts *PublicClientOptions) {
		opts.Cache = cache
	}
}

// NewPublicClient creates a new public client with default options
func NewPublicClient(
	msalOptions *MsalClientOptions,
	options ...PublicClientOption,
) (public.Client, error) {
	if msalOptions == nil {
		return public.Client{}, fmt.Errorf("unable to create public client: msalClientOptions is empty")
	}

	// Apply custom options
	clientOpts := &PublicClientOptions{}
	for _, option := range options {
		option(clientOpts)
	}

	// Build public options
	var publicOptions []public.Option
	publicOptions = append(publicOptions,
		public.WithInstanceDiscovery(!msalOptions.DisableInstanceDiscovery),
		public.WithAuthority(msalOptions.Authority),
	)

	// Add HTTP client if present in msalOptions
	if msalOptions.Options.Transport != nil {
		client, ok := msalOptions.Options.Transport.(*http.Client)
		if !ok {
			return public.Client{}, fmt.Errorf("unable to create public client: msalOptions.Options.Transport is not an *http.Client")
		}
		publicOptions = append(publicOptions,
			public.WithHTTPClient(client),
		)
	}

	// Add cache if specified
	if clientOpts.Cache != nil {
		publicOptions = append(publicOptions, public.WithCache(clientOpts.Cache))
	}

	client, err := public.New(
		msalOptions.ClientID,
		publicOptions...,
	)

	if err != nil {
		return public.Client{}, fmt.Errorf("unable to create public client: %w", err)
	}

	return client, nil
}

// AcquirePoPTokenInteractive acquires a PoP token using MSAL's interactive login flow with caching.
// First attempts silent token acquisition if a single account is cached.
// Uses the provided PoP key for proper token caching.
// Falls back to interactive authentication if silent acquisition fails or no accounts are cached.
func AcquirePoPTokenInteractive(
	ctx context.Context,
	popClaims map[string]string,
	scopes []string,
	client public.Client,
	msalOptions *MsalClientOptions,
	popKey PoPKey,
) (string, int64, error) {

	authnScheme := &PoPAuthenticationScheme{
		Host:   popClaims["u"],
		PoPKey: popKey,
	}

	// Try silent token acquisition first if accounts exist
	accounts, err := client.Accounts(ctx)
	if err == nil && len(accounts) > 0 {
		// Use the first account for silent acquisition (single-user cache)
		account := accounts[0]
		result, err := client.AcquireTokenSilent(
			ctx,
			scopes,
			public.WithSilentAccount(account),
			public.WithAuthenticationScheme(authnScheme),
			public.WithTenantID(msalOptions.TenantID),
		)
		if err == nil {
			return result.AccessToken, result.ExpiresOn.Unix(), nil
		}

		// Silent acquisition failed - clear cache to ensure single-user behavior
		// This handles token expiration, user switching, and cache corruption
		clearErr := clearAllAccounts(ctx, client)
		if clearErr != nil {
			return "", -1, fmt.Errorf("failed to clear cache after silent acquisition failure: %w", clearErr)
		}
	}

	// Interactive login (first time or after cache cleared due to silent acquisition failure)
	result, err := client.AcquireTokenInteractive(
		ctx,
		scopes,
		public.WithAuthenticationScheme(authnScheme),
		public.WithTenantID(msalOptions.TenantID),
	)
	if err != nil {
		return "", -1, fmt.Errorf("failed to create PoP token with interactive flow: %w", err)
	}

	return result.AccessToken, result.ExpiresOn.Unix(), nil
}

// AcquirePoPTokenByUsernamePassword acquires a PoP token using MSAL's username/password login flow with user-specific caching.
// It first tries to acquire a token silently from cache for the specific username, and only falls back to username/password login if needed.
// Uses the provided PoP key for proper token caching. If the cache contains tokens for a different user,
// it clears the cache and authenticates with the provided credentials.
// This flow does not require user interaction as credentials have already been provided.
func AcquirePoPTokenByUsernamePassword(
	ctx context.Context,
	popClaims map[string]string,
	scopes []string,
	client public.Client,
	username,
	password string,
	msalOptions *MsalClientOptions,
	popKey PoPKey,
) (string, int64, error) {

	authnScheme := &PoPAuthenticationScheme{
		Host:   popClaims["u"],
		PoPKey: popKey,
	}

	// Try silent token acquisition first if accounts exist for the specific username
	targetAccount, err := findAccountByUsername(ctx, client, username)
	if err == nil && targetAccount != nil {
		// Try silent acquisition with the matching account
		result, err := client.AcquireTokenSilent(
			ctx,
			scopes,
			public.WithSilentAccount(*targetAccount),
			public.WithAuthenticationScheme(authnScheme),
			public.WithTenantID(msalOptions.TenantID),
		)
		if err == nil {
			return result.AccessToken, result.ExpiresOn.Unix(), nil
		}

		// Silent acquisition failed - clear cache to ensure clean state for username/password authentication
		clearErr := clearAllAccounts(ctx, client)
		if clearErr != nil {
			return "", -1, fmt.Errorf("failed to clear cache before username/password authentication: %w", clearErr)
		}
	}

	// Username/password login (first time, user switch, or after cache cleared due to silent acquisition failure)
	result, err := client.AcquireTokenByUsernamePassword(
		ctx,
		scopes,
		username,
		password,
		public.WithAuthenticationScheme(authnScheme),
		public.WithTenantID(msalOptions.TenantID),
	)
	if err != nil {
		return "", -1, fmt.Errorf("failed to create PoP token with username/password flow: %w", err)
	}

	return result.AccessToken, result.ExpiresOn.Unix(), nil
}

// findAccountByUsername searches for a cached account with the specified username.
// Returns the account if found, nil otherwise.
func findAccountByUsername(ctx context.Context, client public.Client, username string) (*public.Account, error) {
	accounts, err := client.Accounts(ctx)
	if err != nil {
		return nil, err
	}

	for _, account := range accounts {
		if account.PreferredUsername == username {
			return &account, nil
		}
	}
	return nil, nil
}

// clearAllAccounts removes all cached accounts from the MSAL client.
// This is used to implement single-user caching where only the latest authenticated user is cached.
func clearAllAccounts(ctx context.Context, client public.Client) error {
	accounts, err := client.Accounts(ctx)
	if err != nil {
		return err
	}

	for _, account := range accounts {
		err = client.RemoveAccount(ctx, account)
		if err != nil {
			return fmt.Errorf("failed to remove account %s: %w", account.PreferredUsername, err)
		}
	}
	return nil
}
07070100000074000081A4000000000000000000000001691F8CFD00001F7E000000000000000000000000000000000000003600000000kubelogin-0.2.13/pkg/internal/pop/msal_public_test.gopackage pop

import (
	"context"
	"fmt"
	"net/http"
	"os"
	"testing"

	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
	"github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud"
	"github.com/Azure/kubelogin/pkg/internal/testutils"
	"github.com/golang-jwt/jwt/v4"
)

type resourceOwnerTokenVars struct {
	clientID   string
	username   string
	password   string
	resourceID string
	tenantID   string
	popClaims  map[string]string
}

func TestAcquirePoPTokenByUsernamePassword(t *testing.T) {
	pEnv := &resourceOwnerTokenVars{
		clientID: os.Getenv(testutils.ClientID),
		username: os.Getenv(testutils.Username),
		password: os.Getenv(testutils.Password),
		tenantID: os.Getenv(testutils.TenantID),
	}
	// Use defaults if environmental variables are empty
	if pEnv.clientID == "" {
		pEnv.clientID = testutils.TestClientID
	}
	if pEnv.username == "" {
		pEnv.username = testutils.TestUsername
	}
	if pEnv.password == "" {
		pEnv.password = testutils.TestPassword
	}
	if pEnv.tenantID == "" {
		pEnv.tenantID = testutils.TestTenantID
	}

	ctx := context.Background()
	scopes := []string{testutils.TestServerID + "/.default"}
	authority := "https://login.microsoftonline.com/" + pEnv.tenantID
	var expectedToken string
	expectedTokenType := "pop"
	testCase := []struct {
		cassetteName  string
		p             *resourceOwnerTokenVars
		expectedError error
	}{
		{
			// Test using bad password
			cassetteName: "AcquirePoPTokenByUsernamePasswordFromBadPasswordVCR",
			p: &resourceOwnerTokenVars{
				clientID:   pEnv.clientID,
				username:   pEnv.username,
				password:   testutils.BadSecret,
				resourceID: testutils.TestServerID,
				tenantID:   pEnv.tenantID,
				popClaims:  map[string]string{"u": "testhost"},
			},
			expectedError: fmt.Errorf("failed to create PoP token with username/password flow"),
		},
		{
			// Test using username/password to get PoP token
			cassetteName: "AcquirePoPTokenByUsernamePasswordVCR",
			p: &resourceOwnerTokenVars{
				clientID:   pEnv.clientID,
				username:   pEnv.username,
				password:   pEnv.password,
				resourceID: testutils.TestServerID,
				tenantID:   pEnv.tenantID,
				popClaims:  map[string]string{"u": "testhost"},
			},
			expectedError: nil,
		},
	}

	for _, tc := range testCase {
		t.Run(tc.cassetteName, func(t *testing.T) {
			if tc.expectedError == nil {
				expectedToken = testutils.TestToken
			}
			vcrRecorder, err := testutils.GetVCRHttpClient(fmt.Sprintf("testdata/%s", tc.cassetteName), pEnv.tenantID)
			if err != nil {
				t.Fatalf("failed to create vcr recorder: %s", err)
			}

			msalClientOptions := &MsalClientOptions{
				Authority: authority,
				ClientID:  tc.p.clientID,
				Options: azcore.ClientOptions{
					Cloud:     cloud.AzurePublic,
					Transport: vcrRecorder.GetDefaultClient(),
				},
				TenantID: tc.p.tenantID,
			}
			client, err := NewPublicClient(msalClientOptions)
			if err != nil {
				t.Errorf("expected no error creating client but got: %s", err)
			}

			popKey, err := GetSwPoPKeyPersistent("/tmp/test_cache")
			if err != nil {
				t.Errorf("expected no error getting PoP key but got: %s", err)
			}

			token, _, err := AcquirePoPTokenByUsernamePassword(
				ctx,
				tc.p.popClaims,
				scopes,
				client,
				tc.p.username,
				tc.p.password,
				msalClientOptions,
				popKey,
			)
			defer vcrRecorder.Stop()
			if tc.expectedError != nil {
				if !testutils.ErrorContains(err, tc.expectedError.Error()) {
					t.Errorf("expected error %s, but got %s", tc.expectedError.Error(), err)
				}
			} else if err != nil {
				t.Errorf("expected no error, but got: %s", err)
			} else {
				if token == "" {
					t.Error("expected valid token, but received empty token.")
				}
				claims := jwt.MapClaims{}
				parsed, _ := jwt.ParseWithClaims(token, &claims, nil)
				if claims["at"] != expectedToken {
					t.Errorf("unexpected token returned (expected %s, but got %s)", expectedToken, claims["at"])
				}
				if parsed.Header["typ"] != expectedTokenType {
					t.Errorf("unexpected token returned (expected %s, but got %s)", expectedTokenType, parsed.Header["typ"])
				}
			}
		})
	}
}

func TestFindAccountByUsername(t *testing.T) {
	ctx := context.Background()

	// Create a test client
	msalClientOptions := &MsalClientOptions{
		Authority: "https://login.microsoftonline.com/" + testutils.TestTenantID,
		ClientID:  testutils.TestClientID,
		Options: azcore.ClientOptions{
			Cloud: cloud.AzurePublic,
		},
		TenantID: testutils.TestTenantID,
	}

	client, err := NewPublicClient(msalClientOptions)
	if err != nil {
		t.Fatalf("failed to create public client: %s", err)
	}

	// Test with no accounts (fresh client)
	account, err := findAccountByUsername(ctx, client, "user1@example.com")
	if err != nil {
		t.Errorf("findAccountByUsername returned error: %s", err)
	}
	if account != nil {
		t.Errorf("expected no account found, but got %+v", account)
	}

	// Test with non-existent username (should not find anything)
	account, err = findAccountByUsername(ctx, client, "nonexistent@example.com")
	if err != nil {
		t.Errorf("findAccountByUsername returned error: %s", err)
	}
	if account != nil {
		t.Errorf("expected no account found for nonexistent user, but got %+v", account)
	}
}

func TestClearAllAccounts(t *testing.T) {
	ctx := context.Background()

	// Create a test client
	msalClientOptions := &MsalClientOptions{
		Authority: "https://login.microsoftonline.com/" + testutils.TestTenantID,
		ClientID:  testutils.TestClientID,
		Options: azcore.ClientOptions{
			Cloud: cloud.AzurePublic,
		},
		TenantID: testutils.TestTenantID,
	}

	client, err := NewPublicClient(msalClientOptions)
	if err != nil {
		t.Fatalf("failed to create public client: %s", err)
	}

	// Get initial account count
	initialAccounts, err := client.Accounts(ctx)
	if err != nil {
		t.Errorf("error getting initial accounts: %s", err)
	}
	t.Logf("Initial accounts: %d", len(initialAccounts))

	// Clear all accounts
	err = clearAllAccounts(ctx, client)
	if err != nil {
		t.Errorf("clearAllAccounts returned error: %s", err)
	}

	// Verify accounts are cleared
	finalAccounts, err := client.Accounts(ctx)
	if err != nil {
		t.Errorf("error getting final accounts: %s", err)
	}

	if len(finalAccounts) != 0 {
		t.Errorf("expected 0 accounts after clearing, but got %d", len(finalAccounts))
	}
}

func TestGetPublicClient(t *testing.T) {
	httpClient := &http.Client{}
	authority := "https://login.microsoftonline.com/" + testutils.TenantID

	testCase := []struct {
		testName      string
		msalOptions   *MsalClientOptions
		expectedError error
	}{
		{
			// Test using custom HTTP transport
			testName: "TestGetPublicClientWithCustomTransport",
			msalOptions: &MsalClientOptions{
				Authority: authority,
				ClientID:  testutils.ClientID,
				Options: azcore.ClientOptions{
					Cloud:     cloud.AzurePublic,
					Transport: httpClient,
				},
				TenantID: testutils.TenantID,
			},
			expectedError: nil,
		},
		{
			// Test using default HTTP transport
			testName: "TestGetPublicClientWithDefaultTransport",
			msalOptions: &MsalClientOptions{
				Authority: authority,
				ClientID:  testutils.ClientID,
				Options: azcore.ClientOptions{
					Cloud: cloud.AzurePublic,
				},
				TenantID: testutils.TenantID,
			},
			expectedError: nil,
		},
		{
			// Test using incorrectly formatted authority
			testName: "TestGetPublicClientWithBadAuthority",
			msalOptions: &MsalClientOptions{
				Authority: "login.microsoft.com",
				ClientID:  testutils.ClientID,
				Options: azcore.ClientOptions{
					Cloud: cloud.AzurePublic,
				},
				TenantID: testutils.TenantID,
			},
			expectedError: fmt.Errorf("unable to create public client"),
		},
	}

	for _, tc := range testCase {
		t.Run(tc.testName, func(t *testing.T) {
			_, err := NewPublicClient(tc.msalOptions)
			if tc.expectedError != nil {
				if !testutils.ErrorContains(err, tc.expectedError.Error()) {
					t.Errorf("expected error %s, but got %s", tc.expectedError.Error(), err)
				}
			} else if err != nil {
				t.Errorf("expected no error creating client, but got: %s", err.Error())
			}
		})
	}
}
07070100000075000081A4000000000000000000000001691F8CFD00002097000000000000000000000000000000000000002E00000000kubelogin-0.2.13/pkg/internal/pop/poptoken.gopackage pop

import (
	"context"
	"crypto"
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha256"
	"crypto/x509"
	"encoding/base64"
	"encoding/pem"
	"fmt"
	"math/big"
	"os"
	"path/filepath"

	"github.com/Azure/kubelogin/pkg/internal/pop/cache"
)

const popKeyFileName = "pop_rsa_key.cache"

// PoPKey is a generic interface for PoP key properties and methods
type PoPKey interface {
	// encryption/signature algo
	Alg() string
	// kid
	KeyID() string
	// jwk that can be embedded in JWT w/ PoP token's cnf claim
	JWK() string
	// https://tools.ietf.org/html/rfc7638 compliant jwk thumbprint
	JWKThumbprint() string
	// req_cnf claim that can be included in access token request to AAD
	ReqCnf() string
	// sign payload using private key
	Sign([]byte) ([]byte, error)
}

// software based pop key implementation of PoPKey
type SwKey struct {
	key    *rsa.PrivateKey
	keyID  string
	jwk    string
	jwkTP  string
	reqCnf string
}

// Alg returns the algorithm used to encrypt/sign the SwKey
func (swk *SwKey) Alg() string {
	return "RS256"
}

// KeyID returns the keyID of the SwKey, representing the key used to sign the SwKey
func (swk *SwKey) KeyID() string {
	return swk.keyID
}

// JWK returns the JSON Web Key of the given SwKey
func (swk *SwKey) JWK() string {
	return swk.jwk
}

// JWKThumbprint returns the JWK thumbprint of the given SwKey
func (swk *SwKey) JWKThumbprint() string {
	return swk.jwkTP
}

// ReqCnf returns the req_cnf claim to send to AAD for the given SwKey
func (swk *SwKey) ReqCnf() string {
	return swk.reqCnf
}

// Sign uses the given SwKey to sign the given payload and returns the signed payload
func (swk *SwKey) Sign(payload []byte) ([]byte, error) {
	return swk.key.Sign(rand.Reader, payload, crypto.SHA256)
}

// init initializes the given SwKey using the given private key
func (swk *SwKey) init(key *rsa.PrivateKey) {
	swk.key = key

	eB64, nB64 := getRSAKeyExponentAndModulus(key)
	swk.jwkTP = computeJWKThumbprint(eB64, nB64)
	swk.reqCnf = getReqCnf(swk.jwkTP)

	// set keyID to jwkTP
	swk.keyID = swk.jwkTP

	// compute JWK to be included in JWT w/ PoP token's cnf claim
	// - https://tools.ietf.org/html/rfc7800#section-3.2
	swk.jwk = getJWK(eB64, nB64, swk.keyID)
}

// generateSwKey generates a new SwKey and initializes it with required fields before returning it
func generateSwKey(key *rsa.PrivateKey) (*SwKey, error) {
	swk := &SwKey{}
	swk.init(key)
	return swk, nil
}

// GetSwPoPKey generates a new PoP key returns it
func GetSwPoPKey() (*SwKey, error) {
	key, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		return nil, fmt.Errorf("error generating RSA private key: %w", err)
	}
	return GetSwPoPKeyWithRSAKey(key)
}

// GetSwPoPKeyPersistent loads or generates a persistent PoP key for token caching.
// This ensures the same PoP key is used across multiple kubelogin invocations,
// which is required for PoP token caching with MSAL to work correctly.
//
// This implementation uses platform-specific secure storage exclusively:
// - Linux: Kernel keyrings with encrypted files
// - macOS: macOS Keychain
// - Windows: Windows Credential Manager
func GetSwPoPKeyPersistent(cacheDir string) (*SwKey, error) {
	key, err := loadOrGenerateRSAKey(cacheDir)
	if err != nil {
		return nil, fmt.Errorf("error loading or generating persistent RSA private key from secure storage: %w", err)
	}
	return GetSwPoPKeyWithRSAKey(key)
}

func GetSwPoPKeyWithRSAKey(rsaKey *rsa.PrivateKey) (*SwKey, error) {
	key, err := generateSwKey(rsaKey)
	if err != nil {
		return nil, fmt.Errorf("unable to generate PoP key. err: %w", err)
	}
	return key, nil
}

// getRSAKeyExponentAndModulus returns the exponent and modulus from the given RSA key
// as base-64 encoded strings
func getRSAKeyExponentAndModulus(rsaKey *rsa.PrivateKey) (string, string) {
	pubKey := rsaKey.PublicKey
	e := big.NewInt(int64(pubKey.E))
	eB64 := base64.RawURLEncoding.EncodeToString(e.Bytes())
	n := pubKey.N
	nB64 := base64.RawURLEncoding.EncodeToString(n.Bytes())
	return eB64, nB64
}

// computeJWKThumbprint returns a computed JWK thumbprint using the given base-64 encoded
// exponent and modulus
func computeJWKThumbprint(eB64 string, nB64 string) string {
	// compute JWK thumbprint
	// jwk format - e, kty, n - in lexicographic order
	// - https://tools.ietf.org/html/rfc7638#section-3.3
	// - https://tools.ietf.org/html/rfc7638#section-3.1
	jwk := fmt.Sprintf(`{"e":"%s","kty":"RSA","n":"%s"}`, eB64, nB64)
	jwkS256 := sha256.Sum256([]byte(jwk))
	return base64.RawURLEncoding.EncodeToString(jwkS256[:])
}

// getReqCnf computes and returns the value for the req_cnf claim to include when sending
// a request for the token
func getReqCnf(jwkTP string) string {
	// req_cnf - base64URL("{"kid":"jwkTP","xms_ksl":"sw"}")
	reqCnfJSON := fmt.Sprintf(`{"kid":"%s","xms_ksl":"sw"}`, jwkTP)
	return base64.RawURLEncoding.EncodeToString([]byte(reqCnfJSON))
}

// getJWK computes the JWK to be included in the PoP token's enclosed cnf claim and returns it
func getJWK(eB64 string, nB64 string, keyID string) string {
	// compute JWK to be included in JWT w/ PoP token's cnf claim
	// - https://tools.ietf.org/html/rfc7800#section-3.2
	return fmt.Sprintf(`{"e":"%s","kty":"RSA","n":"%s","alg":"RS256","kid":"%s"}`, eB64, nB64, keyID)
}

// getPoPKeyFilePath returns the file path for the persistent PoP RSA key.
func getPoPKeyFilePath(cacheDir string) string {
	return filepath.Join(cacheDir, popKeyFileName)
}

// loadOrGenerateRSAKey loads an existing RSA key from secure storage or generates a new one if it doesn't exist.
// This uses the same encrypted storage infrastructure as our PoP token cache, providing platform-specific secure storage:
// - Linux: Kernel keyrings with encrypted files
// - macOS: macOS Keychain
// - Windows: Windows Credential Manager
func loadOrGenerateRSAKey(cacheDir string) (*rsa.PrivateKey, error) {
	// Create a secure storage accessor using our cache infrastructure
	popKeyPath := getPoPKeyFilePath(cacheDir)
	accessor, err := cache.NewSecureAccessor(popKeyPath)
	if err != nil {
		return nil, fmt.Errorf("failed to create secure storage accessor: %w", err)
	}

	ctx := context.Background()

	// Try to load existing key from secure storage
	if keyData, err := accessor.Read(ctx); err == nil && len(keyData) > 0 {
		if key, err := parseRSAKeyFromPEM(keyData); err == nil {
			return key, nil
		}
		// If parsing fails, we'll generate a new key below
	}

	// Generate new key if loading failed
	key, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		return nil, fmt.Errorf("error generating RSA private key: %w", err)
	}

	// Save the key to secure storage
	keyPEM := marshalRSAKeyToPEM(key)
	if err := accessor.Write(ctx, keyPEM); err != nil {
		// Log warning but don't fail - key generation succeeded
		fmt.Fprintf(os.Stderr, "Warning: failed to persist PoP key to secure storage: %v\n", err)
	}

	return key, nil
}

// parseRSAKeyFromPEM parses an RSA private key from PEM data
func parseRSAKeyFromPEM(pemData []byte) (*rsa.PrivateKey, error) {
	block, _ := pem.Decode(pemData)
	if block == nil || block.Type != "RSA PRIVATE KEY" {
		return nil, fmt.Errorf("invalid PEM block type")
	}

	key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
	if err != nil {
		return nil, fmt.Errorf("failed to parse RSA private key: %w", err)
	}

	return key, nil
}

// marshalRSAKeyToPEM converts an RSA private key to PEM format
func marshalRSAKeyToPEM(key *rsa.PrivateKey) []byte {
	keyBytes := x509.MarshalPKCS1PrivateKey(key)
	return pem.EncodeToMemory(&pem.Block{
		Type:  "RSA PRIVATE KEY",
		Bytes: keyBytes,
	})
}

// GetPoPKeyByPolicy returns a PoP key based on cache directory availability.
// Uses persistent key storage when cacheDir is provided, ephemeral keys otherwise.
// This centralizes the key selection logic used across all PoP credential implementations.
func GetPoPKeyByPolicy(cacheDir string) (*SwKey, error) {
	if cacheDir != "" {
		// Use persistent key storage when cache directory is available
		popKey, err := GetSwPoPKeyPersistent(cacheDir)
		if err != nil {
			return nil, fmt.Errorf("unable to get persistent PoP key: %w", err)
		}
		return popKey, nil
	} else {
		// Use ephemeral keys when no cache directory is available
		popKey, err := GetSwPoPKey()
		if err != nil {
			return nil, fmt.Errorf("unable to generate PoP key: %w", err)
		}
		return popKey, nil
	}
}
07070100000076000081A4000000000000000000000001691F8CFD00001467000000000000000000000000000000000000003300000000kubelogin-0.2.13/pkg/internal/pop/poptoken_test.gopackage pop

import (
	"crypto/rand"
	"crypto/rsa"
	"encoding/pem"
	"os"
	"strings"
	"testing"
)

func TestSwPoPKey(t *testing.T) {
	t.Run("GetSwPoPKeyWithRSAKey should return a key with all the expected fields", func(t *testing.T) {
		rsaKey, err := rsa.GenerateKey(rand.Reader, 2048)
		if err != nil {
			t.Errorf("expected no error generating RSA key but got: %s", err)
		}
		key, err := GetSwPoPKeyWithRSAKey(rsaKey)
		if err != nil {
			t.Errorf("expected no error but got: %s", err)
		}

		// validate key alg
		if key.Alg() != "RS256" {
			t.Errorf("expected key alg: %s but got: %s", "RS256", key.Alg())
		}

		// validate key jwk thumbprint
		eB64, nB64 := getRSAKeyExponentAndModulus(key.key)
		expectedJWKThumbprint := computeJWKThumbprint(eB64, nB64)
		if key.JWKThumbprint() != expectedJWKThumbprint {
			t.Errorf("expected key jwt thumbprint: %s but got: %s", expectedJWKThumbprint, key.JWKThumbprint())
		}

		// validate req_cnf
		expectedReqCnf := getReqCnf(expectedJWKThumbprint)
		if key.ReqCnf() != expectedReqCnf {
			t.Errorf("expected key req_cnf: %s but got: %s", expectedReqCnf, key.ReqCnf())
		}

		// validate key ID
		if key.KeyID() != expectedJWKThumbprint {
			t.Errorf("expected key ID: %s but got: %s", expectedJWKThumbprint, key.KeyID())
		}

		// validate jwk
		expectedJWK := getJWK(eB64, nB64, expectedJWKThumbprint)
		if key.JWK() != expectedJWK {
			t.Errorf("expected key JWK: %s but got: %s", expectedJWK, key.JWK())
		}
	})

	t.Run("GetSwPoPKeyWithRSAKey should return a key with all the expected fields", func(t *testing.T) {
		rsaKey, err := rsa.GenerateKey(rand.Reader, 2048)
		if err != nil {
			t.Errorf("expected no error generating RSA key but got: %s", err)
		}

		e, n := getRSAKeyExponentAndModulus(rsaKey)
		e2, n2 := getRSAKeyExponentAndModulus(rsaKey)
		if e2 != e {
			t.Errorf("%s but got: %s", e, e2)
		}
		if n2 != n {
			t.Errorf("%s but got: %s", n, n2)
		}

		tp1 := computeJWKThumbprint(e, n)
		tp2 := computeJWKThumbprint(e2, n2)
		if tp1 != tp2 {
			t.Errorf("%s but got: %s", tp1, tp2)
		}
	})
}

func TestSecureKeyStorage(t *testing.T) {
	// Create a temporary test directory
	testDir, err := os.MkdirTemp("", "kubelogin_secure_key_test")
	if err != nil {
		t.Fatalf("Failed to create test directory: %v", err)
	}
	defer os.RemoveAll(testDir)

	t.Run("GetSwPoPKeyPersistent should use secure storage and persist keys", func(t *testing.T) {
		// Generate first key (should use secure storage)
		key1, err := GetSwPoPKeyPersistent(testDir)
		if err != nil {
			t.Fatalf("Failed to generate first key: %v", err)
		}

		// Load the same key again (should load from secure storage)
		key2, err := GetSwPoPKeyPersistent(testDir)
		if err != nil {
			t.Fatalf("Failed to load second key: %v", err)
		}

		// Verify they're the same key (same KeyID means same underlying RSA key)
		if key1.KeyID() != key2.KeyID() {
			t.Errorf("Keys don't match! First KeyID: %s, Second KeyID: %s", key1.KeyID(), key2.KeyID())
		}

		// Verify JWK thumbprints match (additional verification)
		if key1.JWKThumbprint() != key2.JWKThumbprint() {
			t.Errorf("JWK thumbprints don't match! First: %s, Second: %s", key1.JWKThumbprint(), key2.JWKThumbprint())
		}
	})

	t.Run("GetSwPoPKeyPersistent should handle non-existent cache directory gracefully", func(t *testing.T) {
		nonExistentDir := "/tmp/non_existent_cache_dir_12345"

		// Should create the directory and work fine
		key, err := GetSwPoPKeyPersistent(nonExistentDir)
		if err != nil {
			t.Fatalf("Failed to generate key with non-existent cache dir: %v", err)
		}

		if key == nil {
			t.Error("Key should not be nil")
		}

		// Clean up
		os.RemoveAll(nonExistentDir)
	})
}

func TestRSAKeyConversion(t *testing.T) {
	t.Run("parseRSAKeyFromPEM and marshalRSAKeyToPEM should be reversible", func(t *testing.T) {
		// Generate a test RSA key
		originalKey, err := rsa.GenerateKey(rand.Reader, 2048)
		if err != nil {
			t.Fatalf("Failed to generate test RSA key: %v", err)
		}

		// Convert to PEM and back
		pemData := marshalRSAKeyToPEM(originalKey)
		parsedKey, err := parseRSAKeyFromPEM(pemData)
		if err != nil {
			t.Fatalf("Failed to parse PEM data: %v", err)
		}

		// Verify they're the same key by comparing modulus
		if originalKey.N.Cmp(parsedKey.N) != 0 {
			t.Error("Original and parsed keys have different modulus")
		}

		if originalKey.E != parsedKey.E {
			t.Error("Original and parsed keys have different exponent")
		}
	})

	t.Run("parseRSAKeyFromPEM should handle invalid PEM data", func(t *testing.T) {
		invalidPEMData := []byte("invalid pem data")

		_, err := parseRSAKeyFromPEM(invalidPEMData)
		if err == nil {
			t.Error("Expected error for invalid PEM data, but got none")
		}
	})

	t.Run("parseRSAKeyFromPEM should handle wrong PEM block type", func(t *testing.T) {
		// Create a PEM block with wrong type
		wrongPEM := pem.EncodeToMemory(&pem.Block{
			Type:  "CERTIFICATE",
			Bytes: []byte("not an RSA key"),
		})

		_, err := parseRSAKeyFromPEM(wrongPEM)
		if err == nil {
			t.Error("Expected error for wrong PEM block type, but got none")
		}

		if !strings.Contains(err.Error(), "invalid PEM block type") {
			t.Errorf("Expected 'invalid PEM block type' error, got: %v", err)
		}
	})
}
07070100000077000041ED000000000000000000000002691F8CFD00000000000000000000000000000000000000000000002B00000000kubelogin-0.2.13/pkg/internal/pop/testdata07070100000078000081A4000000000000000000000001691F8CFD00006731000000000000000000000000000000000000006400000000kubelogin-0.2.13/pkg/internal/pop/testdata/AcquirePoPTokenByUsernamePasswordFromBadPasswordVCR.yaml---
version: 2
interactions:
    - id: 0
      request:
        proto: ""
        proto_major: 0
        proto_minor: 0
        content_length: 0
        transfer_encoding: []
        trailer: {}
        host: ""
        remote_addr: ""
        request_uri: ""
        body: ""
        form: {}
        headers:
            Accept-Encoding:
                - gzip
            Client-Request-Id:
                - 23f1fa48-ea1f-4138-be73-75da7aaa0c7b
            Return-Client-Request-Id:
                - "false"
            X-Client-Cpu:
                - amd64
            X-Client-Os:
                - linux
            X-Client-Sku:
                - MSAL.Go
            X-Client-Ver:
                - 1.2.0
        url: https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/v2.0/.well-known/openid-configuration
        method: GET
      response:
        proto: HTTP/1.1
        proto_major: 1
        proto_minor: 1
        transfer_encoding: []
        trailer: {}
        content_length: 1753
        uncompressed: false
        body: '{"token_endpoint":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/v2.0/token","token_endpoint_auth_methods_supported":["client_secret_post","private_key_jwt","client_secret_basic"],"jwks_uri":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/discovery/v2.0/keys","response_modes_supported":["query","fragment","form_post"],"subject_types_supported":["pairwise"],"id_token_signing_alg_values_supported":["RS256"],"response_types_supported":["code","id_token","code id_token","id_token token"],"scopes_supported":["openid","profile","email","offline_access"],"issuer":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/v2.0","request_uri_parameter_supported":false,"userinfo_endpoint":"https://graph.microsoft.com/oidc/userinfo","authorization_endpoint":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/v2.0/authorize","device_authorization_endpoint":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/v2.0/devicecode","http_logout_supported":true,"frontchannel_logout_supported":true,"end_session_endpoint":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/v2.0/logout","claims_supported":["sub","iss","cloud_instance_name","cloud_instance_host_name","cloud_graph_host_name","msgraph_host","aud","exp","iat","auth_time","acr","nonce","preferred_username","name","tid","ver","at_hash","c_hash","email"],"kerberos_endpoint":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/kerberos","tenant_region_scope":"WW","cloud_instance_name":"microsoftonline.com","cloud_graph_host_name":"graph.windows.net","msgraph_host":"graph.microsoft.com","rbac_url":"https://pas.windows.net"}'
        headers:
            Access-Control-Allow-Methods:
                - GET, OPTIONS
            Access-Control-Allow-Origin:
                - '*'
            Cache-Control:
                - max-age=86400, private
            Client-Request-Id:
                - 23f1fa48-ea1f-4138-be73-75da7aaa0c7b
            Content-Length:
                - "1753"
            Content-Type:
                - application/json; charset=utf-8
            Date:
                - Wed, 21 Feb 2024 21:14:22 GMT
            P3p:
                - CP="DSP CUR OTPi IND OTRi ONL FIN"
            Strict-Transport-Security:
                - max-age=31536000; includeSubDomains
            X-Content-Type-Options:
                - nosniff
            X-Ms-Ests-Server:
                - 2.1.17396.6 - NCUS ProdSlices
            X-Xss-Protection:
                - "0"
        status: 200 OK
        code: 200
        duration: 78.559437ms
    - id: 1
      request:
        proto: ""
        proto_major: 0
        proto_minor: 0
        content_length: 0
        transfer_encoding: []
        trailer: {}
        host: ""
        remote_addr: ""
        request_uri: ""
        body: ""
        form: {}
        headers:
            Accept-Encoding:
                - gzip
            Client-Request-Id:
                - 3f8be16e-19b1-4199-a330-deb218ca2f7b
            Return-Client-Request-Id:
                - "false"
            X-Client-Cpu:
                - amd64
            X-Client-Os:
                - linux
            X-Client-Sku:
                - MSAL.Go
            X-Client-Ver:
                - 1.2.0
            client-request-id:
                - 8cab3f9c-f9d3-4a46-bbf6-08ac1eb5d1d6
        url: https://login.microsoftonline.com/common/UserRealm/USERNAME?api-version=1.0
        method: GET
      response:
        proto: HTTP/1.1
        proto_major: 1
        proto_minor: 1
        transfer_encoding: []
        trailer: {}
        content_length: 384
        uncompressed: false
        body: '{"ver":"1.0","account_type":"Federated","domain_name":"microsoft.com","federation_protocol":"WSTrust","federation_metadata_url":"https://msft.sts.microsoft.com/adfs/services/trust/mex","federation_active_auth_url":"https://msft.sts.microsoft.com/adfs/services/trust/2005/usernamemixed","cloud_instance_name":"microsoftonline.com","cloud_audience_urn":"urn:federation:MicrosoftOnline"}'
        headers:
            Cache-Control:
                - no-store, no-cache
            Content-Disposition:
                - inline; filename=userrealm.json
            Content-Length:
                - "384"
            Content-Type:
                - application/json; charset=utf-8
            Date:
                - Wed, 21 Feb 2024 21:14:22 GMT
            Expires:
                - "-1"
            P3p:
                - CP="DSP CUR OTPi IND OTRi ONL FIN"
            Pragma:
                - no-cache
            Strict-Transport-Security:
                - max-age=31536000; includeSubDomains
            X-Content-Type-Options:
                - nosniff
            X-Ms-Ests-Server:
                - 2.1.17396.6 - WUS3 ProdSlices
            X-Xss-Protection:
                - "0"
        status: 200 OK
        code: 200
        duration: 108.043413ms
    - id: 2
      request:
        proto: ""
        proto_major: 0
        proto_minor: 0
        content_length: 0
        transfer_encoding: []
        trailer: {}
        host: ""
        remote_addr: ""
        request_uri: ""
        body: ""
        form: {}
        headers:
            Accept-Encoding:
                - gzip
            Client-Request-Id:
                - df5fd660-b2c5-4f80-832c-8f5e989d84b4
            Content-Type:
                - application/xml; charset=utf-8
            Return-Client-Request-Id:
                - "false"
            X-Client-Cpu:
                - amd64
            X-Client-Os:
                - linux
            X-Client-Sku:
                - MSAL.Go
            X-Client-Ver:
                - 1.2.0
        url: https://msft.sts.microsoft.com/adfs/services/trust/mex
        method: GET
      response:
        proto: HTTP/1.1
        proto_major: 1
        proto_minor: 1
        transfer_encoding: []
        trailer: {}
        content_length: 14784
        uncompressed: false
        body: <?xml version="1.0" encoding="utf-8"?><wsdl:definitions name="SecurityTokenService" targetNamespace="http://schemas.microsoft.com/ws/2008/06/identity/securitytokenservice" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:t="http://schemas.xmlsoap.org/ws/2005/02/trust" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://schemas.microsoft.com/ws/2008/06/identity/securitytokenservice" xmlns:msc="http://schemas.microsoft.com/ws/2005/12/wsdl/contract" xmlns:wsam="http://www.w3.org/2007/05/addressing/metadata" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" xmlns:wsa10="http://www.w3.org/2005/08/addressing" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:wsaw="http://www.w3.org/2006/05/addressing/wsdl" xmlns:wsx="http://schemas.xmlsoap.org/ws/2004/09/mex" xmlns:wsap="http://schemas.xmlsoap.org/ws/2004/08/addressing/policy" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:trust="http://docs.oasis-open.org/ws-sx/ws-trust/200512" xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"><wsp:Policy wsu:Id="CertificateWSTrustBinding_IWSTrustFeb2005Async_policy"><wsp:ExactlyOne><wsp:All><sp:TransportBinding xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy"><wsp:Policy><sp:TransportToken><wsp:Policy><sp:HttpsToken RequireClientCertificate="false"/></wsp:Policy></sp:TransportToken><sp:AlgorithmSuite><wsp:Policy><sp:Basic256/></wsp:Policy></sp:AlgorithmSuite><sp:Layout><wsp:Policy><sp:Strict/></wsp:Policy></sp:Layout><sp:IncludeTimestamp/></wsp:Policy></sp:TransportBinding><sp:EndorsingSupportingTokens xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy"><wsp:Policy><sp:X509Token sp:IncludeToken="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy/IncludeToken/AlwaysToRecipient"><wsp:Policy><sp:RequireThumbprintReference/><sp:WssX509V3Token10/></wsp:Policy></sp:X509Token><mssp:RsaToken sp:IncludeToken="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy/IncludeToken/Never" wsp:Optional="true" xmlns:mssp="http://schemas.microsoft.com/ws/2005/07/securitypolicy"/><sp:SignedParts><sp:Header Name="To" Namespace="http://www.w3.org/2005/08/addressing"/></sp:SignedParts></wsp:Policy></sp:EndorsingSupportingTokens><sp:Wss11 xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy"><wsp:Policy><sp:MustSupportRefThumbprint/></wsp:Policy></sp:Wss11><sp:Trust10 xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy"><wsp:Policy><sp:MustSupportIssuedTokens/><sp:RequireClientEntropy/><sp:RequireServerEntropy/></wsp:Policy></sp:Trust10><wsaw:UsingAddressing/></wsp:All></wsp:ExactlyOne></wsp:Policy><wsp:Policy wsu:Id="CertificateWSTrustBinding_IWSTrustFeb2005Async1_policy"><wsp:ExactlyOne><wsp:All><sp:TransportBinding xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy"><wsp:Policy><sp:TransportToken><wsp:Policy><sp:HttpsToken RequireClientCertificate="true"/></wsp:Policy></sp:TransportToken><sp:AlgorithmSuite><wsp:Policy><sp:Basic256/></wsp:Policy></sp:AlgorithmSuite><sp:Layout><wsp:Policy><sp:Strict/></wsp:Policy></sp:Layout></wsp:Policy></sp:TransportBinding><wsaw:UsingAddressing/></wsp:All></wsp:ExactlyOne></wsp:Policy><wsp:Policy wsu:Id="UserNameWSTrustBinding_IWSTrustFeb2005Async_policy"><wsp:ExactlyOne><wsp:All><sp:TransportBinding xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy"><wsp:Policy><sp:TransportToken><wsp:Policy><sp:HttpsToken RequireClientCertificate="false"/></wsp:Policy></sp:TransportToken><sp:AlgorithmSuite><wsp:Policy><sp:Basic256/></wsp:Policy></sp:AlgorithmSuite><sp:Layout><wsp:Policy><sp:Strict/></wsp:Policy></sp:Layout><sp:IncludeTimestamp/></wsp:Policy></sp:TransportBinding><sp:SignedSupportingTokens xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy"><wsp:Policy><sp:UsernameToken sp:IncludeToken="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy/IncludeToken/AlwaysToRecipient"><wsp:Policy><sp:WssUsernameToken10/></wsp:Policy></sp:UsernameToken></wsp:Policy></sp:SignedSupportingTokens><sp:EndorsingSupportingTokens xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy"><wsp:Policy><mssp:RsaToken sp:IncludeToken="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy/IncludeToken/Never" wsp:Optional="true" xmlns:mssp="http://schemas.microsoft.com/ws/2005/07/securitypolicy"/><sp:SignedParts><sp:Header Name="To" Namespace="http://www.w3.org/2005/08/addressing"/></sp:SignedParts></wsp:Policy></sp:EndorsingSupportingTokens><sp:Wss11 xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy"><wsp:Policy/></sp:Wss11><sp:Trust10 xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy"><wsp:Policy><sp:MustSupportIssuedTokens/><sp:RequireClientEntropy/><sp:RequireServerEntropy/></wsp:Policy></sp:Trust10><wsaw:UsingAddressing/></wsp:All></wsp:ExactlyOne></wsp:Policy><wsp:Policy wsu:Id="CertificateWSTrustBinding_IWSTrust13Async_policy"><wsp:ExactlyOne><wsp:All><sp:TransportBinding xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702"><wsp:Policy><sp:TransportToken><wsp:Policy><sp:HttpsToken/></wsp:Policy></sp:TransportToken><sp:AlgorithmSuite><wsp:Policy><sp:Basic256/></wsp:Policy></sp:AlgorithmSuite><sp:Layout><wsp:Policy><sp:Strict/></wsp:Policy></sp:Layout><sp:IncludeTimestamp/></wsp:Policy></sp:TransportBinding><sp:EndorsingSupportingTokens xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702"><wsp:Policy><sp:X509Token sp:IncludeToken="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702/IncludeToken/AlwaysToRecipient"><wsp:Policy><sp:RequireThumbprintReference/><sp:WssX509V3Token10/></wsp:Policy></sp:X509Token><sp:KeyValueToken sp:IncludeToken="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702/IncludeToken/Never" wsp:Optional="true"/><sp:SignedParts><sp:Header Name="To" Namespace="http://www.w3.org/2005/08/addressing"/></sp:SignedParts></wsp:Policy></sp:EndorsingSupportingTokens><sp:Wss11 xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702"><wsp:Policy><sp:MustSupportRefThumbprint/></wsp:Policy></sp:Wss11><sp:Trust13 xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702"><wsp:Policy><sp:MustSupportIssuedTokens/><sp:RequireClientEntropy/><sp:RequireServerEntropy/></wsp:Policy></sp:Trust13><wsaw:UsingAddressing/></wsp:All></wsp:ExactlyOne></wsp:Policy><wsp:Policy wsu:Id="UserNameWSTrustBinding_IWSTrust13Async_policy"><wsp:ExactlyOne><wsp:All><sp:TransportBinding xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702"><wsp:Policy><sp:TransportToken><wsp:Policy><sp:HttpsToken/></wsp:Policy></sp:TransportToken><sp:AlgorithmSuite><wsp:Policy><sp:Basic256/></wsp:Policy></sp:AlgorithmSuite><sp:Layout><wsp:Policy><sp:Strict/></wsp:Policy></sp:Layout><sp:IncludeTimestamp/></wsp:Policy></sp:TransportBinding><sp:SignedEncryptedSupportingTokens xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702"><wsp:Policy><sp:UsernameToken sp:IncludeToken="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702/IncludeToken/AlwaysToRecipient"><wsp:Policy><sp:WssUsernameToken10/></wsp:Policy></sp:UsernameToken></wsp:Policy></sp:SignedEncryptedSupportingTokens><sp:EndorsingSupportingTokens xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702"><wsp:Policy><sp:KeyValueToken sp:IncludeToken="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702/IncludeToken/Never" wsp:Optional="true"/><sp:SignedParts><sp:Header Name="To" Namespace="http://www.w3.org/2005/08/addressing"/></sp:SignedParts></wsp:Policy></sp:EndorsingSupportingTokens><sp:Wss11 xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702"><wsp:Policy/></sp:Wss11><sp:Trust13 xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702"><wsp:Policy><sp:MustSupportIssuedTokens/><sp:RequireClientEntropy/><sp:RequireServerEntropy/></wsp:Policy></sp:Trust13><wsaw:UsingAddressing/></wsp:All></wsp:ExactlyOne></wsp:Policy><wsdl:types><xsd:schema targetNamespace="http://schemas.microsoft.com/ws/2008/06/identity/securitytokenservice/Imports"><xsd:import schemaLocation="https://msft.sts.microsoft.com/adfs/services/trust/mex?xsd=xsd0" namespace="http://schemas.microsoft.com/Message"/><xsd:import schemaLocation="https://msft.sts.microsoft.com/adfs/services/trust/mex?xsd=xsd1" namespace="http://schemas.xmlsoap.org/ws/2005/02/trust"/><xsd:import schemaLocation="https://msft.sts.microsoft.com/adfs/services/trust/mex?xsd=xsd2" namespace="http://docs.oasis-open.org/ws-sx/ws-trust/200512"/></xsd:schema></wsdl:types><wsdl:message name="IWSTrustFeb2005Async_TrustFeb2005IssueAsync_InputMessage"><wsdl:part name="request" element="t:RequestSecurityToken"/></wsdl:message><wsdl:message name="IWSTrustFeb2005Async_TrustFeb2005IssueAsync_OutputMessage"><wsdl:part name="TrustFeb2005IssueAsyncResult" element="t:RequestSecurityTokenResponse"/></wsdl:message><wsdl:message name="IWSTrust13Async_Trust13IssueAsync_InputMessage"><wsdl:part name="request" element="trust:RequestSecurityToken"/></wsdl:message><wsdl:message name="IWSTrust13Async_Trust13IssueAsync_OutputMessage"><wsdl:part name="Trust13IssueAsyncResult" element="trust:RequestSecurityTokenResponseCollection"/></wsdl:message><wsdl:portType name="IWSTrustFeb2005Async"><wsdl:operation name="TrustFeb2005IssueAsync"><wsdl:input wsaw:Action="http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue" message="tns:IWSTrustFeb2005Async_TrustFeb2005IssueAsync_InputMessage"/><wsdl:output wsaw:Action="http://schemas.xmlsoap.org/ws/2005/02/trust/RSTR/Issue" message="tns:IWSTrustFeb2005Async_TrustFeb2005IssueAsync_OutputMessage"/></wsdl:operation></wsdl:portType><wsdl:portType name="IWSTrust13Async"><wsdl:operation name="Trust13IssueAsync"><wsdl:input wsaw:Action="http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue" message="tns:IWSTrust13Async_Trust13IssueAsync_InputMessage"/><wsdl:output wsaw:Action="http://docs.oasis-open.org/ws-sx/ws-trust/200512/RSTRC/IssueFinal" message="tns:IWSTrust13Async_Trust13IssueAsync_OutputMessage"/></wsdl:operation></wsdl:portType><wsdl:binding name="CertificateWSTrustBinding_IWSTrustFeb2005Async" type="tns:IWSTrustFeb2005Async"><wsp:PolicyReference URI="#CertificateWSTrustBinding_IWSTrustFeb2005Async_policy"/><soap12:binding transport="http://schemas.xmlsoap.org/soap/http"/><wsdl:operation name="TrustFeb2005IssueAsync"><soap12:operation soapAction="http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue" style="document"/><wsdl:input><soap12:body use="literal"/></wsdl:input><wsdl:output><soap12:body use="literal"/></wsdl:output></wsdl:operation></wsdl:binding><wsdl:binding name="CertificateWSTrustBinding_IWSTrustFeb2005Async1" type="tns:IWSTrustFeb2005Async"><wsp:PolicyReference URI="#CertificateWSTrustBinding_IWSTrustFeb2005Async1_policy"/><soap12:binding transport="http://schemas.xmlsoap.org/soap/http"/><wsdl:operation name="TrustFeb2005IssueAsync"><soap12:operation soapAction="http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue" style="document"/><wsdl:input><soap12:body use="literal"/></wsdl:input><wsdl:output><soap12:body use="literal"/></wsdl:output></wsdl:operation></wsdl:binding><wsdl:binding name="UserNameWSTrustBinding_IWSTrustFeb2005Async" type="tns:IWSTrustFeb2005Async"><wsp:PolicyReference URI="#UserNameWSTrustBinding_IWSTrustFeb2005Async_policy"/><soap12:binding transport="http://schemas.xmlsoap.org/soap/http"/><wsdl:operation name="TrustFeb2005IssueAsync"><soap12:operation soapAction="http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue" style="document"/><wsdl:input><soap12:body use="literal"/></wsdl:input><wsdl:output><soap12:body use="literal"/></wsdl:output></wsdl:operation></wsdl:binding><wsdl:binding name="CertificateWSTrustBinding_IWSTrust13Async" type="tns:IWSTrust13Async"><wsp:PolicyReference URI="#CertificateWSTrustBinding_IWSTrust13Async_policy"/><soap12:binding transport="http://schemas.xmlsoap.org/soap/http"/><wsdl:operation name="Trust13IssueAsync"><soap12:operation soapAction="http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue" style="document"/><wsdl:input><soap12:body use="literal"/></wsdl:input><wsdl:output><soap12:body use="literal"/></wsdl:output></wsdl:operation></wsdl:binding><wsdl:binding name="UserNameWSTrustBinding_IWSTrust13Async" type="tns:IWSTrust13Async"><wsp:PolicyReference URI="#UserNameWSTrustBinding_IWSTrust13Async_policy"/><soap12:binding transport="http://schemas.xmlsoap.org/soap/http"/><wsdl:operation name="Trust13IssueAsync"><soap12:operation soapAction="http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue" style="document"/><wsdl:input><soap12:body use="literal"/></wsdl:input><wsdl:output><soap12:body use="literal"/></wsdl:output></wsdl:operation></wsdl:binding><wsdl:service name="SecurityTokenService"><wsdl:port name="CertificateWSTrustBinding_IWSTrustFeb2005Async" binding="tns:CertificateWSTrustBinding_IWSTrustFeb2005Async"><soap12:address location="https://msft.sts.microsoft.com/adfs/services/trust/2005/certificatemixed"/><wsa10:EndpointReference><wsa10:Address>https://msft.sts.microsoft.com/adfs/services/trust/2005/certificatemixed</wsa10:Address></wsa10:EndpointReference></wsdl:port><wsdl:port name="CertificateWSTrustBinding_IWSTrustFeb2005Async1" binding="tns:CertificateWSTrustBinding_IWSTrustFeb2005Async1"><soap12:address location="https://certauth.msft.sts.microsoft.com/adfs/services/trust/2005/certificatetransport"/><wsa10:EndpointReference><wsa10:Address>https://certauth.msft.sts.microsoft.com/adfs/services/trust/2005/certificatetransport</wsa10:Address></wsa10:EndpointReference></wsdl:port><wsdl:port name="UserNameWSTrustBinding_IWSTrustFeb2005Async" binding="tns:UserNameWSTrustBinding_IWSTrustFeb2005Async"><soap12:address location="https://msft.sts.microsoft.com/adfs/services/trust/2005/usernamemixed"/><wsa10:EndpointReference><wsa10:Address>https://msft.sts.microsoft.com/adfs/services/trust/2005/usernamemixed</wsa10:Address></wsa10:EndpointReference></wsdl:port><wsdl:port name="CertificateWSTrustBinding_IWSTrust13Async" binding="tns:CertificateWSTrustBinding_IWSTrust13Async"><soap12:address location="https://msft.sts.microsoft.com/adfs/services/trust/13/certificatemixed"/><wsa10:EndpointReference><wsa10:Address>https://msft.sts.microsoft.com/adfs/services/trust/13/certificatemixed</wsa10:Address></wsa10:EndpointReference></wsdl:port><wsdl:port name="UserNameWSTrustBinding_IWSTrust13Async" binding="tns:UserNameWSTrustBinding_IWSTrust13Async"><soap12:address location="https://msft.sts.microsoft.com/adfs/services/trust/13/usernamemixed"/><wsa10:EndpointReference><wsa10:Address>https://msft.sts.microsoft.com/adfs/services/trust/13/usernamemixed</wsa10:Address></wsa10:EndpointReference></wsdl:port></wsdl:service></wsdl:definitions>
        headers:
            Content-Length:
                - "14784"
            Content-Type:
                - text/xml; charset=UTF-8
            Date:
                - Wed, 21 Feb 2024 21:14:22 GMT
            Server:
                - Microsoft-HTTPAPI/2.0 Microsoft-HTTPAPI/2.0
        status: 200 OK
        code: 200
        duration: 905.127372ms
    - id: 3
      request:
        proto: ""
        proto_major: 0
        proto_minor: 0
        content_length: 0
        transfer_encoding: []
        trailer: {}
        host: ""
        remote_addr: ""
        request_uri: ""
        body: <s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:wsa="http://www.w3.org/2005/08/addressing" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"><s:Header><wsa:Action s:mustUnderstand="1">http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue</wsa:Action><wsa:messageID>urn:uuid:1d5fcd63-9609-49ba-a1ed-bd260dfaf6e7</wsa:messageID><wsa:ReplyTo><wsa:Address>http://www.w3.org/2005/08/addressing/anonymous</wsa:Address></wsa:ReplyTo><wsa:To s:mustUnderstand="1">https://msft.sts.microsoft.com/adfs/services/trust/13/usernamemixed</wsa:To><wsse:Security s:mustUnderstand="1" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><wsu:Timestamp wsu:Id="MSATimeStamp"><wsu:Created>2024-02-21T21:14:24.560Z</wsu:Created><wsu:Expires>2024-02-21T21:24:24.560Z</wsu:Expires></wsu:Timestamp><wsse:UsernameToken wsu:Id="UnPwSecTok13-fcb9c6d5-e16c-45b6-b3a9-874ea3213795"><wsse:Username>USERNAME</wsse:Username><wsse:Password>Bad_Secret</wsse:Password></wsse:UsernameToken></wsse:Security></s:Header><s:Body><wst:RequestSecurityToken xmlns:wst="http://docs.oasis-open.org/ws-sx/ws-trust/200512"><wsp:AppliesTo xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"><wsa:EndpointReference><wsa:Address>urn:federation:MicrosoftOnline</wsa:Address></wsa:EndpointReference></wsp:AppliesTo><wst:KeyType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/Bearer</wst:KeyType><wst:RequestType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue</wst:RequestType></wst:RequestSecurityToken></s:Body></s:Envelope>
        form: {}
        headers:
            Accept-Encoding:
                - gzip
            Client-Request-Id:
                - 024cc5e1-6771-462d-842b-614f867b7562
            Content-Type:
                - application/soap+xml; charset=utf-8
            Return-Client-Request-Id:
                - "false"
            Soapaction:
                - http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue
            X-Client-Cpu:
                - amd64
            X-Client-Os:
                - linux
            X-Client-Sku:
                - MSAL.Go
            X-Client-Ver:
                - 1.2.0
        url: https://msft.sts.microsoft.com/adfs/services/trust/13/usernamemixed
        method: POST
      response:
        proto: HTTP/1.1
        proto_major: 1
        proto_minor: 1
        transfer_encoding: []
        trailer: {}
        content_length: 953
        uncompressed: false
        body: '<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"><s:Header><a:Action s:mustUnderstand="1">http://www.w3.org/2005/08/addressing/soap/fault</a:Action><o:Security s:mustUnderstand="1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><u:Timestamp u:Id="_0"><u:Created>2024-02-21T21:14:25.473Z</u:Created><u:Expires>2024-02-21T21:19:25.473Z</u:Expires></u:Timestamp></o:Security></s:Header><s:Body><s:Fault><s:Code><s:Value>s:Sender</s:Value><s:Subcode><s:Value xmlns:a="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">a:FailedAuthentication</s:Value></s:Subcode></s:Code><s:Reason><s:Text xml:lang="en-US">ID3242: The security token could not be authenticated or authorized.</s:Text></s:Reason></s:Fault></s:Body></s:Envelope>'
        headers:
            Content-Length:
                - "953"
            Content-Type:
                - application/soap+xml; charset=utf-8
            Date:
                - Wed, 21 Feb 2024 21:14:25 GMT
            Server:
                - Microsoft-HTTPAPI/2.0 Microsoft-HTTPAPI/2.0
        status: 500 Internal Server Error
        code: 500
        duration: 1.090492286s
07070100000079000081A4000000000000000000000001691F8CFD0000DF80000000000000000000000000000000000000005500000000kubelogin-0.2.13/pkg/internal/pop/testdata/AcquirePoPTokenByUsernamePasswordVCR.yaml---
version: 2
interactions:
    - id: 0
      request:
        proto: ""
        proto_major: 0
        proto_minor: 0
        content_length: 0
        transfer_encoding: []
        trailer: {}
        host: ""
        remote_addr: ""
        request_uri: ""
        body: ""
        form: {}
        headers:
            Accept-Encoding:
                - gzip
            Client-Request-Id:
                - 9daec4f1-ed1b-4b64-ba36-d3d34be1a2ac
            Return-Client-Request-Id:
                - "false"
            X-Client-Cpu:
                - amd64
            X-Client-Os:
                - linux
            X-Client-Sku:
                - MSAL.Go
            X-Client-Ver:
                - 1.2.0
        url: https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/v2.0/.well-known/openid-configuration
        method: GET
      response:
        proto: HTTP/1.1
        proto_major: 1
        proto_minor: 1
        transfer_encoding: []
        trailer: {}
        content_length: 1753
        uncompressed: false
        body: '{"token_endpoint":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/v2.0/token","token_endpoint_auth_methods_supported":["client_secret_post","private_key_jwt","client_secret_basic"],"jwks_uri":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/discovery/v2.0/keys","response_modes_supported":["query","fragment","form_post"],"subject_types_supported":["pairwise"],"id_token_signing_alg_values_supported":["RS256"],"response_types_supported":["code","id_token","code id_token","id_token token"],"scopes_supported":["openid","profile","email","offline_access"],"issuer":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/v2.0","request_uri_parameter_supported":false,"userinfo_endpoint":"https://graph.microsoft.com/oidc/userinfo","authorization_endpoint":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/v2.0/authorize","device_authorization_endpoint":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/v2.0/devicecode","http_logout_supported":true,"frontchannel_logout_supported":true,"end_session_endpoint":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/v2.0/logout","claims_supported":["sub","iss","cloud_instance_name","cloud_instance_host_name","cloud_graph_host_name","msgraph_host","aud","exp","iat","auth_time","acr","nonce","preferred_username","name","tid","ver","at_hash","c_hash","email"],"kerberos_endpoint":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/kerberos","tenant_region_scope":"WW","cloud_instance_name":"microsoftonline.com","cloud_graph_host_name":"graph.windows.net","msgraph_host":"graph.microsoft.com","rbac_url":"https://pas.windows.net"}'
        headers:
            Access-Control-Allow-Methods:
                - GET, OPTIONS
            Access-Control-Allow-Origin:
                - '*'
            Cache-Control:
                - max-age=86400, private
            Client-Request-Id:
                - 9daec4f1-ed1b-4b64-ba36-d3d34be1a2ac
            Content-Length:
                - "1753"
            Content-Type:
                - application/json; charset=utf-8
            Date:
                - Wed, 21 Feb 2024 21:14:26 GMT
            P3p:
                - CP="DSP CUR OTPi IND OTRi ONL FIN"
            Strict-Transport-Security:
                - max-age=31536000; includeSubDomains
            X-Content-Type-Options:
                - nosniff
            X-Ms-Ests-Server:
                - 2.1.17396.6 - WUS3 ProdSlices
            X-Xss-Protection:
                - "0"
        status: 200 OK
        code: 200
        duration: 64.332046ms
    - id: 1
      request:
        proto: ""
        proto_major: 0
        proto_minor: 0
        content_length: 0
        transfer_encoding: []
        trailer: {}
        host: ""
        remote_addr: ""
        request_uri: ""
        body: ""
        form: {}
        headers:
            Accept-Encoding:
                - gzip
            Client-Request-Id:
                - 42056c1d-e293-4aa2-bb17-97776c6917de
            Return-Client-Request-Id:
                - "false"
            X-Client-Cpu:
                - amd64
            X-Client-Os:
                - linux
            X-Client-Sku:
                - MSAL.Go
            X-Client-Ver:
                - 1.2.0
            client-request-id:
                - e4609b88-b598-4382-b13a-51bf51f8b458
        url: https://login.microsoftonline.com/common/UserRealm/user@example.com?api-version=1.0
        method: GET
      response:
        proto: HTTP/1.1
        proto_major: 1
        proto_minor: 1
        transfer_encoding: []
        trailer: {}
        content_length: 384
        uncompressed: false
        body: '{"ver":"1.0","account_type":"Federated","domain_name":"microsoft.com","federation_protocol":"WSTrust","federation_metadata_url":"https://msft.sts.microsoft.com/adfs/services/trust/mex","federation_active_auth_url":"https://msft.sts.microsoft.com/adfs/services/trust/2005/usernamemixed","cloud_instance_name":"microsoftonline.com","cloud_audience_urn":"urn:federation:MicrosoftOnline"}'
        headers:
            Cache-Control:
                - no-store, no-cache
            Content-Disposition:
                - inline; filename=userrealm.json
            Content-Length:
                - "384"
            Content-Type:
                - application/json; charset=utf-8
            Date:
                - Wed, 21 Feb 2024 21:14:27 GMT
            Expires:
                - "-1"
            P3p:
                - CP="DSP CUR OTPi IND OTRi ONL FIN"
            Pragma:
                - no-cache
            Strict-Transport-Security:
                - max-age=31536000; includeSubDomains
            X-Content-Type-Options:
                - nosniff
            X-Ms-Ests-Server:
                - 2.1.17396.6 - WUS3 ProdSlices
            X-Xss-Protection:
                - "0"
        status: 200 OK
        code: 200
        duration: 70.175341ms
    - id: 2
      request:
        proto: ""
        proto_major: 0
        proto_minor: 0
        content_length: 0
        transfer_encoding: []
        trailer: {}
        host: ""
        remote_addr: ""
        request_uri: ""
        body: ""
        form: {}
        headers:
            Accept-Encoding:
                - gzip
            Client-Request-Id:
                - fbe23136-87f3-4aa2-98f6-751774fbfa3c
            Content-Type:
                - application/xml; charset=utf-8
            Return-Client-Request-Id:
                - "false"
            X-Client-Cpu:
                - amd64
            X-Client-Os:
                - linux
            X-Client-Sku:
                - MSAL.Go
            X-Client-Ver:
                - 1.2.0
        url: https://msft.sts.microsoft.com/adfs/services/trust/mex
        method: GET
      response:
        proto: HTTP/1.1
        proto_major: 1
        proto_minor: 1
        transfer_encoding: []
        trailer: {}
        content_length: 14784
        uncompressed: false
        body: <?xml version="1.0" encoding="utf-8"?><wsdl:definitions name="SecurityTokenService" targetNamespace="http://schemas.microsoft.com/ws/2008/06/identity/securitytokenservice" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:t="http://schemas.xmlsoap.org/ws/2005/02/trust" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://schemas.microsoft.com/ws/2008/06/identity/securitytokenservice" xmlns:msc="http://schemas.microsoft.com/ws/2005/12/wsdl/contract" xmlns:wsam="http://www.w3.org/2007/05/addressing/metadata" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" xmlns:wsa10="http://www.w3.org/2005/08/addressing" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:wsaw="http://www.w3.org/2006/05/addressing/wsdl" xmlns:wsx="http://schemas.xmlsoap.org/ws/2004/09/mex" xmlns:wsap="http://schemas.xmlsoap.org/ws/2004/08/addressing/policy" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:trust="http://docs.oasis-open.org/ws-sx/ws-trust/200512" xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"><wsp:Policy wsu:Id="CertificateWSTrustBinding_IWSTrustFeb2005Async_policy"><wsp:ExactlyOne><wsp:All><sp:TransportBinding xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy"><wsp:Policy><sp:TransportToken><wsp:Policy><sp:HttpsToken RequireClientCertificate="false"/></wsp:Policy></sp:TransportToken><sp:AlgorithmSuite><wsp:Policy><sp:Basic256/></wsp:Policy></sp:AlgorithmSuite><sp:Layout><wsp:Policy><sp:Strict/></wsp:Policy></sp:Layout><sp:IncludeTimestamp/></wsp:Policy></sp:TransportBinding><sp:EndorsingSupportingTokens xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy"><wsp:Policy><sp:X509Token sp:IncludeToken="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy/IncludeToken/AlwaysToRecipient"><wsp:Policy><sp:RequireThumbprintReference/><sp:WssX509V3Token10/></wsp:Policy></sp:X509Token><mssp:RsaToken sp:IncludeToken="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy/IncludeToken/Never" wsp:Optional="true" xmlns:mssp="http://schemas.microsoft.com/ws/2005/07/securitypolicy"/><sp:SignedParts><sp:Header Name="To" Namespace="http://www.w3.org/2005/08/addressing"/></sp:SignedParts></wsp:Policy></sp:EndorsingSupportingTokens><sp:Wss11 xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy"><wsp:Policy><sp:MustSupportRefThumbprint/></wsp:Policy></sp:Wss11><sp:Trust10 xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy"><wsp:Policy><sp:MustSupportIssuedTokens/><sp:RequireClientEntropy/><sp:RequireServerEntropy/></wsp:Policy></sp:Trust10><wsaw:UsingAddressing/></wsp:All></wsp:ExactlyOne></wsp:Policy><wsp:Policy wsu:Id="CertificateWSTrustBinding_IWSTrustFeb2005Async1_policy"><wsp:ExactlyOne><wsp:All><sp:TransportBinding xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy"><wsp:Policy><sp:TransportToken><wsp:Policy><sp:HttpsToken RequireClientCertificate="true"/></wsp:Policy></sp:TransportToken><sp:AlgorithmSuite><wsp:Policy><sp:Basic256/></wsp:Policy></sp:AlgorithmSuite><sp:Layout><wsp:Policy><sp:Strict/></wsp:Policy></sp:Layout></wsp:Policy></sp:TransportBinding><wsaw:UsingAddressing/></wsp:All></wsp:ExactlyOne></wsp:Policy><wsp:Policy wsu:Id="UserNameWSTrustBinding_IWSTrustFeb2005Async_policy"><wsp:ExactlyOne><wsp:All><sp:TransportBinding xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy"><wsp:Policy><sp:TransportToken><wsp:Policy><sp:HttpsToken RequireClientCertificate="false"/></wsp:Policy></sp:TransportToken><sp:AlgorithmSuite><wsp:Policy><sp:Basic256/></wsp:Policy></sp:AlgorithmSuite><sp:Layout><wsp:Policy><sp:Strict/></wsp:Policy></sp:Layout><sp:IncludeTimestamp/></wsp:Policy></sp:TransportBinding><sp:SignedSupportingTokens xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy"><wsp:Policy><sp:UsernameToken sp:IncludeToken="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy/IncludeToken/AlwaysToRecipient"><wsp:Policy><sp:WssUsernameToken10/></wsp:Policy></sp:UsernameToken></wsp:Policy></sp:SignedSupportingTokens><sp:EndorsingSupportingTokens xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy"><wsp:Policy><mssp:RsaToken sp:IncludeToken="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy/IncludeToken/Never" wsp:Optional="true" xmlns:mssp="http://schemas.microsoft.com/ws/2005/07/securitypolicy"/><sp:SignedParts><sp:Header Name="To" Namespace="http://www.w3.org/2005/08/addressing"/></sp:SignedParts></wsp:Policy></sp:EndorsingSupportingTokens><sp:Wss11 xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy"><wsp:Policy/></sp:Wss11><sp:Trust10 xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy"><wsp:Policy><sp:MustSupportIssuedTokens/><sp:RequireClientEntropy/><sp:RequireServerEntropy/></wsp:Policy></sp:Trust10><wsaw:UsingAddressing/></wsp:All></wsp:ExactlyOne></wsp:Policy><wsp:Policy wsu:Id="CertificateWSTrustBinding_IWSTrust13Async_policy"><wsp:ExactlyOne><wsp:All><sp:TransportBinding xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702"><wsp:Policy><sp:TransportToken><wsp:Policy><sp:HttpsToken/></wsp:Policy></sp:TransportToken><sp:AlgorithmSuite><wsp:Policy><sp:Basic256/></wsp:Policy></sp:AlgorithmSuite><sp:Layout><wsp:Policy><sp:Strict/></wsp:Policy></sp:Layout><sp:IncludeTimestamp/></wsp:Policy></sp:TransportBinding><sp:EndorsingSupportingTokens xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702"><wsp:Policy><sp:X509Token sp:IncludeToken="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702/IncludeToken/AlwaysToRecipient"><wsp:Policy><sp:RequireThumbprintReference/><sp:WssX509V3Token10/></wsp:Policy></sp:X509Token><sp:KeyValueToken sp:IncludeToken="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702/IncludeToken/Never" wsp:Optional="true"/><sp:SignedParts><sp:Header Name="To" Namespace="http://www.w3.org/2005/08/addressing"/></sp:SignedParts></wsp:Policy></sp:EndorsingSupportingTokens><sp:Wss11 xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702"><wsp:Policy><sp:MustSupportRefThumbprint/></wsp:Policy></sp:Wss11><sp:Trust13 xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702"><wsp:Policy><sp:MustSupportIssuedTokens/><sp:RequireClientEntropy/><sp:RequireServerEntropy/></wsp:Policy></sp:Trust13><wsaw:UsingAddressing/></wsp:All></wsp:ExactlyOne></wsp:Policy><wsp:Policy wsu:Id="UserNameWSTrustBinding_IWSTrust13Async_policy"><wsp:ExactlyOne><wsp:All><sp:TransportBinding xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702"><wsp:Policy><sp:TransportToken><wsp:Policy><sp:HttpsToken/></wsp:Policy></sp:TransportToken><sp:AlgorithmSuite><wsp:Policy><sp:Basic256/></wsp:Policy></sp:AlgorithmSuite><sp:Layout><wsp:Policy><sp:Strict/></wsp:Policy></sp:Layout><sp:IncludeTimestamp/></wsp:Policy></sp:TransportBinding><sp:SignedEncryptedSupportingTokens xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702"><wsp:Policy><sp:UsernameToken sp:IncludeToken="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702/IncludeToken/AlwaysToRecipient"><wsp:Policy><sp:WssUsernameToken10/></wsp:Policy></sp:UsernameToken></wsp:Policy></sp:SignedEncryptedSupportingTokens><sp:EndorsingSupportingTokens xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702"><wsp:Policy><sp:KeyValueToken sp:IncludeToken="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702/IncludeToken/Never" wsp:Optional="true"/><sp:SignedParts><sp:Header Name="To" Namespace="http://www.w3.org/2005/08/addressing"/></sp:SignedParts></wsp:Policy></sp:EndorsingSupportingTokens><sp:Wss11 xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702"><wsp:Policy/></sp:Wss11><sp:Trust13 xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702"><wsp:Policy><sp:MustSupportIssuedTokens/><sp:RequireClientEntropy/><sp:RequireServerEntropy/></wsp:Policy></sp:Trust13><wsaw:UsingAddressing/></wsp:All></wsp:ExactlyOne></wsp:Policy><wsdl:types><xsd:schema targetNamespace="http://schemas.microsoft.com/ws/2008/06/identity/securitytokenservice/Imports"><xsd:import schemaLocation="https://msft.sts.microsoft.com/adfs/services/trust/mex?xsd=xsd0" namespace="http://schemas.microsoft.com/Message"/><xsd:import schemaLocation="https://msft.sts.microsoft.com/adfs/services/trust/mex?xsd=xsd1" namespace="http://schemas.xmlsoap.org/ws/2005/02/trust"/><xsd:import schemaLocation="https://msft.sts.microsoft.com/adfs/services/trust/mex?xsd=xsd2" namespace="http://docs.oasis-open.org/ws-sx/ws-trust/200512"/></xsd:schema></wsdl:types><wsdl:message name="IWSTrustFeb2005Async_TrustFeb2005IssueAsync_InputMessage"><wsdl:part name="request" element="t:RequestSecurityToken"/></wsdl:message><wsdl:message name="IWSTrustFeb2005Async_TrustFeb2005IssueAsync_OutputMessage"><wsdl:part name="TrustFeb2005IssueAsyncResult" element="t:RequestSecurityTokenResponse"/></wsdl:message><wsdl:message name="IWSTrust13Async_Trust13IssueAsync_InputMessage"><wsdl:part name="request" element="trust:RequestSecurityToken"/></wsdl:message><wsdl:message name="IWSTrust13Async_Trust13IssueAsync_OutputMessage"><wsdl:part name="Trust13IssueAsyncResult" element="trust:RequestSecurityTokenResponseCollection"/></wsdl:message><wsdl:portType name="IWSTrustFeb2005Async"><wsdl:operation name="TrustFeb2005IssueAsync"><wsdl:input wsaw:Action="http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue" message="tns:IWSTrustFeb2005Async_TrustFeb2005IssueAsync_InputMessage"/><wsdl:output wsaw:Action="http://schemas.xmlsoap.org/ws/2005/02/trust/RSTR/Issue" message="tns:IWSTrustFeb2005Async_TrustFeb2005IssueAsync_OutputMessage"/></wsdl:operation></wsdl:portType><wsdl:portType name="IWSTrust13Async"><wsdl:operation name="Trust13IssueAsync"><wsdl:input wsaw:Action="http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue" message="tns:IWSTrust13Async_Trust13IssueAsync_InputMessage"/><wsdl:output wsaw:Action="http://docs.oasis-open.org/ws-sx/ws-trust/200512/RSTRC/IssueFinal" message="tns:IWSTrust13Async_Trust13IssueAsync_OutputMessage"/></wsdl:operation></wsdl:portType><wsdl:binding name="CertificateWSTrustBinding_IWSTrustFeb2005Async" type="tns:IWSTrustFeb2005Async"><wsp:PolicyReference URI="#CertificateWSTrustBinding_IWSTrustFeb2005Async_policy"/><soap12:binding transport="http://schemas.xmlsoap.org/soap/http"/><wsdl:operation name="TrustFeb2005IssueAsync"><soap12:operation soapAction="http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue" style="document"/><wsdl:input><soap12:body use="literal"/></wsdl:input><wsdl:output><soap12:body use="literal"/></wsdl:output></wsdl:operation></wsdl:binding><wsdl:binding name="CertificateWSTrustBinding_IWSTrustFeb2005Async1" type="tns:IWSTrustFeb2005Async"><wsp:PolicyReference URI="#CertificateWSTrustBinding_IWSTrustFeb2005Async1_policy"/><soap12:binding transport="http://schemas.xmlsoap.org/soap/http"/><wsdl:operation name="TrustFeb2005IssueAsync"><soap12:operation soapAction="http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue" style="document"/><wsdl:input><soap12:body use="literal"/></wsdl:input><wsdl:output><soap12:body use="literal"/></wsdl:output></wsdl:operation></wsdl:binding><wsdl:binding name="UserNameWSTrustBinding_IWSTrustFeb2005Async" type="tns:IWSTrustFeb2005Async"><wsp:PolicyReference URI="#UserNameWSTrustBinding_IWSTrustFeb2005Async_policy"/><soap12:binding transport="http://schemas.xmlsoap.org/soap/http"/><wsdl:operation name="TrustFeb2005IssueAsync"><soap12:operation soapAction="http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue" style="document"/><wsdl:input><soap12:body use="literal"/></wsdl:input><wsdl:output><soap12:body use="literal"/></wsdl:output></wsdl:operation></wsdl:binding><wsdl:binding name="CertificateWSTrustBinding_IWSTrust13Async" type="tns:IWSTrust13Async"><wsp:PolicyReference URI="#CertificateWSTrustBinding_IWSTrust13Async_policy"/><soap12:binding transport="http://schemas.xmlsoap.org/soap/http"/><wsdl:operation name="Trust13IssueAsync"><soap12:operation soapAction="http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue" style="document"/><wsdl:input><soap12:body use="literal"/></wsdl:input><wsdl:output><soap12:body use="literal"/></wsdl:output></wsdl:operation></wsdl:binding><wsdl:binding name="UserNameWSTrustBinding_IWSTrust13Async" type="tns:IWSTrust13Async"><wsp:PolicyReference URI="#UserNameWSTrustBinding_IWSTrust13Async_policy"/><soap12:binding transport="http://schemas.xmlsoap.org/soap/http"/><wsdl:operation name="Trust13IssueAsync"><soap12:operation soapAction="http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue" style="document"/><wsdl:input><soap12:body use="literal"/></wsdl:input><wsdl:output><soap12:body use="literal"/></wsdl:output></wsdl:operation></wsdl:binding><wsdl:service name="SecurityTokenService"><wsdl:port name="CertificateWSTrustBinding_IWSTrustFeb2005Async" binding="tns:CertificateWSTrustBinding_IWSTrustFeb2005Async"><soap12:address location="https://msft.sts.microsoft.com/adfs/services/trust/2005/certificatemixed"/><wsa10:EndpointReference><wsa10:Address>https://msft.sts.microsoft.com/adfs/services/trust/2005/certificatemixed</wsa10:Address></wsa10:EndpointReference></wsdl:port><wsdl:port name="CertificateWSTrustBinding_IWSTrustFeb2005Async1" binding="tns:CertificateWSTrustBinding_IWSTrustFeb2005Async1"><soap12:address location="https://certauth.msft.sts.microsoft.com/adfs/services/trust/2005/certificatetransport"/><wsa10:EndpointReference><wsa10:Address>https://certauth.msft.sts.microsoft.com/adfs/services/trust/2005/certificatetransport</wsa10:Address></wsa10:EndpointReference></wsdl:port><wsdl:port name="UserNameWSTrustBinding_IWSTrustFeb2005Async" binding="tns:UserNameWSTrustBinding_IWSTrustFeb2005Async"><soap12:address location="https://msft.sts.microsoft.com/adfs/services/trust/2005/usernamemixed"/><wsa10:EndpointReference><wsa10:Address>https://msft.sts.microsoft.com/adfs/services/trust/2005/usernamemixed</wsa10:Address></wsa10:EndpointReference></wsdl:port><wsdl:port name="CertificateWSTrustBinding_IWSTrust13Async" binding="tns:CertificateWSTrustBinding_IWSTrust13Async"><soap12:address location="https://msft.sts.microsoft.com/adfs/services/trust/13/certificatemixed"/><wsa10:EndpointReference><wsa10:Address>https://msft.sts.microsoft.com/adfs/services/trust/13/certificatemixed</wsa10:Address></wsa10:EndpointReference></wsdl:port><wsdl:port name="UserNameWSTrustBinding_IWSTrust13Async" binding="tns:UserNameWSTrustBinding_IWSTrust13Async"><soap12:address location="https://msft.sts.microsoft.com/adfs/services/trust/13/usernamemixed"/><wsa10:EndpointReference><wsa10:Address>https://msft.sts.microsoft.com/adfs/services/trust/13/usernamemixed</wsa10:Address></wsa10:EndpointReference></wsdl:port></wsdl:service></wsdl:definitions>
        headers:
            Content-Length:
                - "14784"
            Content-Type:
                - text/xml; charset=UTF-8
            Date:
                - Wed, 21 Feb 2024 21:14:27 GMT
            Server:
                - Microsoft-HTTPAPI/2.0 Microsoft-HTTPAPI/2.0
        status: 200 OK
        code: 200
        duration: 223.877512ms
    - id: 3
      request:
        proto: ""
        proto_major: 0
        proto_minor: 0
        content_length: 0
        transfer_encoding: []
        trailer: {}
        host: ""
        remote_addr: ""
        request_uri: ""
        body: <s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:wsa="http://www.w3.org/2005/08/addressing" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"><s:Header><wsa:Action s:mustUnderstand="1">http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue</wsa:Action><wsa:messageID>urn:uuid:c3163081-59ba-443d-bfe0-4f3c2e343fa7</wsa:messageID><wsa:ReplyTo><wsa:Address>http://www.w3.org/2005/08/addressing/anonymous</wsa:Address></wsa:ReplyTo><wsa:To s:mustUnderstand="1">https://msft.sts.microsoft.com/adfs/services/trust/13/usernamemixed</wsa:To><wsse:Security s:mustUnderstand="1" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><wsu:Timestamp wsu:Id="MSATimeStamp"><wsu:Created>2024-02-21T21:14:27.575Z</wsu:Created><wsu:Expires>2024-02-21T21:24:27.575Z</wsu:Expires></wsu:Timestamp><wsse:UsernameToken wsu:Id="UnPwSecTok13-3e0b3bb9-6e03-4e12-99d6-45a3181c2ec0"><wsse:Username>user@example.com</wsse:Username><wsse:Password>[REDACTED]</wsse:Password></wsse:UsernameToken></wsse:Security></s:Header><s:Body><wst:RequestSecurityToken xmlns:wst="http://docs.oasis-open.org/ws-sx/ws-trust/200512"><wsp:AppliesTo xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"><wsa:EndpointReference><wsa:Address>urn:federation:MicrosoftOnline</wsa:Address></wsa:EndpointReference></wsp:AppliesTo><wst:KeyType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/Bearer</wst:KeyType><wst:RequestType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue</wst:RequestType></wst:RequestSecurityToken></s:Body></s:Envelope>
        form: {}
        headers:
            Accept-Encoding:
                - gzip
            Client-Request-Id:
                - b4273c15-60fd-4be1-b538-10de45a3843b
            Content-Type:
                - application/soap+xml; charset=utf-8
            Return-Client-Request-Id:
                - "false"
            Soapaction:
                - http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue
            X-Client-Cpu:
                - amd64
            X-Client-Os:
                - linux
            X-Client-Sku:
                - MSAL.Go
            X-Client-Ver:
                - 1.2.0
        url: https://msft.sts.microsoft.com/adfs/services/trust/13/usernamemixed
        method: POST
      response:
        proto: HTTP/1.1
        proto_major: 1
        proto_minor: 1
        transfer_encoding: []
        trailer: {}
        content_length: 9894
        uncompressed: false
        body: <s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"><s:Header><a:Action s:mustUnderstand="1">http://docs.oasis-open.org/ws-sx/ws-trust/200512/RSTRC/IssueFinal</a:Action><o:Security s:mustUnderstand="1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><u:Timestamp u:Id="_0"><u:Created>2024-02-21T21:14:27.770Z</u:Created><u:Expires>2024-02-21T21:19:27.770Z</u:Expires></u:Timestamp></o:Security></s:Header><s:Body><trust:RequestSecurityTokenResponseCollection xmlns:trust="http://docs.oasis-open.org/ws-sx/ws-trust/200512"><trust:RequestSecurityTokenResponse><trust:Lifetime><wsu:Created xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">2024-02-21T21:14:27.770Z</wsu:Created><wsu:Expires xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">2024-02-21T22:14:27.770Z</wsu:Expires></trust:Lifetime><wsp:AppliesTo xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"><wsa:EndpointReference xmlns:wsa="http://www.w3.org/2005/08/addressing"><wsa:Address>urn:federation:MicrosoftOnline</wsa:Address></wsa:EndpointReference></wsp:AppliesTo><trust:RequestedSecurityToken><saml:Assertion MajorVersion="1" MinorVersion="1" AssertionID="_cc4cc62c-43fa-4054-95ec-8931c0277f7a" Issuer="urn:federation:MSFT" IssueInstant="2024-02-21T21:14:27.770Z" xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion"><saml:Conditions NotBefore="2024-02-21T21:14:27.770Z" NotOnOrAfter="2024-02-21T22:14:27.770Z"><saml:AudienceRestrictionCondition><saml:Audience>urn:federation:MicrosoftOnline</saml:Audience></saml:AudienceRestrictionCondition></saml:Conditions><saml:AttributeStatement><saml:Subject><saml:NameIdentifier Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">4pGK4tThCkamJqAj33shZA==</saml:NameIdentifier><saml:SubjectConfirmation><saml:ConfirmationMethod>urn:oasis:names:tc:SAML:1.0:cm:bearer</saml:ConfirmationMethod></saml:SubjectConfirmation></saml:Subject><saml:Attribute AttributeName="UPN" AttributeNamespace="http://schemas.xmlsoap.org/claims"><saml:AttributeValue>USERNAME</saml:AttributeValue></saml:Attribute><saml:Attribute AttributeName="objectGUID" AttributeNamespace="http://tempuri.com"><saml:AttributeValue>4pGK4tThCkamJqAj33shZA==</saml:AttributeValue></saml:Attribute><saml:Attribute AttributeName="PersonnelNumber" AttributeNamespace="http://schemas.xmlsoap.org/claims"><saml:AttributeValue>4pGK4tThCkamJqAj33shZA==</saml:AttributeValue></saml:Attribute><saml:Attribute AttributeName="ImmutableID" AttributeNamespace="http://schemas.microsoft.com/LiveID/Federation/2008/05"><saml:AttributeValue>4pGK4tThCkamJqAj33shZA==</saml:AttributeValue></saml:Attribute><saml:Attribute AttributeName="authnmethodsreferences" AttributeNamespace="http://schemas.microsoft.com/claims"><saml:AttributeValue>http://schemas.microsoft.com/ws/2008/06/identity/authenticationmethod/password</saml:AttributeValue></saml:Attribute><saml:Attribute AttributeName="passwordexpirationtime" AttributeNamespace="http://schemas.microsoft.com/ws/2012/01"><saml:AttributeValue>2024-09-21T21:00:03.757Z</saml:AttributeValue></saml:Attribute><saml:Attribute AttributeName="passwordchangeurl" AttributeNamespace="http://schemas.microsoft.com/ws/2012/01"><saml:AttributeValue>https://sspm.microsoft.com</saml:AttributeValue></saml:Attribute><saml:Attribute AttributeName="client-request-id" AttributeNamespace="http://schemas.microsoft.com/2012/01/requestcontext/claims" a:OriginalIssuer="CLIENT CONTEXT" xmlns:a="http://schemas.xmlsoap.org/ws/2009/09/identity/claims"><saml:AttributeValue>b4273c15-60fd-4be1-b538-10de45a3843b</saml:AttributeValue></saml:Attribute></saml:AttributeStatement><saml:AuthenticationStatement AuthenticationMethod="urn:oasis:names:tc:SAML:1.0:am:password" AuthenticationInstant="2024-02-21T21:14:27.707Z"><saml:Subject><saml:NameIdentifier Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">4pGK4tThCkamJqAj33shZA==</saml:NameIdentifier><saml:SubjectConfirmation><saml:ConfirmationMethod>urn:oasis:names:tc:SAML:1.0:cm:bearer</saml:ConfirmationMethod></saml:SubjectConfirmation></saml:Subject></saml:AuthenticationStatement><ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#"><ds:SignedInfo><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/><ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/><ds:Reference URI="#_cc4cc62c-43fa-4054-95ec-8931c0277f7a"><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/><ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><ds:DigestValue>4z/sZaVjT/ZhsGdXQwVeYihESqV+FXtqXkacypO15hY=</ds:DigestValue></ds:Reference></ds:SignedInfo><ds:SignatureValue>tkdyR3za2kbPQAHW/6SEquQ3Lgn+331U9mZnrRFKP1MnwTvfD9GpEw+5dVxQtwqAIqD9p1WwDqbiID7agTWaE8d8bmBk21KXbxhMP7+3Gju2MHLbF9AHI9LfBQVClGcerv2EbrnlE/mF3cPEcbWVp5x2EtD00qxd+1Tw1yzEO7LFSxN+cpFf/SQvqn6Zyyn3RzpEIycCV9EgXWLeKul47rbaaEkWYR1RNx8o68OqNROCAqMH1GG7n4bcoCzj+/pNwRBVhl7FzFQFyWCyN8br01gQge61fLT71nRSCbiuTa7O8j7LjdLGGlfH9GEclBHwEQEUFD6AKVDaJBEhPm+ezw==</ds:SignatureValue><KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"><X509Data><X509Certificate>MIIIcDCCBligAwIBAgITMwAhKgLCjrUMx2lihwAAACEqAjANBgkqhkiG9w0BAQwFADBdMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS4wLAYDVQQDEyVNaWNyb3NvZnQgQXp1cmUgUlNBIFRMUyBJc3N1aW5nIENBIDA0MB4XDTI0MDExNjE4MTQyNVoXDTI1MDExMDE4MTQyNVowbTELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAldBMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xHzAdBgNVBAMTFm1zZnQuc3RzLm1pY3Jvc29mdC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDFKlsVGXV9JFOdUJOp49zbcI1UtywMFp4Vr7DVDYjzlEk8gF8wUJFhqOc56oZU0jKBoAWkC0sefpfXmdazb7YsCFjpUZ/pWel0iNsmDHiAl+ytfyhyi+BnkCJCrCTJiaK2VNp0QgYHJl6KppVYFWuAr3UVhyGtCxcoZruYLBlb7UN2T6AbR3a6WKhD58RRADa4EbecCon6MtVv9AeQdi+izK7hhIGeg5CPMuIEO02d+KgArLlXADm3JO5ie/JtnoPSMsCAhJQGqKxja28IlJ4VorMjTuV5l70x7VUVBg2EIgp+vgJ8F51KXPbQKuJvyXSCLEl+ff7tPjFhi2i3gE/JAgMBAAGjggQXMIIEEzCCAX0GCisGAQQB1nkCBAIEggFtBIIBaQFnAHUATnWjJ1yaEMM4W2zU3z9S6x3w4I4bjWnAsfpksWKaOd8AAAGNE4SG1gAABAMARjBEAiAqXG3UtSbli3eYUIlNk8SIQYU5JdxQkczonOkap+c3OgIgMY2pZGBgr7WdAQA50DBTh8U7OiQ3dPvm1J+TJtVhxUoAdgB9WR4S4XgqexxhZ3xe/fjQh1wUoE6VnrkDL9kOjC55uAAAAY0ThIasAAAEAwBHMEUCIEriWEtcKIzCmm83LZK9m7qH7UpqUy8QMKwZtO/62i6sAiEAg70AUsGhNParA/5gPOkNpMaWvIlb7Lj49XIwDPWr7VEAdgBVgdTCFpA2AUrqC5tXPFPwwOQ4eHAlCBcvo6odBxPTDAAAAY0ThIinAAAEAwBHMEUCIQDXImoDIidH9pqtZVrkPXbz2pajKmhVtlJF9zOD8f7HXgIgaYX9XcmnPSqmj6Ad0j+nB1f1KGaou3wb+AHIfA4ezAEwJwYJKwYBBAGCNxUKBBowGDAKBggrBgEFBQcDAjAKBggrBgEFBQcDATA8BgkrBgEEAYI3FQcELzAtBiUrBgEEAYI3FQiHvdcbgefrRoKBnS6O0AyH8NodXYKE5WmC86c+AgFkAgEmMIG0BggrBgEFBQcBAQSBpzCBpDBzBggrBgEFBQcwAoZnaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNyb3NvZnQlMjBBenVyZSUyMFJTQSUyMFRMUyUyMElzc3VpbmclMjBDQSUyMDA0JTIwLSUyMHhzaWduLmNydDAtBggrBgEFBQcwAYYhaHR0cDovL29uZW9jc3AubWljcm9zb2Z0LmNvbS9vY3NwMB0GA1UdDgQWBBQWstn/NHMw8B02sgLNcALZxKMNMTAOBgNVHQ8BAf8EBAMCBaAwIQYDVR0RBBowGIIWbXNmdC5zdHMubWljcm9zb2Z0LmNvbTAMBgNVHRMBAf8EAjAAMGoGA1UdHwRjMGEwX6BdoFuGWWh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUyMEF6dXJlJTIwUlNBJTIwVExTJTIwSXNzdWluZyUyMENBJTIwMDQuY3JsMGYGA1UdIARfMF0wUQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTAIBgZngQwBAgIwHwYDVR0jBBgwFoAUO3DRU+l2JZ1gqMpmD8abrm9UFmowHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMA0GCSqGSIb3DQEBDAUAA4ICAQAE29MX1vYdWSlJWt8Pirt8WTgIjuKJAf1orLOo3SP27mlrb6PrAfmTLX7US8nmiW6mZF29xW8govfZBMs6w0Xxwj7Z9wFP6h8XCS/38vRHPfES0PgVHiQ4X5N5gDvk9QASWStnW8jUhZ/1fCIwsF6tT7Rrd3S79aUF8FEWV/s5SKE3gu7PkW91BT44nFq1zz3cil5B768D4M0Ip9dcuZ+tejkbPkWVwFhv6vlwWySyruIIZ5MeDzhaJDhVjHD7WiF3+6iUhEIwONE3JhUSRL1tR6ifbFxhJwTxoQm6LL7mCcqdFGFfshRQx9717tPpR0lGVgqnYxlBwv2lSgzhl+mWjy4C8tzgKthgNBhY5n7Rml5FAjfA6cUPQh60ws30AdSaKDZ8kVM1qLKUoAd5hjB/ZwHFNFoYQgr1bgTcsylw4A+3wuJO6f/r4fZuoh3TJjil290XFgvp76oLeMxX7zonW3HSb/cNTeFLNeF3sUbuQS0hxMzwnj6jjIzFJxas54JZrpkrv2XptzDvbeeg3oNARGtG4NF4NAfaTV/c4ArGxtgQl4WxebU5Uz9Rv9WkflJ4nAi/Dkx/9dbnQi7VkT7fBt0bvr9Hab6GfZ62GtlEg6eiZ5b8ZiNHPb35HWKXKDNnpWoicX+lmWjfGAYtdcS64DX3gmW3xso/7We6EKR3bw==</X509Certificate></X509Data></KeyInfo></ds:Signature></saml:Assertion></trust:RequestedSecurityToken><trust:RequestedAttachedReference><o:SecurityTokenReference k:TokenType="http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV1.1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:k="http://docs.oasis-open.org/wss/oasis-wss-wssecurity-secext-1.1.xsd"><o:KeyIdentifier ValueType="http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.0#SAMLAssertionID">_cc4cc62c-43fa-4054-95ec-8931c0277f7a</o:KeyIdentifier></o:SecurityTokenReference></trust:RequestedAttachedReference><trust:RequestedUnattachedReference><o:SecurityTokenReference k:TokenType="http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV1.1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:k="http://docs.oasis-open.org/wss/oasis-wss-wssecurity-secext-1.1.xsd"><o:KeyIdentifier ValueType="http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.0#SAMLAssertionID">_cc4cc62c-43fa-4054-95ec-8931c0277f7a</o:KeyIdentifier></o:SecurityTokenReference></trust:RequestedUnattachedReference><trust:TokenType>urn:oasis:names:tc:SAML:1.0:assertion</trust:TokenType><trust:RequestType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue</trust:RequestType><trust:KeyType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/Bearer</trust:KeyType></trust:RequestSecurityTokenResponse></trust:RequestSecurityTokenResponseCollection></s:Body></s:Envelope>
        headers:
            Content-Length:
                - "9894"
            Content-Type:
                - application/soap+xml; charset=utf-8
            Date:
                - Wed, 21 Feb 2024 21:14:27 GMT
            Server:
                - Microsoft-HTTPAPI/2.0 Microsoft-HTTPAPI/2.0
        status: 200 OK
        code: 200
        duration: 382.05008ms
    - id: 4
      request:
        proto: ""
        proto_major: 0
        proto_minor: 0
        content_length: 9902
        transfer_encoding: []
        trailer: {}
        host: ""
        remote_addr: ""
        request_uri: ""
        body: assertion=PHNhbWw6QXNzZXJ0aW9uIE1ham9yVmVyc2lvbj0iMSIgTWlub3JWZXJzaW9uPSIxIiBBc3NlcnRpb25JRD0iX2NjNGNjNjJjLTQzZmEtNDA1NC05NWVjLTg5MzFjMDI3N2Y3YSIgSXNzdWVyPSJ1cm46ZmVkZXJhdGlvbjpNU0ZUIiBJc3N1ZUluc3RhbnQ9IjIwMjQtMDItMjFUMjE6MTQ6MjcuNzcwWiIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6MS4wOmFzc2VydGlvbiI%2BPHNhbWw6Q29uZGl0aW9ucyBOb3RCZWZvcmU9IjIwMjQtMDItMjFUMjE6MTQ6MjcuNzcwWiIgTm90T25PckFmdGVyPSIyMDI0LTAyLTIxVDIyOjE0OjI3Ljc3MFoiPjxzYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb25Db25kaXRpb24%2BPHNhbWw6QXVkaWVuY2U%2BdXJuOmZlZGVyYXRpb246TWljcm9zb2Z0T25saW5lPC9zYW1sOkF1ZGllbmNlPjwvc2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uQ29uZGl0aW9uPjwvc2FtbDpDb25kaXRpb25zPjxzYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD48c2FtbDpTdWJqZWN0PjxzYW1sOk5hbWVJZGVudGlmaWVyIEZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6MS4xOm5hbWVpZC1mb3JtYXQ6dW5zcGVjaWZpZWQiPjRwR0s0dFRoQ2thbUpxQWozM3NoWkE9PTwvc2FtbDpOYW1lSWRlbnRpZmllcj48c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uPjxzYW1sOkNvbmZpcm1hdGlvbk1ldGhvZD51cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoxLjA6Y206YmVhcmVyPC9zYW1sOkNvbmZpcm1hdGlvbk1ldGhvZD48L3NhbWw6U3ViamVjdENvbmZpcm1hdGlvbj48L3NhbWw6U3ViamVjdD48c2FtbDpBdHRyaWJ1dGUgQXR0cmlidXRlTmFtZT0iVVBOIiBBdHRyaWJ1dGVOYW1lc3BhY2U9Imh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL2NsYWltcyI%2BPHNhbWw6QXR0cmlidXRlVmFsdWU%2Bazhjb25uZWN0c2FAbWljcm9zb2Z0LmNvbTwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBBdHRyaWJ1dGVOYW1lPSJvYmplY3RHVUlEIiBBdHRyaWJ1dGVOYW1lc3BhY2U9Imh0dHA6Ly90ZW1wdXJpLmNvbSI%2BPHNhbWw6QXR0cmlidXRlVmFsdWU%2BNHBHSzR0VGhDa2FtSnFBajMzc2haQT09PC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDpBdHRyaWJ1dGU%2BPHNhbWw6QXR0cmlidXRlIEF0dHJpYnV0ZU5hbWU9IlBlcnNvbm5lbE51bWJlciIgQXR0cmlidXRlTmFtZXNwYWNlPSJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy9jbGFpbXMiPjxzYW1sOkF0dHJpYnV0ZVZhbHVlPjRwR0s0dFRoQ2thbUpxQWozM3NoWkE9PTwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBBdHRyaWJ1dGVOYW1lPSJJbW11dGFibGVJRCIgQXR0cmlidXRlTmFtZXNwYWNlPSJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL0xpdmVJRC9GZWRlcmF0aW9uLzIwMDgvMDUiPjxzYW1sOkF0dHJpYnV0ZVZhbHVlPjRwR0s0dFRoQ2thbUpxQWozM3NoWkE9PTwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBBdHRyaWJ1dGVOYW1lPSJhdXRobm1ldGhvZHNyZWZlcmVuY2VzIiBBdHRyaWJ1dGVOYW1lc3BhY2U9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vY2xhaW1zIj48c2FtbDpBdHRyaWJ1dGVWYWx1ZT5odHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvYXV0aGVudGljYXRpb25tZXRob2QvcGFzc3dvcmQ8L3NhbWw6QXR0cmlidXRlVmFsdWU%2BPC9zYW1sOkF0dHJpYnV0ZT48c2FtbDpBdHRyaWJ1dGUgQXR0cmlidXRlTmFtZT0icGFzc3dvcmRleHBpcmF0aW9udGltZSIgQXR0cmlidXRlTmFtZXNwYWNlPSJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMTIvMDEiPjxzYW1sOkF0dHJpYnV0ZVZhbHVlPjIwMjQtMDktMjFUMjE6MDA6MDMuNzU3Wjwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBBdHRyaWJ1dGVOYW1lPSJwYXNzd29yZGNoYW5nZXVybCIgQXR0cmlidXRlTmFtZXNwYWNlPSJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMTIvMDEiPjxzYW1sOkF0dHJpYnV0ZVZhbHVlPmh0dHBzOi8vc3NwbS5taWNyb3NvZnQuY29tPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDpBdHRyaWJ1dGU%2BPHNhbWw6QXR0cmlidXRlIEF0dHJpYnV0ZU5hbWU9ImNsaWVudC1yZXF1ZXN0LWlkIiBBdHRyaWJ1dGVOYW1lc3BhY2U9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vMjAxMi8wMS9yZXF1ZXN0Y29udGV4dC9jbGFpbXMiIGE6T3JpZ2luYWxJc3N1ZXI9IkNMSUVOVCBDT05URVhUIiB4bWxuczphPSJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA5LzA5L2lkZW50aXR5L2NsYWltcyI%2BPHNhbWw6QXR0cmlidXRlVmFsdWU%2BYjQyNzNjMTUtNjBmZC00YmUxLWI1MzgtMTBkZTQ1YTM4NDNiPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDpBdHRyaWJ1dGU%2BPC9zYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD48c2FtbDpBdXRoZW50aWNhdGlvblN0YXRlbWVudCBBdXRoZW50aWNhdGlvbk1ldGhvZD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6MS4wOmFtOnBhc3N3b3JkIiBBdXRoZW50aWNhdGlvbkluc3RhbnQ9IjIwMjQtMDItMjFUMjE6MTQ6MjcuNzA3WiI%2BPHNhbWw6U3ViamVjdD48c2FtbDpOYW1lSWRlbnRpZmllciBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OnVuc3BlY2lmaWVkIj40cEdLNHRUaENrYW1KcUFqMzNzaFpBPT08L3NhbWw6TmFtZUlkZW50aWZpZXI%2BPHNhbWw6U3ViamVjdENvbmZpcm1hdGlvbj48c2FtbDpDb25maXJtYXRpb25NZXRob2Q%2BdXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6MS4wOmNtOmJlYXJlcjwvc2FtbDpDb25maXJtYXRpb25NZXRob2Q%2BPC9zYW1sOlN1YmplY3RDb25maXJtYXRpb24%2BPC9zYW1sOlN1YmplY3Q%2BPC9zYW1sOkF1dGhlbnRpY2F0aW9uU3RhdGVtZW50PjxkczpTaWduYXR1cmUgeG1sbnM6ZHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPjxkczpTaWduZWRJbmZvPjxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8%2BPGRzOlNpZ25hdHVyZU1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZHNpZy1tb3JlI3JzYS1zaGEyNTYiLz48ZHM6UmVmZXJlbmNlIFVSST0iI19jYzRjYzYyYy00M2ZhLTQwNTQtOTVlYy04OTMxYzAyNzdmN2EiPjxkczpUcmFuc2Zvcm1zPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjZW52ZWxvcGVkLXNpZ25hdHVyZSIvPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz48L2RzOlRyYW5zZm9ybXM%2BPGRzOkRpZ2VzdE1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jI3NoYTI1NiIvPjxkczpEaWdlc3RWYWx1ZT40ei9zWmFWalQvWmhzR2RYUXdWZVlpaEVTcVYrRlh0cVhrYWN5cE8xNWhZPTwvZHM6RGlnZXN0VmFsdWU%2BPC9kczpSZWZlcmVuY2U%2BPC9kczpTaWduZWRJbmZvPjxkczpTaWduYXR1cmVWYWx1ZT50a2R5UjN6YTJrYlBRQUhXLzZTRXF1UTNMZ24rMzMxVTltWm5yUkZLUDFNbndUdmZEOUdwRXcrNWRWeFF0d3FBSXFEOXAxV3dEcWJpSUQ3YWdUV2FFOGQ4Ym1CazIxS1hieGhNUDcrM0dqdTJNSExiRjlBSEk5TGZCUVZDbEdjZXJ2MkVicm5sRS9tRjNjUEVjYldWcDV4MkV0RDAwcXhkKzFUdzF5ekVPN0xGU3hOK2NwRmYvU1F2cW42Wnl5bjNSenBFSXljQ1Y5RWdYV0xlS3VsNDdyYmFhRWtXWVIxUk54OG82OE9xTlJPQ0FxTUgxR0c3bjRiY29DemorL3BOd1JCVmhsN0Z6RlFGeVdDeU44YnIwMWdRZ2U2MWZMVDcxblJTQ2JpdVRhN084ajdMamRMR0dsZkg5R0VjbEJId0VRRVVGRDZBS1ZEYUpCRWhQbStlenc9PTwvZHM6U2lnbmF0dXJlVmFsdWU%2BPEtleUluZm8geG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPjxYNTA5RGF0YT48WDUwOUNlcnRpZmljYXRlPk1JSUljRENDQmxpZ0F3SUJBZ0lUTXdBaEtnTENqclVNeDJsaWh3QUFBQ0VxQWpBTkJna3Foa2lHOXcwQkFRd0ZBREJkTVFzd0NRWURWUVFHRXdKVlV6RWVNQndHQTFVRUNoTVZUV2xqY205emIyWjBJRU52Y25CdmNtRjBhVzl1TVM0d0xBWURWUVFERXlWTmFXTnliM052Wm5RZ1FYcDFjbVVnVWxOQklGUk1VeUJKYzNOMWFXNW5JRU5CSURBME1CNFhEVEkwTURFeE5qRTRNVFF5TlZvWERUSTFNREV4TURFNE1UUXlOVm93YlRFTE1Ba0dBMVVFQmhNQ1ZWTXhDekFKQmdOVkJBZ1RBbGRCTVJBd0RnWURWUVFIRXdkU1pXUnRiMjVrTVI0d0hBWURWUVFLRXhWTmFXTnliM052Wm5RZ1EyOXljRzl5WVhScGIyNHhIekFkQmdOVkJBTVRGbTF6Wm5RdWMzUnpMbTFwWTNKdmMyOW1kQzVqYjIwd2dnRWlNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0SUJEd0F3Z2dFS0FvSUJBUURGS2xzVkdYVjlKRk9kVUpPcDQ5emJjSTFVdHl3TUZwNFZyN0RWRFlqemxFazhnRjh3VUpGaHFPYzU2b1pVMGpLQm9BV2tDMHNlZnBmWG1kYXpiN1lzQ0ZqcFVaL3BXZWwwaU5zbURIaUFsK3l0ZnloeWkrQm5rQ0pDckNUSmlhSzJWTnAwUWdZSEpsNktwcFZZRld1QXIzVVZoeUd0Q3hjb1pydVlMQmxiN1VOMlQ2QWJSM2E2V0toRDU4UlJBRGE0RWJlY0NvbjZNdFZ2OUFlUWRpK2l6SzdoaElHZWc1Q1BNdUlFTzAyZCtLZ0FyTGxYQURtM0pPNWllL0p0bm9QU01zQ0FoSlFHcUt4amEyOElsSjRWb3JNalR1VjVsNzB4N1ZVVkJnMkVJZ3ArdmdKOEY1MUtYUGJRS3VKdnlYU0NMRWwrZmY3dFBqRmhpMmkzZ0UvSkFnTUJBQUdqZ2dRWE1JSUVFekNDQVgwR0Npc0dBUVFCMW5rQ0JBSUVnZ0Z0QklJQmFRRm5BSFVBVG5XakoxeWFFTU00VzJ6VTN6OVM2eDN3NEk0YmpXbkFzZnBrc1dLYU9kOEFBQUdORTRTRzFnQUFCQU1BUmpCRUFpQXFYRzNVdFNibGkzZVlVSWxOazhTSVFZVTVKZHhRa2N6b25Pa2FwK2MzT2dJZ01ZMnBaR0JncjdXZEFRQTUwREJUaDhVN09pUTNkUHZtMUorVEp0Vmh4VW9BZGdCOVdSNFM0WGdxZXh4aFozeGUvZmpRaDF3VW9FNlZucmtETDlrT2pDNTV1QUFBQVkwVGhJYXNBQUFFQXdCSE1FVUNJRXJpV0V0Y0tJekNtbTgzTFpLOW03cUg3VXBxVXk4UU1Ld1p0Ty82Mmk2c0FpRUFnNzBBVXNHaE5QYXJBLzVnUE9rTnBNYVd2SWxiN0xqNDlYSXdEUFdyN1ZFQWRnQlZnZFRDRnBBMkFVcnFDNXRYUEZQd3dPUTRlSEFsQ0Jjdm82b2RCeFBUREFBQUFZMFRoSWluQUFBRUF3QkhNRVVDSVFEWEltb0RJaWRIOXBxdFpWcmtQWGJ6MnBhakttaFZ0bEpGOXpPRDhmN0hYZ0lnYVlYOVhjbW5QU3FtajZBZDBqK25CMWYxS0dhb3Uzd2IrQUhJZkE0ZXpBRXdKd1lKS3dZQkJBR0NOeFVLQkJvd0dEQUtCZ2dyQmdFRkJRY0RBakFLQmdnckJnRUZCUWNEQVRBOEJna3JCZ0VFQVlJM0ZRY0VMekF0QmlVckJnRUVBWUkzRlFpSHZkY2JnZWZyUm9LQm5TNk8wQXlIOE5vZFhZS0U1V21DODZjK0FnRmtBZ0VtTUlHMEJnZ3JCZ0VGQlFjQkFRU0JwekNCcERCekJnZ3JCZ0VGQlFjd0FvWm5hSFIwY0RvdkwzZDNkeTV0YVdOeWIzTnZablF1WTI5dEwzQnJhVzl3Y3k5alpYSjBjeTlOYVdOeWIzTnZablFsTWpCQmVuVnlaU1V5TUZKVFFTVXlNRlJNVXlVeU1FbHpjM1ZwYm1jbE1qQkRRU1V5TURBMEpUSXdMU1V5TUhoemFXZHVMbU55ZERBdEJnZ3JCZ0VGQlFjd0FZWWhhSFIwY0RvdkwyOXVaVzlqYzNBdWJXbGpjbTl6YjJaMExtTnZiUzl2WTNOd01CMEdBMVVkRGdRV0JCUVdzdG4vTkhNdzhCMDJzZ0xOY0FMWnhLTU5NVEFPQmdOVkhROEJBZjhFQkFNQ0JhQXdJUVlEVlIwUkJCb3dHSUlXYlhObWRDNXpkSE11YldsamNtOXpiMlowTG1OdmJUQU1CZ05WSFJNQkFmOEVBakFBTUdvR0ExVWRId1JqTUdFd1g2QmRvRnVHV1doMGRIQTZMeTkzZDNjdWJXbGpjbTl6YjJaMExtTnZiUzl3YTJsdmNITXZZM0pzTDAxcFkzSnZjMjltZENVeU1FRjZkWEpsSlRJd1VsTkJKVEl3VkV4VEpUSXdTWE56ZFdsdVp5VXlNRU5CSlRJd01EUXVZM0pzTUdZR0ExVWRJQVJmTUYwd1VRWU1Ld1lCQkFHQ04weURmUUVCTUVFd1B3WUlLd1lCQlFVSEFnRVdNMmgwZEhBNkx5OTNkM2N1YldsamNtOXpiMlowTG1OdmJTOXdhMmx2Y0hNdlJHOWpjeTlTWlhCdmMybDBiM0o1TG1oMGJUQUlCZ1puZ1F3QkFnSXdId1lEVlIwakJCZ3dGb0FVTzNEUlUrbDJKWjFncU1wbUQ4YWJybTlVRm1vd0hRWURWUjBsQkJZd0ZBWUlLd1lCQlFVSEF3SUdDQ3NHQVFVRkJ3TUJNQTBHQ1NxR1NJYjNEUUVCREFVQUE0SUNBUUFFMjlNWDF2WWRXU2xKV3Q4UGlydDhXVGdJanVLSkFmMW9yTE9vM1NQMjdtbHJiNlByQWZtVExYN1VTOG5taVc2bVpGMjl4Vzhnb3ZmWkJNczZ3MFh4d2o3Wjl3RlA2aDhYQ1MvMzh2UkhQZkVTMFBnVkhpUTRYNU41Z0R2azlRQVNXU3RuVzhqVWhaLzFmQ0l3c0Y2dFQ3UnJkM1M3OWFVRjhGRVdWL3M1U0tFM2d1N1BrVzkxQlQ0NG5GcTF6ejNjaWw1Qjc2OEQ0TTBJcDlkY3VaK3RlamtiUGtXVndGaHY2dmx3V3lTeXJ1SUlaNU1lRHpoYUpEaFZqSEQ3V2lGMys2aVVoRUl3T05FM0poVVNSTDF0UjZpZmJGeGhKd1R4b1FtNkxMN21DY3FkRkdGZnNoUlF4OTcxN3RQcFIwbEdWZ3FuWXhsQnd2MmxTZ3pobCttV2p5NEM4dHpnS3RoZ05CaFk1bjdSbWw1RkFqZkE2Y1VQUWg2MHdzMzBBZFNhS0RaOGtWTTFxTEtVb0FkNWhqQi9ad0hGTkZvWVFncjFiZ1Rjc3lsdzRBKzN3dUpPNmYvcjRmWnVvaDNUSmppbDI5MFhGZ3ZwNzZvTGVNeFg3em9uVzNIU2IvY05UZUZMTmVGM3NVYnVRUzBoeE16d25qNmpqSXpGSnhhczU0SlpycGtydjJYcHR6RHZiZWVnM29OQVJHdEc0TkY0TkFmYVRWL2M0QXJHeHRnUWw0V3hlYlU1VXo5UnY5V2tmbEo0bkFpL0RreC85ZGJuUWk3VmtUN2ZCdDBidnI5SGFiNkdmWjYyR3RsRWc2ZWlaNWI4WmlOSFBiMzVIV0tYS0RObnBXb2ljWCtsbVdqZkdBWXRkY1M2NERYM2dtVzN4c28vN1dlNkVLUjNidz09PC9YNTA5Q2VydGlmaWNhdGU%2BPC9YNTA5RGF0YT48L0tleUluZm8%2BPC9kczpTaWduYXR1cmU%2BPC9zYW1sOkFzc2VydGlvbj4%3D&client_id=[REDACTED]&client_info=1&grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Asaml1_1-bearer&password=[REDACTED]&req_cnf=[REDACTED]&scope=6256c85f-0aad-4d50-b960-e6e9b21efe35%2F.default+openid+offline_access+profile&token_type=pop&username=USERNAME
        form:
            assertion:
                - PHNhbWw6QXNzZXJ0aW9uIE1ham9yVmVyc2lvbj0iMSIgTWlub3JWZXJzaW9uPSIxIiBBc3NlcnRpb25JRD0iX2NjNGNjNjJjLTQzZmEtNDA1NC05NWVjLTg5MzFjMDI3N2Y3YSIgSXNzdWVyPSJ1cm46ZmVkZXJhdGlvbjpNU0ZUIiBJc3N1ZUluc3RhbnQ9IjIwMjQtMDItMjFUMjE6MTQ6MjcuNzcwWiIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6MS4wOmFzc2VydGlvbiI+PHNhbWw6Q29uZGl0aW9ucyBOb3RCZWZvcmU9IjIwMjQtMDItMjFUMjE6MTQ6MjcuNzcwWiIgTm90T25PckFmdGVyPSIyMDI0LTAyLTIxVDIyOjE0OjI3Ljc3MFoiPjxzYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb25Db25kaXRpb24+PHNhbWw6QXVkaWVuY2U+dXJuOmZlZGVyYXRpb246TWljcm9zb2Z0T25saW5lPC9zYW1sOkF1ZGllbmNlPjwvc2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uQ29uZGl0aW9uPjwvc2FtbDpDb25kaXRpb25zPjxzYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD48c2FtbDpTdWJqZWN0PjxzYW1sOk5hbWVJZGVudGlmaWVyIEZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6MS4xOm5hbWVpZC1mb3JtYXQ6dW5zcGVjaWZpZWQiPjRwR0s0dFRoQ2thbUpxQWozM3NoWkE9PTwvc2FtbDpOYW1lSWRlbnRpZmllcj48c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uPjxzYW1sOkNvbmZpcm1hdGlvbk1ldGhvZD51cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoxLjA6Y206YmVhcmVyPC9zYW1sOkNvbmZpcm1hdGlvbk1ldGhvZD48L3NhbWw6U3ViamVjdENvbmZpcm1hdGlvbj48L3NhbWw6U3ViamVjdD48c2FtbDpBdHRyaWJ1dGUgQXR0cmlidXRlTmFtZT0iVVBOIiBBdHRyaWJ1dGVOYW1lc3BhY2U9Imh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL2NsYWltcyI+PHNhbWw6QXR0cmlidXRlVmFsdWU+azhjb25uZWN0c2FAbWljcm9zb2Z0LmNvbTwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBBdHRyaWJ1dGVOYW1lPSJvYmplY3RHVUlEIiBBdHRyaWJ1dGVOYW1lc3BhY2U9Imh0dHA6Ly90ZW1wdXJpLmNvbSI+PHNhbWw6QXR0cmlidXRlVmFsdWU+NHBHSzR0VGhDa2FtSnFBajMzc2haQT09PC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDpBdHRyaWJ1dGU+PHNhbWw6QXR0cmlidXRlIEF0dHJpYnV0ZU5hbWU9IlBlcnNvbm5lbE51bWJlciIgQXR0cmlidXRlTmFtZXNwYWNlPSJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy9jbGFpbXMiPjxzYW1sOkF0dHJpYnV0ZVZhbHVlPjRwR0s0dFRoQ2thbUpxQWozM3NoWkE9PTwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBBdHRyaWJ1dGVOYW1lPSJJbW11dGFibGVJRCIgQXR0cmlidXRlTmFtZXNwYWNlPSJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL0xpdmVJRC9GZWRlcmF0aW9uLzIwMDgvMDUiPjxzYW1sOkF0dHJpYnV0ZVZhbHVlPjRwR0s0dFRoQ2thbUpxQWozM3NoWkE9PTwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBBdHRyaWJ1dGVOYW1lPSJhdXRobm1ldGhvZHNyZWZlcmVuY2VzIiBBdHRyaWJ1dGVOYW1lc3BhY2U9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vY2xhaW1zIj48c2FtbDpBdHRyaWJ1dGVWYWx1ZT5odHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvYXV0aGVudGljYXRpb25tZXRob2QvcGFzc3dvcmQ8L3NhbWw6QXR0cmlidXRlVmFsdWU+PC9zYW1sOkF0dHJpYnV0ZT48c2FtbDpBdHRyaWJ1dGUgQXR0cmlidXRlTmFtZT0icGFzc3dvcmRleHBpcmF0aW9udGltZSIgQXR0cmlidXRlTmFtZXNwYWNlPSJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMTIvMDEiPjxzYW1sOkF0dHJpYnV0ZVZhbHVlPjIwMjQtMDktMjFUMjE6MDA6MDMuNzU3Wjwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBBdHRyaWJ1dGVOYW1lPSJwYXNzd29yZGNoYW5nZXVybCIgQXR0cmlidXRlTmFtZXNwYWNlPSJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMTIvMDEiPjxzYW1sOkF0dHJpYnV0ZVZhbHVlPmh0dHBzOi8vc3NwbS5taWNyb3NvZnQuY29tPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDpBdHRyaWJ1dGU+PHNhbWw6QXR0cmlidXRlIEF0dHJpYnV0ZU5hbWU9ImNsaWVudC1yZXF1ZXN0LWlkIiBBdHRyaWJ1dGVOYW1lc3BhY2U9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vMjAxMi8wMS9yZXF1ZXN0Y29udGV4dC9jbGFpbXMiIGE6T3JpZ2luYWxJc3N1ZXI9IkNMSUVOVCBDT05URVhUIiB4bWxuczphPSJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA5LzA5L2lkZW50aXR5L2NsYWltcyI+PHNhbWw6QXR0cmlidXRlVmFsdWU+YjQyNzNjMTUtNjBmZC00YmUxLWI1MzgtMTBkZTQ1YTM4NDNiPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDpBdHRyaWJ1dGU+PC9zYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD48c2FtbDpBdXRoZW50aWNhdGlvblN0YXRlbWVudCBBdXRoZW50aWNhdGlvbk1ldGhvZD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6MS4wOmFtOnBhc3N3b3JkIiBBdXRoZW50aWNhdGlvbkluc3RhbnQ9IjIwMjQtMDItMjFUMjE6MTQ6MjcuNzA3WiI+PHNhbWw6U3ViamVjdD48c2FtbDpOYW1lSWRlbnRpZmllciBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OnVuc3BlY2lmaWVkIj40cEdLNHRUaENrYW1KcUFqMzNzaFpBPT08L3NhbWw6TmFtZUlkZW50aWZpZXI+PHNhbWw6U3ViamVjdENvbmZpcm1hdGlvbj48c2FtbDpDb25maXJtYXRpb25NZXRob2Q+dXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6MS4wOmNtOmJlYXJlcjwvc2FtbDpDb25maXJtYXRpb25NZXRob2Q+PC9zYW1sOlN1YmplY3RDb25maXJtYXRpb24+PC9zYW1sOlN1YmplY3Q+PC9zYW1sOkF1dGhlbnRpY2F0aW9uU3RhdGVtZW50PjxkczpTaWduYXR1cmUgeG1sbnM6ZHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPjxkczpTaWduZWRJbmZvPjxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+PGRzOlNpZ25hdHVyZU1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZHNpZy1tb3JlI3JzYS1zaGEyNTYiLz48ZHM6UmVmZXJlbmNlIFVSST0iI19jYzRjYzYyYy00M2ZhLTQwNTQtOTVlYy04OTMxYzAyNzdmN2EiPjxkczpUcmFuc2Zvcm1zPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjZW52ZWxvcGVkLXNpZ25hdHVyZSIvPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz48L2RzOlRyYW5zZm9ybXM+PGRzOkRpZ2VzdE1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jI3NoYTI1NiIvPjxkczpEaWdlc3RWYWx1ZT40ei9zWmFWalQvWmhzR2RYUXdWZVlpaEVTcVYrRlh0cVhrYWN5cE8xNWhZPTwvZHM6RGlnZXN0VmFsdWU+PC9kczpSZWZlcmVuY2U+PC9kczpTaWduZWRJbmZvPjxkczpTaWduYXR1cmVWYWx1ZT50a2R5UjN6YTJrYlBRQUhXLzZTRXF1UTNMZ24rMzMxVTltWm5yUkZLUDFNbndUdmZEOUdwRXcrNWRWeFF0d3FBSXFEOXAxV3dEcWJpSUQ3YWdUV2FFOGQ4Ym1CazIxS1hieGhNUDcrM0dqdTJNSExiRjlBSEk5TGZCUVZDbEdjZXJ2MkVicm5sRS9tRjNjUEVjYldWcDV4MkV0RDAwcXhkKzFUdzF5ekVPN0xGU3hOK2NwRmYvU1F2cW42Wnl5bjNSenBFSXljQ1Y5RWdYV0xlS3VsNDdyYmFhRWtXWVIxUk54OG82OE9xTlJPQ0FxTUgxR0c3bjRiY29DemorL3BOd1JCVmhsN0Z6RlFGeVdDeU44YnIwMWdRZ2U2MWZMVDcxblJTQ2JpdVRhN084ajdMamRMR0dsZkg5R0VjbEJId0VRRVVGRDZBS1ZEYUpCRWhQbStlenc9PTwvZHM6U2lnbmF0dXJlVmFsdWU+PEtleUluZm8geG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPjxYNTA5RGF0YT48WDUwOUNlcnRpZmljYXRlPk1JSUljRENDQmxpZ0F3SUJBZ0lUTXdBaEtnTENqclVNeDJsaWh3QUFBQ0VxQWpBTkJna3Foa2lHOXcwQkFRd0ZBREJkTVFzd0NRWURWUVFHRXdKVlV6RWVNQndHQTFVRUNoTVZUV2xqY205emIyWjBJRU52Y25CdmNtRjBhVzl1TVM0d0xBWURWUVFERXlWTmFXTnliM052Wm5RZ1FYcDFjbVVnVWxOQklGUk1VeUJKYzNOMWFXNW5JRU5CSURBME1CNFhEVEkwTURFeE5qRTRNVFF5TlZvWERUSTFNREV4TURFNE1UUXlOVm93YlRFTE1Ba0dBMVVFQmhNQ1ZWTXhDekFKQmdOVkJBZ1RBbGRCTVJBd0RnWURWUVFIRXdkU1pXUnRiMjVrTVI0d0hBWURWUVFLRXhWTmFXTnliM052Wm5RZ1EyOXljRzl5WVhScGIyNHhIekFkQmdOVkJBTVRGbTF6Wm5RdWMzUnpMbTFwWTNKdmMyOW1kQzVqYjIwd2dnRWlNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0SUJEd0F3Z2dFS0FvSUJBUURGS2xzVkdYVjlKRk9kVUpPcDQ5emJjSTFVdHl3TUZwNFZyN0RWRFlqemxFazhnRjh3VUpGaHFPYzU2b1pVMGpLQm9BV2tDMHNlZnBmWG1kYXpiN1lzQ0ZqcFVaL3BXZWwwaU5zbURIaUFsK3l0ZnloeWkrQm5rQ0pDckNUSmlhSzJWTnAwUWdZSEpsNktwcFZZRld1QXIzVVZoeUd0Q3hjb1pydVlMQmxiN1VOMlQ2QWJSM2E2V0toRDU4UlJBRGE0RWJlY0NvbjZNdFZ2OUFlUWRpK2l6SzdoaElHZWc1Q1BNdUlFTzAyZCtLZ0FyTGxYQURtM0pPNWllL0p0bm9QU01zQ0FoSlFHcUt4amEyOElsSjRWb3JNalR1VjVsNzB4N1ZVVkJnMkVJZ3ArdmdKOEY1MUtYUGJRS3VKdnlYU0NMRWwrZmY3dFBqRmhpMmkzZ0UvSkFnTUJBQUdqZ2dRWE1JSUVFekNDQVgwR0Npc0dBUVFCMW5rQ0JBSUVnZ0Z0QklJQmFRRm5BSFVBVG5XakoxeWFFTU00VzJ6VTN6OVM2eDN3NEk0YmpXbkFzZnBrc1dLYU9kOEFBQUdORTRTRzFnQUFCQU1BUmpCRUFpQXFYRzNVdFNibGkzZVlVSWxOazhTSVFZVTVKZHhRa2N6b25Pa2FwK2MzT2dJZ01ZMnBaR0JncjdXZEFRQTUwREJUaDhVN09pUTNkUHZtMUorVEp0Vmh4VW9BZGdCOVdSNFM0WGdxZXh4aFozeGUvZmpRaDF3VW9FNlZucmtETDlrT2pDNTV1QUFBQVkwVGhJYXNBQUFFQXdCSE1FVUNJRXJpV0V0Y0tJekNtbTgzTFpLOW03cUg3VXBxVXk4UU1Ld1p0Ty82Mmk2c0FpRUFnNzBBVXNHaE5QYXJBLzVnUE9rTnBNYVd2SWxiN0xqNDlYSXdEUFdyN1ZFQWRnQlZnZFRDRnBBMkFVcnFDNXRYUEZQd3dPUTRlSEFsQ0Jjdm82b2RCeFBUREFBQUFZMFRoSWluQUFBRUF3QkhNRVVDSVFEWEltb0RJaWRIOXBxdFpWcmtQWGJ6MnBhakttaFZ0bEpGOXpPRDhmN0hYZ0lnYVlYOVhjbW5QU3FtajZBZDBqK25CMWYxS0dhb3Uzd2IrQUhJZkE0ZXpBRXdKd1lKS3dZQkJBR0NOeFVLQkJvd0dEQUtCZ2dyQmdFRkJRY0RBakFLQmdnckJnRUZCUWNEQVRBOEJna3JCZ0VFQVlJM0ZRY0VMekF0QmlVckJnRUVBWUkzRlFpSHZkY2JnZWZyUm9LQm5TNk8wQXlIOE5vZFhZS0U1V21DODZjK0FnRmtBZ0VtTUlHMEJnZ3JCZ0VGQlFjQkFRU0JwekNCcERCekJnZ3JCZ0VGQlFjd0FvWm5hSFIwY0RvdkwzZDNkeTV0YVdOeWIzTnZablF1WTI5dEwzQnJhVzl3Y3k5alpYSjBjeTlOYVdOeWIzTnZablFsTWpCQmVuVnlaU1V5TUZKVFFTVXlNRlJNVXlVeU1FbHpjM1ZwYm1jbE1qQkRRU1V5TURBMEpUSXdMU1V5TUhoemFXZHVMbU55ZERBdEJnZ3JCZ0VGQlFjd0FZWWhhSFIwY0RvdkwyOXVaVzlqYzNBdWJXbGpjbTl6YjJaMExtTnZiUzl2WTNOd01CMEdBMVVkRGdRV0JCUVdzdG4vTkhNdzhCMDJzZ0xOY0FMWnhLTU5NVEFPQmdOVkhROEJBZjhFQkFNQ0JhQXdJUVlEVlIwUkJCb3dHSUlXYlhObWRDNXpkSE11YldsamNtOXpiMlowTG1OdmJUQU1CZ05WSFJNQkFmOEVBakFBTUdvR0ExVWRId1JqTUdFd1g2QmRvRnVHV1doMGRIQTZMeTkzZDNjdWJXbGpjbTl6YjJaMExtTnZiUzl3YTJsdmNITXZZM0pzTDAxcFkzSnZjMjltZENVeU1FRjZkWEpsSlRJd1VsTkJKVEl3VkV4VEpUSXdTWE56ZFdsdVp5VXlNRU5CSlRJd01EUXVZM0pzTUdZR0ExVWRJQVJmTUYwd1VRWU1Ld1lCQkFHQ04weURmUUVCTUVFd1B3WUlLd1lCQlFVSEFnRVdNMmgwZEhBNkx5OTNkM2N1YldsamNtOXpiMlowTG1OdmJTOXdhMmx2Y0hNdlJHOWpjeTlTWlhCdmMybDBiM0o1TG1oMGJUQUlCZ1puZ1F3QkFnSXdId1lEVlIwakJCZ3dGb0FVTzNEUlUrbDJKWjFncU1wbUQ4YWJybTlVRm1vd0hRWURWUjBsQkJZd0ZBWUlLd1lCQlFVSEF3SUdDQ3NHQVFVRkJ3TUJNQTBHQ1NxR1NJYjNEUUVCREFVQUE0SUNBUUFFMjlNWDF2WWRXU2xKV3Q4UGlydDhXVGdJanVLSkFmMW9yTE9vM1NQMjdtbHJiNlByQWZtVExYN1VTOG5taVc2bVpGMjl4Vzhnb3ZmWkJNczZ3MFh4d2o3Wjl3RlA2aDhYQ1MvMzh2UkhQZkVTMFBnVkhpUTRYNU41Z0R2azlRQVNXU3RuVzhqVWhaLzFmQ0l3c0Y2dFQ3UnJkM1M3OWFVRjhGRVdWL3M1U0tFM2d1N1BrVzkxQlQ0NG5GcTF6ejNjaWw1Qjc2OEQ0TTBJcDlkY3VaK3RlamtiUGtXVndGaHY2dmx3V3lTeXJ1SUlaNU1lRHpoYUpEaFZqSEQ3V2lGMys2aVVoRUl3T05FM0poVVNSTDF0UjZpZmJGeGhKd1R4b1FtNkxMN21DY3FkRkdGZnNoUlF4OTcxN3RQcFIwbEdWZ3FuWXhsQnd2MmxTZ3pobCttV2p5NEM4dHpnS3RoZ05CaFk1bjdSbWw1RkFqZkE2Y1VQUWg2MHdzMzBBZFNhS0RaOGtWTTFxTEtVb0FkNWhqQi9ad0hGTkZvWVFncjFiZ1Rjc3lsdzRBKzN3dUpPNmYvcjRmWnVvaDNUSmppbDI5MFhGZ3ZwNzZvTGVNeFg3em9uVzNIU2IvY05UZUZMTmVGM3NVYnVRUzBoeE16d25qNmpqSXpGSnhhczU0SlpycGtydjJYcHR6RHZiZWVnM29OQVJHdEc0TkY0TkFmYVRWL2M0QXJHeHRnUWw0V3hlYlU1VXo5UnY5V2tmbEo0bkFpL0RreC85ZGJuUWk3VmtUN2ZCdDBidnI5SGFiNkdmWjYyR3RsRWc2ZWlaNWI4WmlOSFBiMzVIV0tYS0RObnBXb2ljWCtsbVdqZkdBWXRkY1M2NERYM2dtVzN4c28vN1dlNkVLUjNidz09PC9YNTA5Q2VydGlmaWNhdGU+PC9YNTA5RGF0YT48L0tleUluZm8+PC9kczpTaWduYXR1cmU+PC9zYW1sOkFzc2VydGlvbj4=
            client_id:
                - '[REDACTED]'
            client_info:
                - "1"
            grant_type:
                - urn:ietf:params:oauth:grant-type:saml1_1-bearer
            password:
                - '[REDACTED]'
            req_cnf:
                - '[REDACTED]'
            scope:
                - '6dae42f8-4368-4678-94ff-3960e28e3630/.default openid offline_access profile'
            token_type:
                - pop
            username:
                - user@example.com
        headers:
            Accept-Encoding:
                - gzip
            Client-Request-Id:
                - ccb6d130-1f77-495d-b371-3f785ac3cdb6
            Content-Type:
                - application/x-www-form-urlencoded; charset=utf-8
            Return-Client-Request-Id:
                - "false"
            X-Client-Cpu:
                - amd64
            X-Client-Os:
                - linux
            X-Client-Sku:
                - MSAL.Go
            X-Client-Ver:
                - 1.2.0
        url: https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/v2.0/token
        method: POST
      response:
        proto: HTTP/1.1
        proto_major: 1
        proto_minor: 1
        transfer_encoding: []
        trailer: {}
        content_length: 4946
        uncompressed: false
        body: '{"token_type":"Bearer","expires_in":86399,"ext_expires_in":86399,"access_token":"TEST_ACCESS_TOKEN"}'
        headers:
            Cache-Control:
                - no-store, no-cache
            Client-Request-Id:
                - ccb6d130-1f77-495d-b371-3f785ac3cdb6
            Content-Length:
                - "4946"
            Content-Type:
                - application/json; charset=utf-8
            Date:
                - Wed, 21 Feb 2024 21:14:28 GMT
            Expires:
                - "-1"
            P3p:
                - CP="DSP CUR OTPi IND OTRi ONL FIN"
            Pragma:
                - no-cache
            Strict-Transport-Security:
                - max-age=31536000; includeSubDomains
            X-Content-Type-Options:
                - nosniff
            X-Ms-Clitelem:
                - 1,0,0,,
            X-Ms-Ests-Server:
                - 2.1.17396.6 - EUS ProdSlices
            X-Xss-Protection:
                - "0"
        status: 200 OK
        code: 200
        duration: 460.493814ms
0707010000007A000081A4000000000000000000000001691F8CFD000026C9000000000000000000000000000000000000005C00000000kubelogin-0.2.13/pkg/internal/pop/testdata/AcquirePoPTokenConfidentialFromBadSecretVCR.yaml---
version: 2
interactions:
    - id: 0
      request:
        proto: ""
        proto_major: 0
        proto_minor: 0
        content_length: 0
        transfer_encoding: []
        trailer: {}
        host: ""
        remote_addr: ""
        request_uri: ""
        body: ""
        form: {}
        headers:
            Accept-Encoding:
                - gzip
            Client-Request-Id:
                - be8c850b-0a27-4a34-bb09-9c47caa68378
            Return-Client-Request-Id:
                - "false"
            X-Client-Cpu:
                - amd64
            X-Client-Os:
                - linux
            X-Client-Sku:
                - MSAL.Go
            X-Client-Ver:
                - 1.2.0
        url: https://login.microsoftonline.com/common/discovery/instance?api-version=1.1&authorization_endpoint=https%3A%2F%2Flogin.microsoftonline.com%2F00000000-0000-0000-0000-000000000000%2Foauth2%2Fv2.0%2Fauthorize
        method: GET
      response:
        proto: HTTP/1.1
        proto_major: 1
        proto_minor: 1
        transfer_encoding: []
        trailer: {}
        content_length: 980
        uncompressed: false
        body: '{"tenant_discovery_endpoint":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/v2.0/.well-known/openid-configuration","api-version":"1.1","metadata":[{"preferred_network":"login.microsoftonline.com","preferred_cache":"login.windows.net","aliases":["login.microsoftonline.com","login.windows.net","login.microsoft.com","sts.windows.net"]},{"preferred_network":"login.partner.microsoftonline.cn","preferred_cache":"login.partner.microsoftonline.cn","aliases":["login.partner.microsoftonline.cn","login.chinacloudapi.cn"]},{"preferred_network":"login.microsoftonline.de","preferred_cache":"login.microsoftonline.de","aliases":["login.microsoftonline.de"]},{"preferred_network":"login.microsoftonline.us","preferred_cache":"login.microsoftonline.us","aliases":["login.microsoftonline.us","login.usgovcloudapi.net"]},{"preferred_network":"login-us.microsoftonline.com","preferred_cache":"login-us.microsoftonline.com","aliases":["login-us.microsoftonline.com"]}]}'
        headers:
            Access-Control-Allow-Methods:
                - GET, OPTIONS
            Access-Control-Allow-Origin:
                - '*'
            Cache-Control:
                - max-age=86400, private
            Client-Request-Id:
                - be8c850b-0a27-4a34-bb09-9c47caa68378
            Content-Length:
                - "980"
            Content-Type:
                - application/json; charset=utf-8
            Date:
                - Fri, 01 Sep 2023 00:04:12 GMT
            P3p:
                - CP="DSP CUR OTPi IND OTRi ONL FIN"
            Strict-Transport-Security:
                - max-age=31536000; includeSubDomains
            X-Content-Type-Options:
                - nosniff
            X-Ms-Ests-Server:
                - 2.1.16150.3 - SCUS ProdSlices
            X-Xss-Protection:
                - "0"
        status: 200 OK
        code: 200
        duration: 251.892272ms
    - id: 1
      request:
        proto: ""
        proto_major: 0
        proto_minor: 0
        content_length: 0
        transfer_encoding: []
        trailer: {}
        host: ""
        remote_addr: ""
        request_uri: ""
        body: ""
        form: {}
        headers:
            Accept-Encoding:
                - gzip
            Client-Request-Id:
                - 179f7bc3-d530-4b6c-a573-a60bded5a3cd
            Return-Client-Request-Id:
                - "false"
            X-Client-Cpu:
                - amd64
            X-Client-Os:
                - linux
            X-Client-Sku:
                - MSAL.Go
            X-Client-Ver:
                - 1.2.0
        url: https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/v2.0/.well-known/openid-configuration
        method: GET
      response:
        proto: HTTP/1.1
        proto_major: 1
        proto_minor: 1
        transfer_encoding: []
        trailer: {}
        content_length: 1753
        uncompressed: false
        body: '{"token_endpoint":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/v2.0/token","token_endpoint_auth_methods_supported":["client_secret_post","private_key_jwt","client_secret_basic"],"jwks_uri":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/discovery/v2.0/keys","response_modes_supported":["query","fragment","form_post"],"subject_types_supported":["pairwise"],"id_token_signing_alg_values_supported":["RS256"],"response_types_supported":["code","id_token","code id_token","id_token token"],"scopes_supported":["openid","profile","email","offline_access"],"issuer":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/v2.0","request_uri_parameter_supported":false,"userinfo_endpoint":"https://graph.microsoft.com/oidc/userinfo","authorization_endpoint":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/v2.0/authorize","device_authorization_endpoint":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/v2.0/devicecode","http_logout_supported":true,"frontchannel_logout_supported":true,"end_session_endpoint":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/v2.0/logout","claims_supported":["sub","iss","cloud_instance_name","cloud_instance_host_name","cloud_graph_host_name","msgraph_host","aud","exp","iat","auth_time","acr","nonce","preferred_username","name","tid","ver","at_hash","c_hash","email"],"kerberos_endpoint":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/kerberos","tenant_region_scope":"WW","cloud_instance_name":"microsoftonline.com","cloud_graph_host_name":"graph.windows.net","msgraph_host":"graph.microsoft.com","rbac_url":"https://pas.windows.net"}'
        headers:
            Access-Control-Allow-Methods:
                - GET, OPTIONS
            Access-Control-Allow-Origin:
                - '*'
            Cache-Control:
                - max-age=86400, private
            Client-Request-Id:
                - 179f7bc3-d530-4b6c-a573-a60bded5a3cd
            Content-Length:
                - "1753"
            Content-Type:
                - application/json; charset=utf-8
            Date:
                - Fri, 01 Sep 2023 00:04:13 GMT
            P3p:
                - CP="DSP CUR OTPi IND OTRi ONL FIN"
            Strict-Transport-Security:
                - max-age=31536000; includeSubDomains
            X-Content-Type-Options:
                - nosniff
            X-Ms-Ests-Server:
                - 2.1.16209.3 - EUS ProdSlices
            X-Xss-Protection:
                - "0"
        status: 200 OK
        code: 200
        duration: 89.482682ms
    - id: 2
      request:
        proto: ""
        proto_major: 0
        proto_minor: 0
        content_length: 300
        transfer_encoding: []
        trailer: {}
        host: ""
        remote_addr: ""
        request_uri: ""
        body: client_id=[REDACTED]&client_secret=Bad_Secret&grant_type=client_credentials&req_cnf=[REDACTED]&scope=6256c85f-0aad-4d50-b960-e6e9b21efe35%2F.default+openid+offline_access+profile&token_type=pop
        form:
            client_id:
                - '[REDACTED]'
            client_secret:
                - Bad_Secret
            grant_type:
                - client_credentials
            req_cnf:
                - '[REDACTED]'
            scope:
                - '6dae42f8-4368-4678-94ff-3960e28e3630/.default openid offline_access profile'
            token_type:
                - pop
        headers:
            Accept-Encoding:
                - gzip
            Client-Request-Id:
                - 4a139f54-91e6-483a-ab79-25451bd3479f
            Content-Type:
                - application/x-www-form-urlencoded; charset=utf-8
            Return-Client-Request-Id:
                - "false"
            X-Client-Cpu:
                - amd64
            X-Client-Os:
                - linux
            X-Client-Sku:
                - MSAL.Go
            X-Client-Ver:
                - 1.2.0
        url: https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/v2.0/token
        method: POST
      response:
        proto: HTTP/1.1
        proto_major: 1
        proto_minor: 1
        transfer_encoding: []
        trailer: {}
        content_length: 632
        uncompressed: false
        body: '{"error":"invalid_client","error_description":"AADSTS7000215: Invalid client secret provided. Ensure the secret being sent in the request is the client secret value, not the client secret ID, for a secret added to app ''''[REDACTED]''''.\r\nTrace ID: [REDACTED]\r\nCorrelation ID: [REDACTED]\r\nTimestamp: 2023-06-02 21:00:26Z","error_codes":[7000215],"timestamp":"2023-06-02 21:00:26Z","trace_id":"[REDACTED]","correlation_id":"[REDACTED]","error_uri":"https://login.microsoftonline.com/error?code=7000215"}'
        headers:
            Cache-Control:
                - no-store, no-cache
            Client-Request-Id:
                - 4a139f54-91e6-483a-ab79-25451bd3479f
            Content-Length:
                - "632"
            Content-Type:
                - application/json; charset=utf-8
            Date:
                - Fri, 01 Sep 2023 00:04:13 GMT
            Expires:
                - "-1"
            P3p:
                - CP="DSP CUR OTPi IND OTRi ONL FIN"
            Pragma:
                - no-cache
            Strict-Transport-Security:
                - max-age=31536000; includeSubDomains
            X-Content-Type-Options:
                - nosniff
            X-Ms-Clitelem:
                - 1,7000215,0,,
            X-Ms-Ests-Server:
                - 2.1.16209.3 - EUS ProdSlices
            X-Xss-Protection:
                - "0"
        status: 401 Unauthorized
        code: 401
        duration: 175.229239ms
0707010000007B000081A4000000000000000000000001691F8CFD00002525000000000000000000000000000000000000005900000000kubelogin-0.2.13/pkg/internal/pop/testdata/AcquirePoPTokenConfidentialWithSecretVCR.yaml---
version: 2
interactions:
    - id: 0
      request:
        proto: ""
        proto_major: 0
        proto_minor: 0
        content_length: 0
        transfer_encoding: []
        trailer: {}
        host: ""
        remote_addr: ""
        request_uri: ""
        body: ""
        form: {}
        headers:
            Accept-Encoding:
                - gzip
            Client-Request-Id:
                - 065e28b0-f783-4c75-8e2c-e4dc665742d2
            Return-Client-Request-Id:
                - "false"
            X-Client-Cpu:
                - amd64
            X-Client-Os:
                - linux
            X-Client-Sku:
                - MSAL.Go
            X-Client-Ver:
                - 1.2.0
        url: https://login.microsoftonline.com/common/discovery/instance?api-version=1.1&authorization_endpoint=https%3A%2F%2Flogin.microsoftonline.com%2F00000000-0000-0000-0000-000000000000%2Foauth2%2Fv2.0%2Fauthorize
        method: GET
      response:
        proto: HTTP/1.1
        proto_major: 1
        proto_minor: 1
        transfer_encoding: []
        trailer: {}
        content_length: 980
        uncompressed: false
        body: '{"tenant_discovery_endpoint":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/v2.0/.well-known/openid-configuration","api-version":"1.1","metadata":[{"preferred_network":"login.microsoftonline.com","preferred_cache":"login.windows.net","aliases":["login.microsoftonline.com","login.windows.net","login.microsoft.com","sts.windows.net"]},{"preferred_network":"login.partner.microsoftonline.cn","preferred_cache":"login.partner.microsoftonline.cn","aliases":["login.partner.microsoftonline.cn","login.chinacloudapi.cn"]},{"preferred_network":"login.microsoftonline.de","preferred_cache":"login.microsoftonline.de","aliases":["login.microsoftonline.de"]},{"preferred_network":"login.microsoftonline.us","preferred_cache":"login.microsoftonline.us","aliases":["login.microsoftonline.us","login.usgovcloudapi.net"]},{"preferred_network":"login-us.microsoftonline.com","preferred_cache":"login-us.microsoftonline.com","aliases":["login-us.microsoftonline.com"]}]}'
        headers:
            Access-Control-Allow-Methods:
                - GET, OPTIONS
            Access-Control-Allow-Origin:
                - '*'
            Cache-Control:
                - max-age=86400, private
            Client-Request-Id:
                - 065e28b0-f783-4c75-8e2c-e4dc665742d2
            Content-Length:
                - "980"
            Content-Type:
                - application/json; charset=utf-8
            Date:
                - Fri, 01 Sep 2023 00:07:59 GMT
            P3p:
                - CP="DSP CUR OTPi IND OTRi ONL FIN"
            Strict-Transport-Security:
                - max-age=31536000; includeSubDomains
            X-Content-Type-Options:
                - nosniff
            X-Ms-Ests-Server:
                - 2.1.16150.3 - EUS ProdSlices
            X-Xss-Protection:
                - "0"
        status: 200 OK
        code: 200
        duration: 262.893349ms
    - id: 1
      request:
        proto: ""
        proto_major: 0
        proto_minor: 0
        content_length: 0
        transfer_encoding: []
        trailer: {}
        host: ""
        remote_addr: ""
        request_uri: ""
        body: ""
        form: {}
        headers:
            Accept-Encoding:
                - gzip
            Client-Request-Id:
                - 4e0b4541-609a-48ee-91ec-e77d961ef56d
            Return-Client-Request-Id:
                - "false"
            X-Client-Cpu:
                - amd64
            X-Client-Os:
                - linux
            X-Client-Sku:
                - MSAL.Go
            X-Client-Ver:
                - 1.2.0
        url: https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/v2.0/.well-known/openid-configuration
        method: GET
      response:
        proto: HTTP/1.1
        proto_major: 1
        proto_minor: 1
        transfer_encoding: []
        trailer: {}
        content_length: 1753
        uncompressed: false
        body: '{"token_endpoint":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/v2.0/token","token_endpoint_auth_methods_supported":["client_secret_post","private_key_jwt","client_secret_basic"],"jwks_uri":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/discovery/v2.0/keys","response_modes_supported":["query","fragment","form_post"],"subject_types_supported":["pairwise"],"id_token_signing_alg_values_supported":["RS256"],"response_types_supported":["code","id_token","code id_token","id_token token"],"scopes_supported":["openid","profile","email","offline_access"],"issuer":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/v2.0","request_uri_parameter_supported":false,"userinfo_endpoint":"https://graph.microsoft.com/oidc/userinfo","authorization_endpoint":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/v2.0/authorize","device_authorization_endpoint":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/v2.0/devicecode","http_logout_supported":true,"frontchannel_logout_supported":true,"end_session_endpoint":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/v2.0/logout","claims_supported":["sub","iss","cloud_instance_name","cloud_instance_host_name","cloud_graph_host_name","msgraph_host","aud","exp","iat","auth_time","acr","nonce","preferred_username","name","tid","ver","at_hash","c_hash","email"],"kerberos_endpoint":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/kerberos","tenant_region_scope":"WW","cloud_instance_name":"microsoftonline.com","cloud_graph_host_name":"graph.windows.net","msgraph_host":"graph.microsoft.com","rbac_url":"https://pas.windows.net"}'
        headers:
            Access-Control-Allow-Methods:
                - GET, OPTIONS
            Access-Control-Allow-Origin:
                - '*'
            Cache-Control:
                - max-age=86400, private
            Client-Request-Id:
                - 4e0b4541-609a-48ee-91ec-e77d961ef56d
            Content-Length:
                - "1753"
            Content-Type:
                - application/json; charset=utf-8
            Date:
                - Fri, 01 Sep 2023 00:07:59 GMT
            P3p:
                - CP="DSP CUR OTPi IND OTRi ONL FIN"
            Strict-Transport-Security:
                - max-age=31536000; includeSubDomains
            X-Content-Type-Options:
                - nosniff
            X-Ms-Ests-Server:
                - 2.1.16209.3 - EUS ProdSlices
            X-Xss-Protection:
                - "0"
        status: 200 OK
        code: 200
        duration: 91.482757ms
    - id: 2
      request:
        proto: ""
        proto_major: 0
        proto_minor: 0
        content_length: 330
        transfer_encoding: []
        trailer: {}
        host: ""
        remote_addr: ""
        request_uri: ""
        body: client_id=[REDACTED]&client_secret=[REDACTED]&grant_type=client_credentials&req_cnf=[REDACTED]&scope=6256c85f-0aad-4d50-b960-e6e9b21efe35%2F.default+openid+offline_access+profile&token_type=pop
        form:
            client_id:
                - '[REDACTED]'
            client_secret:
                - '[REDACTED]'
            grant_type:
                - client_credentials
            req_cnf:
                - '[REDACTED]'
            scope:
                - '6dae42f8-4368-4678-94ff-3960e28e3630/.default openid offline_access profile'
            token_type:
                - pop
        headers:
            Accept-Encoding:
                - gzip
            Client-Request-Id:
                - 0e0789fc-d6fd-4157-a622-d5da8347b009
            Content-Type:
                - application/x-www-form-urlencoded; charset=utf-8
            Return-Client-Request-Id:
                - "false"
            X-Client-Cpu:
                - amd64
            X-Client-Os:
                - linux
            X-Client-Sku:
                - MSAL.Go
            X-Client-Ver:
                - 1.2.0
        url: https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/v2.0/token
        method: POST
      response:
        proto: HTTP/1.1
        proto_major: 1
        proto_minor: 1
        transfer_encoding: []
        trailer: {}
        content_length: 1475
        uncompressed: false
        body: '{"token_type":"Bearer","expires_in":86399,"ext_expires_in":86399,"access_token":"TEST_ACCESS_TOKEN"}'
        headers:
            Cache-Control:
                - no-store, no-cache
            Client-Request-Id:
                - 0e0789fc-d6fd-4157-a622-d5da8347b009
            Content-Length:
                - "1475"
            Content-Type:
                - application/json; charset=utf-8
            Date:
                - Fri, 01 Sep 2023 00:07:59 GMT
            Expires:
                - "-1"
            P3p:
                - CP="DSP CUR OTPi IND OTRi ONL FIN"
            Pragma:
                - no-cache
            Strict-Transport-Security:
                - max-age=31536000; includeSubDomains
            X-Content-Type-Options:
                - nosniff
            X-Ms-Clitelem:
                - 1,0,0,,
            X-Ms-Ests-Server:
                - 2.1.16209.3 - SCUS ProdSlices
            X-Xss-Protection:
                - "0"
        status: 200 OK
        code: 200
        duration: 157.469883ms
0707010000007C000041ED000000000000000000000002691F8CFD00000000000000000000000000000000000000000000002800000000kubelogin-0.2.13/pkg/internal/testutils0707010000007D000081A4000000000000000000000001691F8CFD0000223B000000000000000000000000000000000000003600000000kubelogin-0.2.13/pkg/internal/testutils/govcrutils.gopackage testutils

import (
	"encoding/json"
	"net/http"
	"regexp"
	"strings"

	"gopkg.in/dnaeon/go-vcr.v4/pkg/cassette"
	"gopkg.in/dnaeon/go-vcr.v4/pkg/recorder"
)

const (
	redactedToken = "[REDACTED]"
	TestToken     = "TEST_ACCESS_TOKEN"
	TestUsername  = "user@example.com"
	TestPassword  = "password123"
	TestTenantID  = "00000000-0000-0000-0000-000000000000"
	TestClientID  = "80faf920-1908-4b52-b5ef-a8e7bedfc67a"
	TestServerID  = "6dae42f8-4368-4678-94ff-3960e28e3630"
)

const (
	mockClientInfo = "eyJ1aWQiOiJjNzNjNmYyOC1hZTVmLTQxM2QtYTlhMi1lMTFlNWFmNjY4ZjgiLCJ1dGlkIjoiZTBiZDIzMjEtMDdmYS00Y2YwLTg3YjgtMDBhYTJhNzQ3MzI5In0"
	mockIDT        = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Imwzc1EtNTBjQ0g0eEJWWkxIVEd3blNSNzY4MCJ9.eyJhdWQiOiIwNGIwNzc5NS04ZGRiLTQ2MWEtYmJlZS0wMmY5ZTFiZjdiNDYiLCJpc3MiOiJodHRwczovL2xvZ2luLm1pY3Jvc29mdG9ubGluZS5jb20vYzU0ZmFjODgtM2RkMy00NjFmLWE3YzQtOGEzNjhlMDM0MGIzL3YyLjAiLCJpYXQiOjE2MzcxOTEyMTIsIm5iZiI6MTYzNzE5MTIxMiwiZXhwIjoxNjM3MTk1MTEyLCJhaW8iOiJBVVFBdS84VEFBQUFQMExOZGNRUXQxNmJoSkFreXlBdjFoUGJuQVhtT0o3RXJDVHV4N0hNTjhHd2VMb2FYMWR1cDJhQ2Y0a0p5bDFzNmovSzF5R05DZmVIQlBXM21QUWlDdz09IiwiaWRwIjoiaHR0cHM6Ly9zdHMud2luZG93cy5uZXQvZTBiZDIzMjEtMDdmYS00Y2YwLTg3YjgtMDBhYTJhNzQ3MzI5LyIsIm5hbWUiOiJJZGVudGl0eSBUZXN0IFVzZXIiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJpZGVudGl0eXRlc3R1c2VyQGF6dXJlc2Rrb3V0bG9vay5vbm1pY3Jvc29mdC5jb20iLCJyaCI6IjAuQVMwQWlLeFB4ZE05SDBhbnhJbzJqZ05BczVWM3NBVGJqUnBHdS00Qy1lR19lMFl0QUxFLiIsInN1YiI6ImMxYTBsY2xtbWxCYW9wc0MwVmlaLVpPMjFCT2dSUXE3SG9HRUtOOXloZnMiLCJ0aWQiOiJjNTRmYWM4OC0zZGQzLTQ2MWYtYTdjNC04YTM2OGUwMzQwYjMiLCJ1dGkiOiI5TXFOSWI5WjdrQy1QVHRtai11X0FBIiwidmVyIjoiMi4wIn0.hh5Exz9MBjTXrTuTZnz7vceiuQjcC_oRSTeBIC9tYgSO2c2sqQRpZi91qBZFQD9okayLPPKcwqXgEJD9p0-c4nUR5UQN7YSeDLmYtZUYMG79EsA7IMiQaiy94AyIe2E-oBDcLwFycGwh1iIOwwOwjbanmu2Dx3HfQx831lH9uVjagf0Aow0wTkTVCsedGSZvG-cRUceFLj-kFN-feFH3NuScuOfLR2Magf541pJda7X7oStwL_RNUFqjJFTdsiFV4e-VHK5qo--3oPU06z0rS9bosj0pFSATIVHrrS4gY7jiSvgMbG837CDBQkz5b08GUN5GlLN9jlygl1plBmbgww"
)

var emailRegex = regexp.MustCompile(`[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}`)

func GetVCRHttpClient(path, tenantID string) (*recorder.Recorder, error) {
	deviceCodePendingCount := 0
	beforeSaveHook := func(i *cassette.Interaction) error {
		// in device code login, since the client polls for the completion of the login
		// we only record it once to speed up the replay
		if strings.Contains(i.Response.Body, "AADSTS70016") {
			if deviceCodePendingCount > 0 {
				i.DiscardOnSave = true
				return nil
			}
			deviceCodePendingCount++
		}
		var detectedClientID,
			detectedClientSecret,
			detectedClientAssertion,
			detectedScope,
			detectedReqCnf,
			detectedPassword,
			detectedUsername,
			detectedDeviceCode string
		// Delete sensitive content
		delete(i.Response.Headers, "Set-Cookie")
		delete(i.Response.Headers, "X-Ms-Request-Id")
		if i.Request.Form["client_id"] != nil {
			detectedClientID = i.Request.Form["client_id"][0]
			i.Request.Form["client_id"] = []string{redactedToken}
		}
		if i.Request.Form["client_secret"] != nil && i.Request.Form["client_secret"][0] != BadSecret {
			detectedClientSecret = i.Request.Form["client_secret"][0]
			i.Request.Form["client_secret"] = []string{redactedToken}
		}
		if i.Request.Form["client_assertion"] != nil {
			detectedClientAssertion = i.Request.Form["client_assertion"][0]
			i.Request.Form["client_assertion"] = []string{redactedToken}
		}
		if i.Request.Form["req_cnf"] != nil {
			detectedScope = i.Request.Form["req_cnf"][0]
			i.Request.Form["req_cnf"] = []string{redactedToken}
		}
		if i.Request.Form["password"] != nil && i.Request.Form["password"][0] != BadSecret {
			detectedPassword = i.Request.Form["password"][0]
			i.Request.Form["password"] = []string{redactedToken}
		}
		if i.Request.Form["username"] != nil {
			detectedUsername = i.Request.Form["username"][0]
			i.Request.Form["username"] = []string{redactedToken}
		}
		if i.Request.Form["device_code"] != nil {
			detectedDeviceCode = i.Request.Form["device_code"][0]
			i.Request.Form["device_code"] = []string{redactedToken}
		}

		i.Request.URL = redactURL(i.Request.URL, tenantID)
		i.Response.Body = strings.ReplaceAll(i.Response.Body, tenantID, TestTenantID)

		if detectedClientID != "" {
			i.Request.Body = strings.ReplaceAll(i.Request.Body, detectedClientID, redactedToken)
		}
		if detectedClientSecret != "" {
			i.Request.Body = ReplaceSecretValuesIncludingURLEscaped(i.Request.Body, detectedClientSecret, redactedToken)
		}
		if detectedClientAssertion != "" {
			i.Request.Body = strings.ReplaceAll(i.Request.Body, detectedClientAssertion, redactedToken)
		}
		if detectedScope != "" {
			i.Request.Body = strings.ReplaceAll(i.Request.Body, detectedScope, redactedToken)
		}
		if detectedReqCnf != "" {
			i.Request.Body = strings.ReplaceAll(i.Request.Body, detectedReqCnf, redactedToken)
		}
		if detectedPassword != "" {
			i.Request.Body = ReplaceSecretValuesIncludingURLEscaped(i.Request.Body, detectedPassword, redactedToken)
		}
		if detectedUsername != "" {
			i.Request.Body = ReplaceSecretValuesIncludingURLEscaped(i.Request.Body, detectedUsername, TestUsername)
			i.Request.URL = ReplaceSecretValuesIncludingURLEscaped(i.Request.URL, detectedUsername, TestUsername)
		}
		if detectedDeviceCode != "" {
			i.Request.Body = strings.ReplaceAll(i.Request.Body, detectedDeviceCode, redactedToken)
		}

		if strings.Contains(i.Response.Body, "access_token") || strings.Contains(i.Response.Body, "device_code") {
			redacted, err := redactToken(i.Response.Body)
			if err != nil {
				return err
			}
			i.Response.Body = redacted
		}

		if strings.Contains(i.Response.Body, "Invalid client secret provided") {
			i.Response.Body = `{"error":"invalid_client","error_description":"AADSTS7000215: Invalid client secret provided. Ensure the secret being sent in the request is the client secret value, not the client secret ID, for a secret added to app ''[REDACTED]''.\r\nTrace ID: [REDACTED]\r\nCorrelation ID: [REDACTED]\r\nTimestamp: 2023-06-02 21:00:26Z","error_codes":[7000215],"timestamp":"2023-06-02 21:00:26Z","trace_id":"[REDACTED]","correlation_id":"[REDACTED]","error_uri":"https://login.microsoftonline.com/error?code=7000215"}`
		}
		return nil
	}

	playbackHook := func(i *cassette.Interaction) error {
		if strings.Contains(i.Response.Body, "access_token") {
			redacted, err := redactToken(i.Response.Body)
			if err != nil {
				return err
			}
			i.Response.Body = redacted
		}
		return nil
	}

	matcher := func(r *http.Request, i cassette.Request) bool {
		url := redactURL(r.URL.String(), tenantID)
		recordedURL := i.URL

		// Normalize URLs by removing trailing empty query strings
		url = normalizeURL(url)
		recordedURL = normalizeURL(recordedURL)

		if r.Method != i.Method || url != recordedURL {
			return false
		}
		_ = r.ParseForm()
		requestFormValues := r.Form
		isPop := i.Form["token_type"] != nil && i.Form["token_type"][0] == "pop"

		for k, v := range i.Form {
			if requestFormValues[k][0] != v[0] {
				// if recorded value is redaction token and request value is empty, then it is a mismatch
				if v[0] == redactedToken {
					if len(requestFormValues[k][0]) == 0 {
						return false
					}
					continue
				}
				// saml assertion is not relevant for the test
				if isPop && k == "assertion" {
					continue
				}
				return false
			}
		}

		return true
	}

	recOpts := []recorder.Option{
		recorder.WithHook(beforeSaveHook, recorder.BeforeSaveHook),
		recorder.WithHook(playbackHook, recorder.BeforeResponseReplayHook),
		recorder.WithMatcher(matcher),
		recorder.WithSkipRequestLatency(true),
	}

	return recorder.New(path, recOpts...)
}

func redactURL(url, tenantID string) string {
	if strings.Contains(url, "UserRealm") {
		url = emailRegex.ReplaceAllString(url, TestUsername)
	}
	return strings.ReplaceAll(url, tenantID, TestTenantID)
}

// normalizeURL removes trailing empty query strings and other URL inconsistencies
func normalizeURL(url string) string {
	// Remove trailing ? if no query parameters follow
	url = strings.TrimSuffix(url, "?")
	return url
}

func redactToken(body string) (string, error) {
	var data map[string]interface{}
	err := json.Unmarshal([]byte(body), &data)
	if err != nil {
		return "", err
	}

	if _, ok := data["access_token"]; ok {
		data["access_token"] = TestToken
	}

	if _, ok := data["refresh_token"]; ok {
		data["refresh_token"] = TestToken
	}

	if _, ok := data["id_token"]; ok {
		data["id_token"] = mockIDT
	}

	if _, ok := data["client_info"]; ok {
		data["client_info"] = mockClientInfo
	}

	if _, ok := data["device_code"]; ok {
		data["device_code"] = redactedToken
	}

	// Marshal the map back to a JSON string
	redactedJSON, err := json.Marshal(data)
	if err != nil {
		return "", err
	}

	return string(redactedJSON), nil
}
0707010000007E000081A4000000000000000000000001691F8CFD000005DE000000000000000000000000000000000000003500000000kubelogin-0.2.13/pkg/internal/testutils/testutils.gopackage testutils

import (
	"net/url"
	"strings"
)

const (
	ClientID       = "AZURE_CLIENT_ID"
	ClientSecret   = "AAD_SERVICE_PRINCIPAL_CLIENT_SECRET"
	ClientCert     = "AZURE_CLIENT_CER"
	ClientCertPass = "AZURE_CLIENT_CERTIFICATE_PASSWORD"
	ResourceID     = "AZURE_RESOURCE_ID"
	TenantID       = "AZURE_TENANT_ID"
	BadSecret      = "Bad_Secret"
	Username       = "USERNAME"
	Password       = "PASSWORD"
)

// ErrorContains takes an input error and a desired substring, checks if the string is present
// in the error message, and returns the boolean result
func ErrorContains(out error, want string) bool {
	substring := strings.TrimSpace(want)
	if out == nil {
		return substring == ""
	}
	if substring == "" {
		return false
	}
	return strings.Contains(out.Error(), substring)
}

// ReplaceSecretValuesIncludingURLEscaped takes an input string, finds any instances of the
// input secret in the string (including in URL-escaped format), and replaces all instances
// with the given redaction token
// This is used for VCR tests as they sometimes include a URL-escaped version of the secret
// in the request body
func ReplaceSecretValuesIncludingURLEscaped(body, secret, redactionToken string) string {
	body = strings.ReplaceAll(body, secret, redactionToken)
	// get the URL-escaped version of the secret which replaces special characters with
	// the URL-safe "%AB" format
	escapedSecret := url.QueryEscape(secret)
	body = strings.ReplaceAll(body, escapedSecret, redactionToken)
	return body
}
0707010000007F000081A4000000000000000000000001691F8CFD00000E0D000000000000000000000000000000000000003A00000000kubelogin-0.2.13/pkg/internal/testutils/testutils_test.gopackage testutils

import (
	"fmt"
	"testing"
)

func TestErrorContains(t *testing.T) {
	testCase := []struct {
		name             string
		err              error
		desiredSubstring string
		expectedResult   bool
	}{
		{
			name:             "should return true if error is nil and desired substring is empty string",
			desiredSubstring: "",
			err:              nil,
			expectedResult:   true,
		},
		{
			name:             "should return true if error is nil and desired substring is whitespace",
			desiredSubstring: "		   ",
			err:              nil,
			expectedResult:   true,
		},
		{
			name:             "should return false if error is not nil and desired substring is empty string",
			desiredSubstring: "",
			err:              fmt.Errorf("test error"),
			expectedResult:   false,
		},
		{
			name:             "should return false if error is not nil and desired substring is whitespace",
			desiredSubstring: "	   		",
			err:              fmt.Errorf("test error"),
			expectedResult:   false,
		},
		{
			name:             "should return false if error is not nil and desired substring is not contained in error",
			desiredSubstring: "not a test error",
			err:              fmt.Errorf("test error"),
			expectedResult:   false,
		},
		{
			name:             "should return true if error is not nil and desired substring is smaller than but contained in error",
			desiredSubstring: "error",
			err:              fmt.Errorf("test error"),
			expectedResult:   true,
		},
		{
			name:             "should return true if error is not nil and desired substring is the same as error string",
			desiredSubstring: "test error",
			err:              fmt.Errorf("test error"),
			expectedResult:   true,
		},
		{
			name:             "should return false if error is not nil and desired substring is the same as error string but has different casing",
			desiredSubstring: "Test Error",
			err:              fmt.Errorf("test error"),
			expectedResult:   false,
		},
	}

	for _, tc := range testCase {
		t.Run(tc.name, func(t *testing.T) {
			result := ErrorContains(tc.err, tc.desiredSubstring)
			if result != tc.expectedResult {
				t.Errorf(
					"comparing error: %s and desired substring: %s, expected %t but got %t",
					tc.err,
					tc.desiredSubstring,
					tc.expectedResult,
					result,
				)
			}
		})
	}
}

func TestReplaceSecretValuesIncludingURLEscaped(t *testing.T) {
	testCase := []struct {
		name           string
		body           string
		secret         string
		expectedResult string
	}{
		{
			name:           "TestReplaceMultipleSecretValuesWithNonEscapedString",
			body:           "This is a test request body. ABC123. This is the rest of the request body. ThisABC123 is another line.",
			secret:         "ABC123",
			expectedResult: "This is a test request body. [REDACTED]. This is the rest of the request body. This[REDACTED] is another line.",
		},
		{
			name:           "TestReplaceMultipleSecretValuesWithStringEscape",
			body:           "This is a test request body. Q#4@6:=. This is the rest of the request body. ThisQ%234%406%3A%3D is another line.",
			secret:         "Q#4@6:=",
			expectedResult: "This is a test request body. [REDACTED]. This is the rest of the request body. This[REDACTED] is another line.",
		},
	}

	for _, tc := range testCase {
		t.Run(tc.name, func(t *testing.T) {
			result := ReplaceSecretValuesIncludingURLEscaped(tc.body, tc.secret, redactedToken)
			if result != tc.expectedResult {
				t.Errorf(
					"expected redaction of secret as \n%s\n but got \n%s\n",
					tc.expectedResult,
					result,
				)
			}
		})
	}
}
07070100000080000041ED000000000000000000000002691F8CFD00000000000000000000000000000000000000000000002400000000kubelogin-0.2.13/pkg/internal/token07070100000081000081A4000000000000000000000001691F8CFD000006BF000000000000000000000000000000000000002E00000000kubelogin-0.2.13/pkg/internal/token/README.MDInstruction for recording response using [GO-VCR](https://github.com/dnaeon/go-vcr) for unit test
# Things to know if you want to record new recording
* All the recorded responses have been saved under folder `/testdata`

* Highly recommand using `RecordOnly` if you want completely new recording, otherwise, current recordings have been modified without the sensitive contents
* Here's the variable you need to input for recording 
Modify these variables 
modify authorizer clientID `AZURE_CLIENT_ID="<specify with real value>"`
modify authorizer clientSecret `AAD_SERVICE_PRINCIPAL_CLIENT_SECRET="<specify with real value>" `
modify authorizer clientCert `AZURE_CLIENT_CER="<specify with real value>"`
modify authorizer clientCertPass `AZURE_CLIENT_CERTIFICATE_PASSWORD="<specify with real value>" `
modify authorizer resourceID `AZURE_RESOURCE_ID="<specify with real value>"`
modify authorizer tenantID `AZURE_TENANT_ID="<specify with real value>" `
modify go-vcr record mode `VCR_MODE="<specify vcr mode>" `
you can set to record mode by setting vcr mode to RecordOnly `VCR_MODE="RecordOnly"`
To return to replay mode, simply unset the enviroment variable by `unset VCR_MODE`

Examples: 
# Recording Mode
* Navigate to `pkg/token` folder in terminal
* Setup your enviroment variables

```
export AZURE_CLIENT_ID="<specify with real value>"
export AAD_SERVICE_PRINCIPAL_CLIENT_SECRET="<specify with real value>"
export AZURE_CLIENT_CER="<specify with real value>"
export AZURE_CLIENT_CERTIFICATE_PASSWORD="<specify with real value>"
export AZURE_RESOURCE_ID="<specify with real value>"
export AZURE_TENANT_ID="<specify with real value>"
export VCR_MODE="RecordOnly"
go test
```

# Replay Mode
```
unset VCR_MODE
go test
```
07070100000082000081A4000000000000000000000001691F8CFD00000ACB000000000000000000000000000000000000004000000000kubelogin-0.2.13/pkg/internal/token/adalclientcertcredential.gopackage token

import (
	"context"
	"fmt"
	"strings"

	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
	"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
	"github.com/Azure/go-autorest/autorest/adal"
)

type ADALClientCertCredential struct {
	oAuthConfig        adal.OAuthConfig
	clientID           string
	clientCert         string
	clientCertPassword string
}

var _ CredentialProvider = (*ADALClientCertCredential)(nil)

func newADALClientCertCredential(opts *Options) (CredentialProvider, error) {
	if !opts.IsLegacy {
		return nil, fmt.Errorf("ADALClientCertCredential is not supported in non-legacy mode")
	}
	if opts.ClientID == "" {
		return nil, fmt.Errorf("client ID cannot be empty")
	}
	if opts.TenantID == "" {
		return nil, fmt.Errorf("tenant ID cannot be empty")
	}
	if opts.ClientCert == "" {
		return nil, fmt.Errorf("client certificate cannot be empty")
	}
	cloud := opts.GetCloudConfiguration()
	oAuthConfig, err := adal.NewOAuthConfig(cloud.ActiveDirectoryAuthorityHost, opts.TenantID)
	if err != nil {
		return nil, fmt.Errorf("failed to create OAuth config: %w", err)
	}
	return &ADALClientCertCredential{
		oAuthConfig:        *oAuthConfig,
		clientID:           opts.ClientID,
		clientCert:         opts.ClientCert,
		clientCertPassword: opts.ClientCertPassword,
	}, nil
}

func (c *ADALClientCertCredential) Name() string {
	return "ADALClientCertCredential"
}

func (c *ADALClientCertCredential) Authenticate(ctx context.Context, opts *policy.TokenRequestOptions) (azidentity.AuthenticationRecord, error) {
	return azidentity.AuthenticationRecord{}, errAuthenticateNotSupported
}

func (c *ADALClientCertCredential) GetToken(ctx context.Context, opts policy.TokenRequestOptions) (azcore.AccessToken, error) {
	// Get the certificate and private key from cert file
	cert, rsaPrivateKey, err := readCertificate(c.clientCert, c.clientCertPassword)
	if err != nil {
		return azcore.AccessToken{}, fmt.Errorf("failed to read certificate: %w", err)
	}

	// to keep backward compatibility,
	// 1. we only support one resource
	// 2. we remove the "/.default" suffix from the resource
	resource := strings.Replace(opts.Scopes[0], "/.default", "", 1)
	spt, err := adal.NewServicePrincipalTokenFromCertificate(
		c.oAuthConfig,
		c.clientID,
		cert,
		rsaPrivateKey,
		resource)
	if err != nil {
		return azcore.AccessToken{}, fmt.Errorf("failed to create service principal token using secret: %w", err)
	}

	if err := spt.EnsureFreshWithContext(ctx); err != nil {
		return azcore.AccessToken{}, err
	}

	token := spt.Token()
	return azcore.AccessToken{Token: token.AccessToken, ExpiresOn: token.Expires()}, nil
}

func (c *ADALClientCertCredential) NeedAuthenticate() bool {
	return false
}
07070100000083000081A4000000000000000000000001691F8CFD00000855000000000000000000000000000000000000004500000000kubelogin-0.2.13/pkg/internal/token/adalclientcertcredential_test.gopackage token

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestNewADALClientCertCredential(t *testing.T) {
	testCases := []struct {
		name           string
		opts           *Options
		expectErrorMsg string
		expectName     string
	}{
		{
			name: "valid options",
			opts: &Options{
				ClientID:           "test-client-id",
				TenantID:           "test-tenant-id",
				ClientCert:         "test-cert-path",
				ClientCertPassword: "test-cert-password",
				IsLegacy:           true,
			},
			expectName: "ADALClientCertCredential",
		},
		{
			name: "missing client ID",
			opts: &Options{
				TenantID:           "test-tenant-id",
				ClientCert:         "test-cert-path",
				ClientCertPassword: "test-cert-password",
				IsLegacy:           true,
			},
			expectErrorMsg: "client ID cannot be empty",
		},
		{
			name: "missing tenant ID",
			opts: &Options{
				ClientID:           "test-client-id",
				ClientCert:         "test-cert-path",
				ClientCertPassword: "test-cert-password",
				IsLegacy:           true,
			},
			expectErrorMsg: "tenant ID cannot be empty",
		},
		{
			name: "missing client certificate",
			opts: &Options{
				ClientID:           "test-client-id",
				TenantID:           "test-tenant-id",
				ClientCertPassword: "test-cert-password",
				IsLegacy:           true,
			},
			expectErrorMsg: "client certificate cannot be empty",
		},
		{
			name: "non-legacy mode",
			opts: &Options{
				ClientID:           "test-client-id",
				TenantID:           "test-tenant-id",
				ClientCert:         "test-cert-path",
				ClientCertPassword: "test-cert-password",
				IsLegacy:           false,
			},
			expectErrorMsg: "ADALClientCertCredential is not supported in non-legacy mode",
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			cred, err := newADALClientCertCredential(tc.opts)
			if tc.expectErrorMsg != "" {
				assert.Error(t, err)
				assert.Equal(t, tc.expectErrorMsg, err.Error())
				assert.Nil(t, cred)
			} else {
				assert.NoError(t, err)
				assert.NotNil(t, cred)
				assert.Equal(t, tc.expectName, cred.Name())
			}
		})
	}
}
07070100000084000081A4000000000000000000000001691F8CFD0000096C000000000000000000000000000000000000004200000000kubelogin-0.2.13/pkg/internal/token/adalclientsecretcredential.gopackage token

import (
	"context"
	"fmt"
	"strings"

	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
	"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
	"github.com/Azure/go-autorest/autorest/adal"
)

type ADALClientSecretCredential struct {
	oAuthConfig  adal.OAuthConfig
	clientID     string
	clientSecret string
}

var _ CredentialProvider = (*ADALClientSecretCredential)(nil)

func newADALClientSecretCredential(opts *Options) (CredentialProvider, error) {
	if !opts.IsLegacy {
		return nil, fmt.Errorf("ADALClientSecretCredential is not supported in non-legacy mode")
	}
	if opts.ClientID == "" {
		return nil, fmt.Errorf("client ID cannot be empty")
	}
	if opts.TenantID == "" {
		return nil, fmt.Errorf("tenant ID cannot be empty")
	}
	if opts.ClientSecret == "" {
		return nil, fmt.Errorf("client secret cannot be empty")
	}
	cloud := opts.GetCloudConfiguration()
	oAuthConfig, err := adal.NewOAuthConfig(cloud.ActiveDirectoryAuthorityHost, opts.TenantID)
	if err != nil {
		return nil, fmt.Errorf("failed to create OAuth config: %w", err)
	}
	return &ADALClientSecretCredential{
		oAuthConfig:  *oAuthConfig,
		clientID:     opts.ClientID,
		clientSecret: opts.ClientSecret,
	}, nil
}

func (c *ADALClientSecretCredential) Name() string {
	return "ADALClientSecretCredential"
}

func (c *ADALClientSecretCredential) Authenticate(ctx context.Context, opts *policy.TokenRequestOptions) (azidentity.AuthenticationRecord, error) {
	return azidentity.AuthenticationRecord{}, errAuthenticateNotSupported
}

func (c *ADALClientSecretCredential) GetToken(ctx context.Context, opts policy.TokenRequestOptions) (azcore.AccessToken, error) {
	// to keep backward compatibility,
	// 1. we only support one resource
	// 2. we remove the "/.default" suffix from the resource
	resource := strings.Replace(opts.Scopes[0], "/.default", "", 1)
	spt, err := adal.NewServicePrincipalToken(
		c.oAuthConfig,
		c.clientID,
		c.clientSecret,
		resource)
	if err != nil {
		return azcore.AccessToken{}, fmt.Errorf("failed to create service principal token using secret: %w", err)
	}

	if err := spt.EnsureFreshWithContext(ctx); err != nil {
		return azcore.AccessToken{}, err
	}

	token := spt.Token()
	return azcore.AccessToken{Token: token.AccessToken, ExpiresOn: token.Expires()}, nil
}

func (c *ADALClientSecretCredential) NeedAuthenticate() bool {
	return false
}
07070100000085000081A4000000000000000000000001691F8CFD0000070B000000000000000000000000000000000000004700000000kubelogin-0.2.13/pkg/internal/token/adalclientsecretcredential_test.gopackage token

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestNewADALClientSecretCredential(t *testing.T) {
	testCases := []struct {
		name           string
		opts           *Options
		expectErrorMsg string
		expectName     string
	}{
		{
			name: "valid options",
			opts: &Options{
				ClientID:     "test-client-id",
				TenantID:     "test-tenant-id",
				ClientSecret: "test-client-secret",
				IsLegacy:     true,
			},
			expectName: "ADALClientSecretCredential",
		},
		{
			name: "missing client ID",
			opts: &Options{
				TenantID:     "test-tenant-id",
				ClientSecret: "test-client-secret",
				IsLegacy:     true,
			},
			expectErrorMsg: "client ID cannot be empty",
		},
		{
			name: "missing tenant ID",
			opts: &Options{
				ClientID:     "test-client-id",
				ClientSecret: "test-client-secret",
				IsLegacy:     true,
			},
			expectErrorMsg: "tenant ID cannot be empty",
		},
		{
			name: "missing client secret",
			opts: &Options{
				ClientID: "test-client-id",
				TenantID: "test-tenant-id",
				IsLegacy: true,
			},
			expectErrorMsg: "client secret cannot be empty",
		},
		{
			name: "non-legacy mode",
			opts: &Options{
				ClientID:     "test-client-id",
				TenantID:     "test-tenant-id",
				ClientSecret: "test-client-secret",
				IsLegacy:     false,
			},
			expectErrorMsg: "ADALClientSecretCredential is not supported in non-legacy mode",
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			cred, err := newADALClientSecretCredential(tc.opts)
			if tc.expectErrorMsg != "" {
				assert.Error(t, err)
				assert.Equal(t, tc.expectErrorMsg, err.Error())
				assert.Nil(t, cred)
			} else {
				assert.NoError(t, err)
				assert.NotNil(t, cred)
				assert.Equal(t, tc.expectName, cred.Name())
			}
		})
	}
}
07070100000086000081A4000000000000000000000001691F8CFD000009ED000000000000000000000000000000000000004000000000kubelogin-0.2.13/pkg/internal/token/adaldevicecodecredential.gopackage token

import (
	"context"
	"fmt"
	"os"
	"strings"

	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
	"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
	"github.com/Azure/go-autorest/autorest"
	"github.com/Azure/go-autorest/autorest/adal"
)

type ADALDeviceCodeCredential struct {
	oAuthConfig adal.OAuthConfig
	clientID    string
}

var _ CredentialProvider = (*ADALDeviceCodeCredential)(nil)

func newADALDeviceCodeCredential(opts *Options) (CredentialProvider, error) {
	if !opts.IsLegacy {
		return nil, fmt.Errorf("ADALDeviceCodeCredential is not supported in non-legacy mode")
	}
	if opts.ClientID == "" {
		return nil, fmt.Errorf("client ID cannot be empty")
	}
	if opts.TenantID == "" {
		return nil, fmt.Errorf("tenant ID cannot be empty")
	}
	cloud := opts.GetCloudConfiguration()
	oAuthConfig, err := adal.NewOAuthConfig(cloud.ActiveDirectoryAuthorityHost, opts.TenantID)
	if err != nil {
		return nil, fmt.Errorf("failed to create OAuth config: %w", err)
	}
	return &ADALDeviceCodeCredential{
		oAuthConfig: *oAuthConfig,
		clientID:    opts.ClientID,
	}, nil
}

func (c *ADALDeviceCodeCredential) Name() string {
	return "ADALDeviceCodeCredential"
}

func (c *ADALDeviceCodeCredential) Authenticate(ctx context.Context, opts *policy.TokenRequestOptions) (azidentity.AuthenticationRecord, error) {
	return azidentity.AuthenticationRecord{}, errAuthenticateNotSupported
}

func (c *ADALDeviceCodeCredential) GetToken(ctx context.Context, opts policy.TokenRequestOptions) (azcore.AccessToken, error) {
	client := &autorest.Client{}
	// to keep backward compatibility,
	// 1. we only support one resource
	// 2. we remove the "/.default" suffix from the resource
	resource := strings.Replace(opts.Scopes[0], "/.default", "", 1)
	deviceCode, err := adal.InitiateDeviceAuth(client, c.oAuthConfig, c.clientID, resource)
	if err != nil {
		return azcore.AccessToken{}, fmt.Errorf("initialing the device code authentication: %w", err)
	}

	if _, err := fmt.Fprintln(os.Stderr, *deviceCode.Message); err != nil {
		return azcore.AccessToken{}, fmt.Errorf("prompting the device code message: %w", err)
	}

	token, err := adal.WaitForUserCompletionWithContext(ctx, client, deviceCode)
	if err != nil {
		return azcore.AccessToken{}, fmt.Errorf("waiting for device code authentication to complete: %w", err)
	}

	return azcore.AccessToken{Token: token.AccessToken, ExpiresOn: token.Expires()}, nil
}

func (c *ADALDeviceCodeCredential) NeedAuthenticate() bool {
	return false
}
07070100000087000081A4000000000000000000000001691F8CFD00000500000000000000000000000000000000000000004500000000kubelogin-0.2.13/pkg/internal/token/adaldevicecodecredential_test.gopackage token

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestNewADALDeviceCodeCredential(t *testing.T) {
	testCases := []struct {
		name     string
		opts     *Options
		expected string
	}{
		{
			name: "valid options",
			opts: &Options{
				ClientID: "test-client-id",
				TenantID: "test-tenant-id",
				IsLegacy: true,
			},
			expected: "ADALDeviceCodeCredential",
		},
		{
			name: "missing client ID",
			opts: &Options{
				TenantID: "test-tenant-id",
				IsLegacy: true,
			},
			expected: "client ID cannot be empty",
		},
		{
			name: "missing tenant ID",
			opts: &Options{
				ClientID: "test-client-id",
				IsLegacy: true,
			},
			expected: "tenant ID cannot be empty",
		},
		{
			name: "non-legacy mode",
			opts: &Options{
				ClientID: "test-client-id",
				TenantID: "test-tenant-id",
				IsLegacy: false,
			},
			expected: "ADALDeviceCodeCredential is not supported in non-legacy mode",
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			cred, err := newADALDeviceCodeCredential(tc.opts)
			if err != nil {
				assert.EqualError(t, err, tc.expected)
				assert.Nil(t, cred)
			} else {
				assert.NoError(t, err)
				assert.NotNil(t, cred)
				assert.Equal(t, tc.expected, cred.Name())
			}
		})
	}
}
07070100000088000081A4000000000000000000000001691F8CFD000003D3000000000000000000000000000000000000003C00000000kubelogin-0.2.13/pkg/internal/token/authenticationrecord.gopackage token

import (
	"encoding/json"
	"os"
	"path/filepath"

	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
)

type CachedRecordProvider interface {
	// Retrieve reads the authentication record from the file.
	Retrieve() (azidentity.AuthenticationRecord, error)
	// Store writes the authentication record to the file.
	Store(record azidentity.AuthenticationRecord) error
}

type defaultCachedRecordProvider struct {
	file string
}

func (c *defaultCachedRecordProvider) Retrieve() (azidentity.AuthenticationRecord, error) {
	record := azidentity.AuthenticationRecord{}
	b, err := os.ReadFile(c.file)
	if err == nil {
		err = json.Unmarshal(b, &record)
	}
	return record, err
}

func (c *defaultCachedRecordProvider) Store(record azidentity.AuthenticationRecord) error {
	b, err := json.Marshal(record)
	if err != nil {
		return err
	}

	dir := filepath.Dir(c.file)
	if err := os.MkdirAll(dir, 0700); err != nil {
		return err
	}

	return os.WriteFile(c.file, b, 0600)
}
07070100000089000081A4000000000000000000000001691F8CFD00000BA2000000000000000000000000000000000000004100000000kubelogin-0.2.13/pkg/internal/token/authenticationrecord_test.gopackage token

import (
	"os"
	"path/filepath"
	"testing"

	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
	"github.com/stretchr/testify/assert"
)

func TestDefaultCachedRecordProvider(t *testing.T) {
	testCases := []struct {
		name           string
		fileContent    string
		expectErrorMsg string
	}{
		{
			name:        "valid record",
			fileContent: `{"tenantID":"test-tenant-id","clientID":"test-client-id","authority":"https://login.microsoftonline.com/","homeAccountID":"test-home-account-id","username":"test-username","version":"1.0"}`,
		},
		{
			name:           "invalid JSON",
			fileContent:    `invalid-json-content`,
			expectErrorMsg: "invalid character",
		},
		{
			name:           "empty file",
			fileContent:    ``,
			expectErrorMsg: "unexpected end of JSON input",
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			file, err := os.CreateTemp("", "test-record-*.json")
			assert.NoError(t, err)
			defer os.Remove(file.Name())

			_, err = file.WriteString(tc.fileContent)
			assert.NoError(t, err)
			file.Close()

			provider := &defaultCachedRecordProvider{file: file.Name()}
			record, err := provider.Retrieve()
			if tc.expectErrorMsg != "" {
				assert.Error(t, err)
				assert.Contains(t, err.Error(), tc.expectErrorMsg)
			} else {
				assert.NoError(t, err)
				assert.NotNil(t, record)
			}
		})
	}

	record := azidentity.AuthenticationRecord{
		TenantID:      "test-tenant-id",
		ClientID:      "test-client-id",
		Authority:     "https://login.microsoftonline.com/",
		HomeAccountID: "test-home-account-id",
		Username:      "test-username",
		Version:       "1.0",
	}

	file, err := os.CreateTemp("", "test-record-*.json")
	assert.NoError(t, err)
	defer os.Remove(file.Name())

	provider := &defaultCachedRecordProvider{file: file.Name()}
	err = provider.Store(record)
	assert.NoError(t, err)

	storedRecord, err := provider.Retrieve()
	assert.NoError(t, err)
	assert.Equal(t, record, storedRecord)
}

func TestDefaultCachedRecordProvider_NonExistentDirectory(t *testing.T) {
	tempDir, err := os.MkdirTemp("", "test-record-*")
	assert.NoError(t, err)
	defer os.RemoveAll(tempDir)

	nonExistentDir := filepath.Join(tempDir, "subdir", "nested")
	filePath := filepath.Join(nonExistentDir, "record.json")

	record := azidentity.AuthenticationRecord{
		TenantID:      "test-tenant-id",
		ClientID:      "test-client-id",
		Authority:     "https://login.microsoftonline.com/",
		HomeAccountID: "test-home-account-id",
		Username:      "test-username",
		Version:       "1.0",
	}

	provider := &defaultCachedRecordProvider{file: filePath}
	err = provider.Store(record)
	assert.NoError(t, err)

	// Verify the file was created and can be read
	storedRecord, err := provider.Retrieve()
	assert.NoError(t, err)
	assert.Equal(t, record, storedRecord)

	// Verify the directory was created with correct permissions
	fileInfo, err := os.Stat(nonExistentDir)
	assert.NoError(t, err)
	assert.True(t, fileInfo.IsDir())
}
0707010000008A000081A4000000000000000000000001691F8CFD0000049F000000000000000000000000000000000000003A00000000kubelogin-0.2.13/pkg/internal/token/azureclicredential.gopackage token

import (
	"context"
	"fmt"

	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
	"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
)

type AzureCLICredential struct {
	cred *azidentity.AzureCLICredential
}

var _ CredentialProvider = (*AzureCLICredential)(nil)

func newAzureCLICredential(opts *Options) (CredentialProvider, error) {
	cred, err := azidentity.NewAzureCLICredential(&azidentity.AzureCLICredentialOptions{
		TenantID: opts.TenantID,
	})
	if err != nil {
		return nil, fmt.Errorf("failed to create azure cli credential: %w", err)
	}
	return &AzureCLICredential{cred: cred}, nil
}

func (c *AzureCLICredential) Name() string {
	return "AzureCLICredential"
}

func (c *AzureCLICredential) Authenticate(ctx context.Context, opts *policy.TokenRequestOptions) (azidentity.AuthenticationRecord, error) {
	return azidentity.AuthenticationRecord{}, errAuthenticateNotSupported
}

func (c *AzureCLICredential) GetToken(ctx context.Context, opts policy.TokenRequestOptions) (azcore.AccessToken, error) {
	return c.cred.GetToken(ctx, opts)
}

func (c *AzureCLICredential) NeedAuthenticate() bool {
	return false
}
0707010000008B000081A4000000000000000000000001691F8CFD000002FB000000000000000000000000000000000000003F00000000kubelogin-0.2.13/pkg/internal/token/azureclicredential_test.gopackage token

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestNewAzureCLICredential(t *testing.T) {
	testCases := []struct {
		name           string
		opts           *Options
		expectErrorMsg string
		expectName     string
	}{
		{
			name: "valid options",
			opts: &Options{
				TenantID: "test-tenant-id",
			},
			expectName: "AzureCLICredential",
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			cred, err := newAzureCLICredential(tc.opts)
			if tc.expectErrorMsg != "" {
				assert.Error(t, err)
				assert.Equal(t, tc.expectErrorMsg, err.Error())
				assert.Nil(t, cred)
			} else {
				assert.NoError(t, err)
				assert.NotNil(t, cred)
				assert.Equal(t, tc.expectName, cred.Name())
			}
		})
	}
}
0707010000008C000081A4000000000000000000000001691F8CFD00000515000000000000000000000000000000000000003D00000000kubelogin-0.2.13/pkg/internal/token/azuredevopscredential.gopackage token

import (
	"context"
	"fmt"

	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
	"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
)

type AzureDeveloperCLICredential struct {
	cred *azidentity.AzureDeveloperCLICredential
}

var _ CredentialProvider = (*AzureDeveloperCLICredential)(nil)

func newAzureDeveloperCLICredential(opts *Options) (CredentialProvider, error) {
	cred, err := azidentity.NewAzureDeveloperCLICredential(&azidentity.AzureDeveloperCLICredentialOptions{
		TenantID: opts.TenantID,
	})
	if err != nil {
		return nil, fmt.Errorf("failed to create azure developer cli credential: %w", err)
	}
	return &AzureDeveloperCLICredential{cred: cred}, nil
}

func (c *AzureDeveloperCLICredential) Name() string {
	return "AzureDeveloperCLICredential"
}

func (c *AzureDeveloperCLICredential) Authenticate(ctx context.Context, opts *policy.TokenRequestOptions) (azidentity.AuthenticationRecord, error) {
	return azidentity.AuthenticationRecord{}, errAuthenticateNotSupported
}

func (c *AzureDeveloperCLICredential) GetToken(ctx context.Context, opts policy.TokenRequestOptions) (azcore.AccessToken, error) {
	return c.cred.GetToken(ctx, opts)
}

func (c *AzureDeveloperCLICredential) NeedAuthenticate() bool {
	return false
}
0707010000008D000081A4000000000000000000000001691F8CFD00000316000000000000000000000000000000000000004200000000kubelogin-0.2.13/pkg/internal/token/azuredevopscredential_test.gopackage token

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestNewAzureDeveloperCLICredential(t *testing.T) {
	testCases := []struct {
		name           string
		opts           *Options
		expectErrorMsg string
		expectName     string
	}{
		{
			name: "valid options",
			opts: &Options{
				TenantID: "test-tenant-id",
			},
			expectName: "AzureDeveloperCLICredential",
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			cred, err := newAzureDeveloperCLICredential(tc.opts)
			if tc.expectErrorMsg != "" {
				assert.Error(t, err)
				assert.Equal(t, tc.expectErrorMsg, err.Error())
				assert.Nil(t, cred)
			} else {
				assert.NoError(t, err)
				assert.NotNil(t, cred)
				assert.Equal(t, tc.expectName, cred.Name())
			}
		})
	}
}
0707010000008E000081A4000000000000000000000001691F8CFD00000826000000000000000000000000000000000000004000000000kubelogin-0.2.13/pkg/internal/token/azurepipelinescredential.gopackage token

import (
	"context"
	"fmt"
	"os"

	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
	"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
	"github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache"
	"k8s.io/klog/v2"

	"github.com/Azure/kubelogin/pkg/internal/env"
)

type AzurePipelinesCredential struct {
	cred *azidentity.AzurePipelinesCredential
}

var _ CredentialProvider = (*AzurePipelinesCredential)(nil)

func newAzurePipelinesCredential(opts *Options) (CredentialProvider, error) {
	systemAccessToken := os.Getenv(env.SystemAccessToken)
	if systemAccessToken == "" {
		return nil, fmt.Errorf("%s environment variable not set", env.SystemAccessToken)
	}

	var (
		c   azidentity.Cache
		err error
	)
	if opts.UsePersistentCache {
		c, err = cache.New(nil)
		if err != nil {
			klog.V(5).Infof("failed to create cache: %v", err)
		}
	}

	azOpts := &azidentity.AzurePipelinesCredentialOptions{
		ClientOptions:            azcore.ClientOptions{Cloud: opts.GetCloudConfiguration()},
		Cache:                    c,
		DisableInstanceDiscovery: opts.DisableInstanceDiscovery,
	}

	if opts.httpClient != nil {
		azOpts.Transport = opts.httpClient
	}

	cred, err := azidentity.NewAzurePipelinesCredential(
		opts.TenantID,
		opts.ClientID,
		opts.AzurePipelinesServiceConnectionID,
		systemAccessToken,
		azOpts,
	)
	if err != nil {
		return nil, fmt.Errorf("failed to create azure pipelines credential: %w", err)
	}
	return &AzurePipelinesCredential{cred: cred}, nil
}

func (c *AzurePipelinesCredential) Name() string {
	return "AzurePipelinesCredential"
}

func (c *AzurePipelinesCredential) Authenticate(ctx context.Context, opts *policy.TokenRequestOptions) (azidentity.AuthenticationRecord, error) {
	return azidentity.AuthenticationRecord{}, errAuthenticateNotSupported
}

func (c *AzurePipelinesCredential) GetToken(ctx context.Context, opts policy.TokenRequestOptions) (azcore.AccessToken, error) {
	return c.cred.GetToken(ctx, opts)
}

func (c *AzurePipelinesCredential) NeedAuthenticate() bool {
	return false
}
0707010000008F000081A4000000000000000000000001691F8CFD00000A3D000000000000000000000000000000000000004500000000kubelogin-0.2.13/pkg/internal/token/azurepipelinescredential_test.gopackage token

import (
	"fmt"
	"os"
	"testing"

	"github.com/Azure/kubelogin/pkg/internal/env"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestNewAzurePipelinesCredential(t *testing.T) {
	// Clean up environment variables after test
	defer func() {
		os.Unsetenv(env.SystemAccessToken)
		os.Unsetenv(env.SystemOIDCRequestURI)
	}()

	tests := []struct {
		name                 string
		opts                 *Options
		systemAccessToken    string
		systemOIDCRequestURI string
		expectError          bool
		expectErrorSubstring string
	}{
		{
			name: "valid credentials",
			opts: &Options{
				TenantID:                          "test-tenant-id",
				ClientID:                          "test-client-id",
				AzurePipelinesServiceConnectionID: "test-service-connection-id",
			},
			systemAccessToken:    "test-system-access-token",
			systemOIDCRequestURI: "https://test.oidc.request.uri",
			expectError:          false,
		},
		{
			name: "missing system access token",
			opts: &Options{
				TenantID:                          "test-tenant-id",
				ClientID:                          "test-client-id",
				AzurePipelinesServiceConnectionID: "test-service-connection-id",
			},
			systemAccessToken:    "",
			systemOIDCRequestURI: "https://test.oidc.request.uri",
			expectError:          true,
			expectErrorSubstring: fmt.Sprintf("%s environment variable not set", env.SystemAccessToken),
		},
		{
			name: "missing tenant ID",
			opts: &Options{
				ClientID:                          "test-client-id",
				AzurePipelinesServiceConnectionID: "test-service-connection-id",
			},
			systemAccessToken:    "test-system-access-token",
			systemOIDCRequestURI: "https://test.oidc.request.uri",
			expectError:          true,
			expectErrorSubstring: "failed to create azure pipelines credential",
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			if test.systemAccessToken != "" {
				os.Setenv(env.SystemAccessToken, test.systemAccessToken)
			} else {
				os.Unsetenv(env.SystemAccessToken)
			}

			if test.systemOIDCRequestURI != "" {
				os.Setenv(env.SystemOIDCRequestURI, test.systemOIDCRequestURI)
			} else {
				os.Unsetenv(env.SystemOIDCRequestURI)
			}

			cred, err := newAzurePipelinesCredential(test.opts)

			if test.expectError {
				require.Error(t, err)
				assert.Contains(t, err.Error(), test.expectErrorSubstring)
				assert.Nil(t, cred)
			} else {
				require.NoError(t, err)
				assert.NotNil(t, cred)
				assert.Equal(t, "AzurePipelinesCredential", cred.Name())
				assert.False(t, cred.NeedAuthenticate())
			}
		})
	}
}07070100000090000081A4000000000000000000000001691F8CFD0000156F000000000000000000000000000000000000003C00000000kubelogin-0.2.13/pkg/internal/token/clientcertcredential.gopackage token

import (
	"context"
	"crypto/rsa"
	"crypto/x509"
	"encoding/pem"
	"fmt"
	"os"
	"strings"

	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
	"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
	"github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache"
	"golang.org/x/crypto/pkcs12"
	"k8s.io/klog/v2"
)

type ClientCertificateCredential struct {
	cred *azidentity.ClientCertificateCredential
}

var _ CredentialProvider = (*ClientCertificateCredential)(nil)

func newClientCertificateCredential(opts *Options) (CredentialProvider, error) {
	if opts.ClientID == "" {
		return nil, fmt.Errorf("client ID cannot be empty")
	}
	if opts.TenantID == "" {
		return nil, fmt.Errorf("tenant ID cannot be empty")
	}
	if opts.ClientCert == "" {
		return nil, fmt.Errorf("client certificate cannot be empty")
	}
	var (
		c   azidentity.Cache
		err error
	)
	if opts.UsePersistentCache {
		c, err = cache.New(nil)
		if err != nil {
			klog.V(5).Infof("failed to create cache: %v", err)
		}
	}

	// Get the certificate and private key from file
	cert, rsaPrivateKey, err := readCertificate(opts.ClientCert, opts.ClientCertPassword)
	if err != nil {
		return nil, fmt.Errorf("failed to read certificate: %w", err)
	}

	azOpts := &azidentity.ClientCertificateCredentialOptions{
		ClientOptions:            azcore.ClientOptions{Cloud: opts.GetCloudConfiguration()},
		Cache:                    c,
		SendCertificateChain:     true,
		DisableInstanceDiscovery: opts.DisableInstanceDiscovery,
	}

	if opts.httpClient != nil {
		azOpts.Transport = opts.httpClient
	}

	cred, err := azidentity.NewClientCertificateCredential(
		opts.TenantID, opts.ClientID,
		[]*x509.Certificate{cert}, rsaPrivateKey,
		azOpts)
	if err != nil {
		return nil, fmt.Errorf("failed to create client certificate credential: %w", err)
	}
	return &ClientCertificateCredential{cred: cred}, nil
}

func (c *ClientCertificateCredential) Name() string {
	return "ClientCertificateCredential"
}

func (c *ClientCertificateCredential) Authenticate(ctx context.Context, opts *policy.TokenRequestOptions) (azidentity.AuthenticationRecord, error) {
	return azidentity.AuthenticationRecord{}, errAuthenticateNotSupported
}

func (c *ClientCertificateCredential) GetToken(ctx context.Context, opts policy.TokenRequestOptions) (azcore.AccessToken, error) {
	return c.cred.GetToken(ctx, opts)
}

func (c *ClientCertificateCredential) NeedAuthenticate() bool {
	return false
}

func isPublicKeyEqual(key1, key2 *rsa.PublicKey) bool {
	if key1.N == nil || key2.N == nil {
		return false
	}
	return key1.E == key2.E && key1.N.Cmp(key2.N) == 0
}

func splitPEMBlock(pemBlock []byte) (certPEM []byte, keyPEM []byte) {
	for {
		var derBlock *pem.Block
		derBlock, pemBlock = pem.Decode(pemBlock)
		if derBlock == nil {
			break
		}
		switch derBlock.Type {
		case "CERTIFICATE":
			certPEM = append(certPEM, pem.EncodeToMemory(derBlock)...)
		case "PRIVATE KEY":
			keyPEM = append(keyPEM, pem.EncodeToMemory(derBlock)...)
		}
	}

	return certPEM, keyPEM
}

func parseRsaPrivateKey(privateKeyPEM []byte) (*rsa.PrivateKey, error) {
	block, _ := pem.Decode(privateKeyPEM)
	if block == nil {
		return nil, fmt.Errorf("failed to decode a pem block from private key")
	}

	privatePkcs1Key, errPkcs1 := x509.ParsePKCS1PrivateKey(block.Bytes)
	if errPkcs1 == nil {
		return privatePkcs1Key, nil
	}

	privatePkcs8Key, errPkcs8 := x509.ParsePKCS8PrivateKey(block.Bytes)
	if errPkcs8 == nil {
		privatePkcs8RsaKey, ok := privatePkcs8Key.(*rsa.PrivateKey)
		if !ok {
			return nil, fmt.Errorf("pkcs8 contained non-RSA key. Expected RSA key")
		}
		return privatePkcs8RsaKey, nil
	}

	return nil, fmt.Errorf("failed to parse private key as Pkcs#1 or Pkcs#8. (%w), (%w)", errPkcs1, errPkcs8)
}

func parseKeyPairFromPEMBlock(pemBlock []byte) (*x509.Certificate, *rsa.PrivateKey, error) {
	certPEM, keyPEM := splitPEMBlock(pemBlock)

	privateKey, err := parseRsaPrivateKey(keyPEM)
	if err != nil {
		return nil, nil, err
	}

	found := false
	var cert *x509.Certificate
	for {
		var certBlock *pem.Block
		var err error
		certBlock, certPEM = pem.Decode(certPEM)
		if certBlock == nil {
			break
		}

		cert, err = x509.ParseCertificate(certBlock.Bytes)
		if err != nil {
			return nil, nil, fmt.Errorf("unable to parse certificate: %w", err)
		}

		certPublicKey, ok := cert.PublicKey.(*rsa.PublicKey)
		if ok && isPublicKeyEqual(certPublicKey, &privateKey.PublicKey) {
			found = true
			break
		}
	}

	if !found {
		return nil, nil, fmt.Errorf("unable to find a matching public certificate")
	}

	return cert, privateKey, nil
}

func decodePkcs12(pkcs []byte, password string) (*x509.Certificate, *rsa.PrivateKey, error) {
	blocks, err := pkcs12.ToPEM(pkcs, password)
	if err != nil {
		return nil, nil, err
	}

	var pemData []byte
	for _, b := range blocks {
		pemData = append(pemData, pem.EncodeToMemory(b)...)
	}

	return parseKeyPairFromPEMBlock(pemData)
}

func readCertificate(certFile, password string) (*x509.Certificate, *rsa.PrivateKey, error) {
	if strings.HasSuffix(certFile, ".pfx") {
		cert, err := os.ReadFile(certFile)
		if err != nil {
			return nil, nil, fmt.Errorf("failed to read the certificate file (%s): %w", certFile, err)
		}
		return decodePkcs12(cert, password)
	} else {
		cert, err := os.ReadFile(certFile)
		if err != nil {
			return nil, nil, fmt.Errorf("failed to read the certificate file (%s): %w", certFile, err)
		}
		return parseKeyPairFromPEMBlock(cert)
	}
}
07070100000091000081A4000000000000000000000001691F8CFD00000447000000000000000000000000000000000000004100000000kubelogin-0.2.13/pkg/internal/token/clientcertcredential_test.gopackage token

import (
	"context"
	"os"
	"testing"

	"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
	"github.com/Azure/kubelogin/pkg/internal/testutils"
	"github.com/stretchr/testify/assert"
)

func TestClientCertCredential_GetToken(t *testing.T) {
	certFile := os.Getenv("KUBELOGIN_LIVETEST_CERTIFICATE_FILE")
	if certFile == "" {
		certFile = "fixtures/cert.pem"
	}

	rec, err := testutils.GetVCRHttpClient("fixtures/client_cert_credential", testutils.TestTenantID)
	if err != nil {
		t.Fatalf("failed to create recorder: %v", err)
	}
	defer rec.Stop()

	opts := &Options{
		ClientID:   testutils.TestClientID,
		ServerID:   testutils.TestServerID,
		ClientCert: certFile,
		TenantID:   testutils.TestTenantID,
		httpClient: rec.GetDefaultClient(),
	}

	cred, err := newClientCertificateCredential(opts)
	if err != nil {
		t.Fatalf("failed to create credential: %v", err)
	}

	token, err := cred.GetToken(context.Background(), policy.TokenRequestOptions{
		Scopes: []string{opts.ServerID + "/.default"},
	})
	assert.NoError(t, err)
	assert.Equal(t, testutils.TestToken, token.Token)
}
07070100000092000081A4000000000000000000000001691F8CFD00000ECD000000000000000000000000000000000000004300000000kubelogin-0.2.13/pkg/internal/token/clientcertcredentialwithpop.gopackage token

import (
	"context"
	"crypto/x509"
	"fmt"
	"net/url"
	"time"

	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
	"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
	"github.com/Azure/kubelogin/pkg/internal/pop"
	"github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential"
)

type ClientCertificateCredentialWithPoP struct {
	popClaims   map[string]string
	cred        confidential.Credential
	client      confidential.Client
	options     *pop.MsalClientOptions
	keyProvider PoPKeyProvider
}

var _ CredentialProvider = (*ClientCertificateCredentialWithPoP)(nil)

func newClientCertificateCredentialWithPoP(opts *Options) (CredentialProvider, error) {
	if opts.ClientID == "" {
		return nil, fmt.Errorf("client ID cannot be empty")
	}
	if opts.TenantID == "" {
		return nil, fmt.Errorf("tenant ID cannot be empty")
	}
	if opts.ClientCert == "" {
		return nil, fmt.Errorf("client certificate cannot be empty")
	}
	popClaimsMap, err := parsePoPClaims(opts.PoPTokenClaims)
	if err != nil {
		return nil, fmt.Errorf("unable to parse PoP claims: %w", err)
	}
	if len(popClaimsMap) == 0 {
		return nil, fmt.Errorf("number of pop claims is invalid: %d", len(popClaimsMap))
	}

	// Get the certificate and private key from cert file
	cert, rsaPrivateKey, err := readCertificate(opts.ClientCert, opts.ClientCertPassword)
	if err != nil {
		return nil, fmt.Errorf("failed to read certificate: %w", err)
	}

	cred, err := confidential.NewCredFromCert([]*x509.Certificate{cert}, rsaPrivateKey)
	if err != nil {
		return nil, fmt.Errorf("unable to create credential from certificate: %w", err)
	}

	// Construct authority URL properly to avoid malformation
	authorityURL, err := url.JoinPath(opts.GetCloudConfiguration().ActiveDirectoryAuthorityHost, opts.TenantID)
	if err != nil {
		return nil, fmt.Errorf("unable to construct authority URL: %w", err)
	}

	msalOpts := &pop.MsalClientOptions{
		Authority:                authorityURL,
		ClientID:                 opts.ClientID,
		TenantID:                 opts.TenantID,
		DisableInstanceDiscovery: opts.DisableInstanceDiscovery,
	}
	if opts.httpClient != nil {
		msalOpts.Options.Transport = opts.httpClient
	}
	// Get cache from Options
	popCache := opts.GetPoPTokenCache()

	client, err := pop.NewConfidentialClient(
		cred,
		msalOpts,
		pop.WithCustomCacheConfidential(popCache),
	)
	if err != nil {
		return nil, fmt.Errorf("unable to create confidential client: %w", err)
	}

	return &ClientCertificateCredentialWithPoP{
		popClaims:   popClaimsMap,
		cred:        cred,
		client:      client,
		options:     msalOpts,
		keyProvider: opts.GetPoPKeyProvider(),
	}, nil
}

func (c *ClientCertificateCredentialWithPoP) Name() string {
	return "ClientCertificateCredentialWithPoP"
}

func (c *ClientCertificateCredentialWithPoP) Authenticate(ctx context.Context, opts *policy.TokenRequestOptions) (azidentity.AuthenticationRecord, error) {
	return azidentity.AuthenticationRecord{}, errAuthenticateNotSupported
}

func (c *ClientCertificateCredentialWithPoP) GetToken(ctx context.Context, opts policy.TokenRequestOptions) (azcore.AccessToken, error) {
	// Get PoP key using centralized key provider
	popKey, err := c.keyProvider.GetPoPKey()
	if err != nil {
		return azcore.AccessToken{}, err
	}

	accessToken, expiresOn, err := pop.AcquirePoPTokenConfidential(
		ctx,
		c.popClaims,
		opts.Scopes,
		c.client,
		c.options.TenantID,
		popKey,
	)
	if err != nil {
		return azcore.AccessToken{}, fmt.Errorf("failed to create PoP token using client certificate credential: %w", err)
	}
	return azcore.AccessToken{Token: accessToken, ExpiresOn: time.Unix(expiresOn, 0)}, nil
}

func (c *ClientCertificateCredentialWithPoP) NeedAuthenticate() bool {
	return false
}
07070100000093000081A4000000000000000000000001691F8CFD00000F5A000000000000000000000000000000000000004800000000kubelogin-0.2.13/pkg/internal/token/clientcertcredentialwithpop_test.gopackage token

import (
	"os"
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestNewClientCertificateCredentialWithPoP(t *testing.T) {
	certFile := os.Getenv("KUBELOGIN_LIVETEST_CERTIFICATE_FILE")
	if certFile == "" {
		certFile = "fixtures/cert.pem"
	}

	testCases := []struct {
		name           string
		opts           *Options
		expectErrorMsg string
		expectName     string
	}{
		{
			name: "valid options",
			opts: &Options{
				ClientID:          "test-client-id",
				TenantID:          "test-tenant-id",
				ClientCert:        certFile,
				IsPoPTokenEnabled: true,
				PoPTokenClaims:    "u=test-cluster",
				AuthorityHost:     "https://login.microsoftonline.com/",
			},
			expectName: "ClientCertificateCredentialWithPoP",
		},
		{
			name: "missing client ID",
			opts: &Options{
				TenantID:          "test-tenant-id",
				ClientCert:        certFile,
				IsPoPTokenEnabled: true,
				PoPTokenClaims:    "u=test-cluster",
				AuthorityHost:     "https://login.microsoftonline.com/",
			},
			expectErrorMsg: "client ID cannot be empty",
		},
		{
			name: "missing tenant ID",
			opts: &Options{
				ClientID:          "test-client-id",
				ClientCert:        certFile,
				IsPoPTokenEnabled: true,
				PoPTokenClaims:    "u=test-cluster",
				AuthorityHost:     "https://login.microsoftonline.com/",
			},
			expectErrorMsg: "tenant ID cannot be empty",
		},
		{
			name: "missing client certificate",
			opts: &Options{
				ClientID:          "test-client-id",
				TenantID:          "test-tenant-id",
				IsPoPTokenEnabled: true,
				PoPTokenClaims:    "u=test-cluster",
				AuthorityHost:     "https://login.microsoftonline.com/",
			},
			expectErrorMsg: "client certificate cannot be empty",
		},
		{
			name: "missing PoP claims",
			opts: &Options{
				ClientID:          "test-client-id",
				TenantID:          "test-tenant-id",
				ClientCert:        certFile,
				IsPoPTokenEnabled: true,
				AuthorityHost:     "https://login.microsoftonline.com/",
			},
			expectErrorMsg: "unable to parse PoP claims: failed to parse PoP token claims: no claims provided",
		},
		{
			name: "invalid PoP claims format",
			opts: &Options{
				ClientID:          "test-client-id",
				TenantID:          "test-tenant-id",
				ClientCert:        certFile,
				IsPoPTokenEnabled: true,
				PoPTokenClaims:    "invalid-format",
				AuthorityHost:     "https://login.microsoftonline.com/",
			},
			expectErrorMsg: "unable to parse PoP claims: failed to parse PoP token claims. Ensure the claims are formatted as `key=value` with no extra whitespace",
		},
		{
			name: "missing required u-claim",
			opts: &Options{
				ClientID:          "test-client-id",
				TenantID:          "test-tenant-id",
				ClientCert:        certFile,
				IsPoPTokenEnabled: true,
				PoPTokenClaims:    "key=value",
				AuthorityHost:     "https://login.microsoftonline.com/",
			},
			expectErrorMsg: "unable to parse PoP claims: required u-claim not provided for PoP token flow. Please provide the ARM ID of the cluster in the format `u=<ARM_ID>`",
		},
		{
			name: "invalid certificate file",
			opts: &Options{
				ClientID:          "test-client-id",
				TenantID:          "test-tenant-id",
				ClientCert:        "nonexistent.pem",
				IsPoPTokenEnabled: true,
				PoPTokenClaims:    "u=test-cluster",
				AuthorityHost:     "https://login.microsoftonline.com/",
			},
			expectErrorMsg: "failed to read certificate: failed to read the certificate file (nonexistent.pem):",
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			cred, err := newClientCertificateCredentialWithPoP(tc.opts)

			if tc.expectErrorMsg != "" {
				assert.Error(t, err)
				if tc.expectErrorMsg != "" {
					assert.Contains(t, err.Error(), tc.expectErrorMsg)
				}
				assert.Nil(t, cred)
			} else {
				assert.NoError(t, err)
				assert.NotNil(t, cred)
				assert.Equal(t, tc.expectName, cred.Name())
			}
		})
	}
}
07070100000094000081A4000000000000000000000001691F8CFD000007F8000000000000000000000000000000000000003E00000000kubelogin-0.2.13/pkg/internal/token/clientsecretcredential.gopackage token

import (
	"context"
	"fmt"

	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
	"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
	"github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache"
	"k8s.io/klog/v2"
)

type ClientSecretCredential struct {
	cred *azidentity.ClientSecretCredential
}

var _ CredentialProvider = (*ClientSecretCredential)(nil)

func newClientSecretCredential(opts *Options) (CredentialProvider, error) {
	if opts.ClientID == "" {
		return nil, fmt.Errorf("client ID cannot be empty")
	}
	if opts.TenantID == "" {
		return nil, fmt.Errorf("tenant ID cannot be empty")
	}
	if opts.ClientSecret == "" {
		return nil, fmt.Errorf("client secret cannot be empty")
	}
	var (
		c   azidentity.Cache
		err error
	)
	if opts.UsePersistentCache {
		c, err = cache.New(nil)
		if err != nil {
			klog.V(5).Infof("failed to create cache: %v", err)
		}
	}

	azOpts := &azidentity.ClientSecretCredentialOptions{
		ClientOptions:            azcore.ClientOptions{Cloud: opts.GetCloudConfiguration()},
		Cache:                    c,
		DisableInstanceDiscovery: opts.DisableInstanceDiscovery,
	}

	if opts.httpClient != nil {
		azOpts.Transport = opts.httpClient
	}

	cred, err := azidentity.NewClientSecretCredential(
		opts.TenantID, opts.ClientID, opts.ClientSecret, azOpts)
	if err != nil {
		return nil, fmt.Errorf("failed to create client secret credential: %w", err)
	}
	return &ClientSecretCredential{cred: cred}, nil
}

func (c *ClientSecretCredential) Name() string {
	return "ClientSecretCredential"
}

func (c *ClientSecretCredential) Authenticate(ctx context.Context, opts *policy.TokenRequestOptions) (azidentity.AuthenticationRecord, error) {
	return azidentity.AuthenticationRecord{}, errAuthenticateNotSupported
}

func (c *ClientSecretCredential) GetToken(ctx context.Context, opts policy.TokenRequestOptions) (azcore.AccessToken, error) {
	return c.cred.GetToken(ctx, opts)
}

func (c *ClientSecretCredential) NeedAuthenticate() bool {
	return false
}
07070100000095000081A4000000000000000000000001691F8CFD000003D4000000000000000000000000000000000000004300000000kubelogin-0.2.13/pkg/internal/token/clientsecretcredential_test.gopackage token

import (
	"context"
	"testing"

	"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
	"github.com/Azure/kubelogin/pkg/internal/testutils"
	"github.com/stretchr/testify/assert"
)

func TestClientSecretCredential_GetToken(t *testing.T) {
	rec, err := testutils.GetVCRHttpClient("fixtures/client_secret_credential", testutils.TestTenantID)
	if err != nil {
		t.Fatalf("failed to create recorder: %v", err)
	}
	defer rec.Stop()

	opts := &Options{
		ClientID:     testutils.TestClientID,
		ServerID:     testutils.TestServerID,
		ClientSecret: "password",
		TenantID:     testutils.TestTenantID,
		httpClient:   rec.GetDefaultClient(),
	}

	cred, err := newClientSecretCredential(opts)
	if err != nil {
		t.Fatalf("failed to create credential: %v", err)
	}

	token, err := cred.GetToken(context.Background(), policy.TokenRequestOptions{
		Scopes: []string{opts.ServerID + "/.default"},
	})
	assert.NoError(t, err)
	assert.Equal(t, testutils.TestToken, token.Token)
}
07070100000096000081A4000000000000000000000001691F8CFD00000D8D000000000000000000000000000000000000004500000000kubelogin-0.2.13/pkg/internal/token/clientsecretcredentialwithpop.gopackage token

import (
	"context"
	"fmt"
	"net/url"
	"time"

	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
	"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
	"github.com/Azure/kubelogin/pkg/internal/pop"
	"github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential"
)

type ClientSecretCredentialWithPoP struct {
	popClaims   map[string]string
	cred        confidential.Credential
	client      confidential.Client
	options     *pop.MsalClientOptions
	keyProvider PoPKeyProvider
}

var _ CredentialProvider = (*ClientSecretCredentialWithPoP)(nil)

func newClientSecretCredentialWithPoP(opts *Options) (CredentialProvider, error) {
	if opts.ClientID == "" {
		return nil, fmt.Errorf("client ID cannot be empty")
	}
	if opts.TenantID == "" {
		return nil, fmt.Errorf("tenant ID cannot be empty")
	}
	if opts.ClientSecret == "" {
		return nil, fmt.Errorf("client secret cannot be empty")
	}
	popClaimsMap, err := parsePoPClaims(opts.PoPTokenClaims)
	if err != nil {
		return nil, fmt.Errorf("unable to parse PoP claims: %w", err)
	}
	if len(popClaimsMap) == 0 {
		return nil, fmt.Errorf("number of pop claims is invalid: %d", len(popClaimsMap))
	}

	cred, err := confidential.NewCredFromSecret(opts.ClientSecret)
	if err != nil {
		return nil, fmt.Errorf("unable to create confidential credential: %w", err)
	}

	// Construct authority URL properly to avoid malformation
	authorityURL, err := url.JoinPath(opts.GetCloudConfiguration().ActiveDirectoryAuthorityHost, opts.TenantID)
	if err != nil {
		return nil, fmt.Errorf("unable to construct authority URL: %w", err)
	}

	msalOpts := &pop.MsalClientOptions{
		Authority:                authorityURL,
		ClientID:                 opts.ClientID,
		TenantID:                 opts.TenantID,
		DisableInstanceDiscovery: opts.DisableInstanceDiscovery,
	}
	if opts.httpClient != nil {
		msalOpts.Options.Transport = opts.httpClient
	}
	// Get cache from Options
	popCache := opts.GetPoPTokenCache()

	client, err := pop.NewConfidentialClient(
		cred,
		msalOpts,
		pop.WithCustomCacheConfidential(popCache),
	)
	if err != nil {
		return nil, fmt.Errorf("unable to create confidential client: %w", err)
	}

	return &ClientSecretCredentialWithPoP{
		popClaims:   popClaimsMap,
		cred:        cred,
		client:      client,
		options:     msalOpts,
		keyProvider: opts.GetPoPKeyProvider(),
	}, nil
}

func (c *ClientSecretCredentialWithPoP) Name() string {
	return "ClientSecretCredentialWithPoP"
}

func (c *ClientSecretCredentialWithPoP) Authenticate(ctx context.Context, opts *policy.TokenRequestOptions) (azidentity.AuthenticationRecord, error) {
	return azidentity.AuthenticationRecord{}, errAuthenticateNotSupported
}

func (c *ClientSecretCredentialWithPoP) GetToken(ctx context.Context, opts policy.TokenRequestOptions) (azcore.AccessToken, error) {
	// Get PoP key using centralized key provider
	popKey, err := c.keyProvider.GetPoPKey()
	if err != nil {
		return azcore.AccessToken{}, err
	}

	accessToken, expiresOn, err := pop.AcquirePoPTokenConfidential(
		ctx,
		c.popClaims,
		opts.Scopes,
		c.client,
		c.options.TenantID,
		popKey,
	)
	if err != nil {
		return azcore.AccessToken{}, fmt.Errorf("failed to create PoP token using client secret credential: %w", err)
	}
	return azcore.AccessToken{Token: accessToken, ExpiresOn: time.Unix(expiresOn, 0)}, nil
}

func (c *ClientSecretCredentialWithPoP) NeedAuthenticate() bool {
	return false
}
07070100000097000081A4000000000000000000000001691F8CFD00000D06000000000000000000000000000000000000004A00000000kubelogin-0.2.13/pkg/internal/token/clientsecretcredentialwithpop_test.gopackage token

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestNewClientSecretCredentialWithPoP(t *testing.T) {
	testCases := []struct {
		name           string
		opts           *Options
		expectErrorMsg string
		expectName     string
	}{
		{
			name: "valid options",
			opts: &Options{
				ClientID:          "test-client-id",
				TenantID:          "test-tenant-id",
				ClientSecret:      "test-secret",
				IsPoPTokenEnabled: true,
				PoPTokenClaims:    "u=test-cluster",
				AuthorityHost:     "https://login.microsoftonline.com/",
			},
			expectName: "ClientSecretCredentialWithPoP",
		},
		{
			name: "missing client ID",
			opts: &Options{
				TenantID:          "test-tenant-id",
				ClientSecret:      "test-secret",
				IsPoPTokenEnabled: true,
				PoPTokenClaims:    "u=test-cluster",
				AuthorityHost:     "https://login.microsoftonline.com/",
			},
			expectErrorMsg: "client ID cannot be empty",
		},
		{
			name: "missing tenant ID",
			opts: &Options{
				ClientID:          "test-client-id",
				ClientSecret:      "test-secret",
				IsPoPTokenEnabled: true,
				PoPTokenClaims:    "u=test-cluster",
				AuthorityHost:     "https://login.microsoftonline.com/",
			},
			expectErrorMsg: "tenant ID cannot be empty",
		},
		{
			name: "missing client secret",
			opts: &Options{
				ClientID:          "test-client-id",
				TenantID:          "test-tenant-id",
				IsPoPTokenEnabled: true,
				PoPTokenClaims:    "u=test-cluster",
				AuthorityHost:     "https://login.microsoftonline.com/",
			},
			expectErrorMsg: "client secret cannot be empty",
		},
		{
			name: "missing PoP claims",
			opts: &Options{
				ClientID:          "test-client-id",
				TenantID:          "test-tenant-id",
				ClientSecret:      "test-secret",
				IsPoPTokenEnabled: true,
				AuthorityHost:     "https://login.microsoftonline.com/",
			},
			expectErrorMsg: "unable to parse PoP claims: failed to parse PoP token claims: no claims provided",
		},
		{
			name: "invalid PoP claims format",
			opts: &Options{
				ClientID:          "test-client-id",
				TenantID:          "test-tenant-id",
				ClientSecret:      "test-secret",
				IsPoPTokenEnabled: true,
				PoPTokenClaims:    "invalid-format",
				AuthorityHost:     "https://login.microsoftonline.com/",
			},
			expectErrorMsg: "unable to parse PoP claims: failed to parse PoP token claims. Ensure the claims are formatted as `key=value` with no extra whitespace",
		},
		{
			name: "missing required u-claim",
			opts: &Options{
				ClientID:          "test-client-id",
				TenantID:          "test-tenant-id",
				ClientSecret:      "test-secret",
				IsPoPTokenEnabled: true,
				PoPTokenClaims:    "key=value",
				AuthorityHost:     "https://login.microsoftonline.com/",
			},
			expectErrorMsg: "unable to parse PoP claims: required u-claim not provided for PoP token flow. Please provide the ARM ID of the cluster in the format `u=<ARM_ID>`",
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			cred, err := newClientSecretCredentialWithPoP(tc.opts)
			if tc.expectErrorMsg != "" {
				assert.Error(t, err)
				assert.Equal(t, tc.expectErrorMsg, err.Error())
				assert.Nil(t, cred)
			} else {
				assert.NoError(t, err)
				assert.NotNil(t, cred)
				assert.Equal(t, tc.expectName, cred.Name())
			}
		})
	}
}
07070100000098000081A4000000000000000000000001691F8CFD0000086D000000000000000000000000000000000000003C00000000kubelogin-0.2.13/pkg/internal/token/devicecodecredential.gopackage token

import (
	"context"
	"fmt"
	"os"

	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
	"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
	"github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache"
	"k8s.io/klog/v2"
)

type DeviceCodeCredential struct {
	cred *azidentity.DeviceCodeCredential
}

var _ CredentialProvider = (*DeviceCodeCredential)(nil)

func newDeviceCodeCredential(opts *Options, record azidentity.AuthenticationRecord) (CredentialProvider, error) {
	if opts.ClientID == "" {
		return nil, fmt.Errorf("client ID cannot be empty")
	}
	if opts.TenantID == "" {
		return nil, fmt.Errorf("tenant ID cannot be empty")
	}
	var (
		c   azidentity.Cache
		err error
	)
	if opts.UsePersistentCache {
		c, err = cache.New(nil)
		if err != nil {
			klog.V(5).Infof("failed to create cache: %v", err)
		}
	}

	azOpts := &azidentity.DeviceCodeCredentialOptions{
		ClientOptions:            azcore.ClientOptions{Cloud: opts.GetCloudConfiguration()},
		AuthenticationRecord:     record,
		Cache:                    c,
		ClientID:                 opts.ClientID,
		TenantID:                 opts.TenantID,
		DisableInstanceDiscovery: opts.DisableInstanceDiscovery,
		UserPrompt: func(ctx context.Context, dcm azidentity.DeviceCodeMessage) error {
			_, err := fmt.Fprintln(os.Stderr, dcm.Message)
			return err
		},
	}

	if opts.httpClient != nil {
		azOpts.Transport = opts.httpClient
	}

	cred, err := azidentity.NewDeviceCodeCredential(azOpts)
	if err != nil {
		return nil, fmt.Errorf("failed to create device code credential: %w", err)
	}
	return &DeviceCodeCredential{cred: cred}, nil
}

func (c *DeviceCodeCredential) Name() string {
	return "DeviceCodeCredential"
}

func (c *DeviceCodeCredential) Authenticate(ctx context.Context, opts *policy.TokenRequestOptions) (azidentity.AuthenticationRecord, error) {
	return c.cred.Authenticate(ctx, opts)
}

func (c *DeviceCodeCredential) GetToken(ctx context.Context, opts policy.TokenRequestOptions) (azcore.AccessToken, error) {
	return c.cred.GetToken(ctx, opts)
}

func (c *DeviceCodeCredential) NeedAuthenticate() bool {
	return true
}
07070100000099000081A4000000000000000000000001691F8CFD00000413000000000000000000000000000000000000004100000000kubelogin-0.2.13/pkg/internal/token/devicecodecredential_test.gopackage token

import (
	"context"
	"testing"

	"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
	"github.com/Azure/kubelogin/pkg/internal/testutils"
	"github.com/stretchr/testify/assert"
)

func TestDeviceCodeCredential_GetToken(t *testing.T) {
	rec, err := testutils.GetVCRHttpClient("fixtures/device_code_credential", testutils.TestTenantID)
	if err != nil {
		t.Fatalf("failed to create recorder: %v", err)
	}
	defer rec.Stop()

	opts := &Options{
		ClientID:   testutils.TestClientID,
		ServerID:   testutils.TestServerID,
		TenantID:   testutils.TestTenantID,
		httpClient: rec.GetDefaultClient(),
	}

	record := azidentity.AuthenticationRecord{}
	cred, err := newDeviceCodeCredential(opts, record)
	if err != nil {
		t.Fatalf("failed to create credential: %v", err)
	}

	token, err := cred.GetToken(context.Background(), policy.TokenRequestOptions{
		Scopes: []string{opts.ServerID + "/.default"},
	})
	assert.NoError(t, err)
	assert.Equal(t, testutils.TestToken, token.Token)
}
0707010000009A000081A4000000000000000000000001691F8CFD00000D81000000000000000000000000000000000000003C00000000kubelogin-0.2.13/pkg/internal/token/execCredentialPlugin.gopackage token

//go:generate sh -c "mockgen -destination mock_$GOPACKAGE/execCredentialPlugin.go github.com/Azure/kubelogin/pkg/internal/token ExecCredentialPlugin"

import (
	"context"
	"errors"
	"fmt"
	"os"
	"strings"

	"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
	klog "k8s.io/klog/v2"

	popcache "github.com/Azure/kubelogin/pkg/internal/pop/cache"
)

type ExecCredentialPlugin interface {
	Do(ctx context.Context) error
}

type execCredentialPlugin struct {
	o                    *Options
	cachedRecord         CachedRecordProvider
	execCredentialWriter ExecCredentialWriter
	newCredentialFunc    func(record azidentity.AuthenticationRecord, o *Options) (CredentialProvider, error)
}

var errAuthenticateNotSupported = errors.New("authenticate is not supported")

func New(o *Options) (ExecCredentialPlugin, error) {
	klog.V(10).Info(o.ToString())

	// Initialize PoP token cache in Options if enabled
	if o.IsPoPTokenEnabled && o.popTokenCache == nil {
		// Create PoP token cache using the official MSAL & MSAL extension libraries.
		popTokenCache, err := popcache.NewCache(o.AuthRecordCacheDir)
		if err != nil {
			// Fallback: Log warning and continue without PoP token caching when cache creation fails
			klog.V(2).Infof("PoP token caching disabled due to secure storage failure (likely container environment): %v", err)
			popTokenCache = nil
			// Continue execution without using cached PoP tokens
		}
		o.setPoPTokenCache(popTokenCache)
	}

	return &execCredentialPlugin{
		o:                    o,
		execCredentialWriter: &execCredentialWriter{},
		// cachedRecord stores authentication record (account info) to avoid re-prompting user
		cachedRecord: &defaultCachedRecordProvider{
			file: o.authRecordCacheFile,
		},
		newCredentialFunc: NewAzIdentityCredential,
	}, nil
}

func (p *execCredentialPlugin) Do(ctx context.Context) error {
	if p.o.ServerID == "" {
		return errors.New("server-id is required")
	}

	ctx, cancel := context.WithTimeout(ctx, p.o.Timeout)
	defer cancel()

	record, err := p.cachedRecord.Retrieve()
	if err != nil {
		klog.V(5).Infof("failed to retrieve cached record: %s", err)
	}

	cred, err := p.newCredentialFunc(record, p.o)
	if err != nil {
		return fmt.Errorf("failed to create azidentity credential: %w", err)
	}

	klog.V(5).Infof("using credential: %s", cred.Name())
	scopes := []string{GetScope(p.o.ServerID)}
	tokenRequestOptions := policy.TokenRequestOptions{
		TenantID: p.o.TenantID,
		Scopes:   scopes,
	}

	if cred.NeedAuthenticate() && record == (azidentity.AuthenticationRecord{}) {
		// No stored record; call Authenticate to acquire one.
		// This will prompt the user to authenticate interactively.
		klog.V(5).Info("no stored record; calling Authenticate")
		record, err = cred.Authenticate(ctx, &tokenRequestOptions)
		if err != nil {
			return fmt.Errorf("failed to authenticate: %w", err)
		}
		err = p.cachedRecord.Store(record)
		if err != nil {
			return fmt.Errorf("failed to store record: %w", err)
		}
	}
	klog.V(5).Infof("getting token with scopes: %v", scopes)
	token, err := cred.GetToken(ctx, tokenRequestOptions)
	if err != nil {
		return fmt.Errorf("failed to get token: %w", err)
	}

	return p.execCredentialWriter.Write(token, os.Stdout)
}

func GetScope(serverID string) string {
	scope := strings.TrimRight(serverID, "/")
	if !strings.HasSuffix(scope, defaultScope) {
		scope += defaultScope
	}
	return scope
}
0707010000009B000081A4000000000000000000000001691F8CFD00001078000000000000000000000000000000000000004100000000kubelogin-0.2.13/pkg/internal/token/execCredentialPlugin_test.gopackage token

import (
	"os"
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestKUBERNETES_EXEC_INFOIsEmpty(t *testing.T) {
	testData := []struct {
		name            string
		execInfoEnvTest string
		options         Options
	}{
		{
			name:            "KUBERNETES_EXEC_INFO is empty",
			execInfoEnvTest: "",
			options: Options{
				LoginMethod: DeviceCodeLogin,
				ClientID:    "clientID",
				ServerID:    "serverID",
				TenantID:    "tenantID",
			},
		},
	}

	for _, data := range testData {
		t.Run(data.name, func(t *testing.T) {
			os.Setenv("KUBERNETES_EXEC_INFO", data.execInfoEnvTest)
			defer os.Unsetenv("KUBERNETES_EXEC_INFO")
			ecp, err := New(&data.options)
			if ecp == nil || err != nil {
				t.Fatalf("expected: return execCredentialPlugin and nil error, actual: did not return execCredentialPlugin or did not return expected error")
			}
		})
	}
}

// TestNew_PoPCacheFallbackResilience validates the fallback mechanism for PoP token cache creation.
// This is critical for container compatibility where secure storage (Linux keyrings) may not be available.
// This test validates that New() never fails regardless of cache creation success/failure.
func TestNew_PoPCacheFallbackResilience(t *testing.T) {
	t.Run("PoP disabled - no cache attempted", func(t *testing.T) {
		options := &Options{
			LoginMethod:       DeviceCodeLogin,
			ClientID:          "clientID",
			ServerID:          "serverID",
			TenantID:          "tenantID",
			IsPoPTokenEnabled: false,
		}

		plugin, err := New(options)

		assert.NoError(t, err, "Should succeed when PoP is disabled")
		assert.NotNil(t, plugin, "Should return valid plugin")

		execPlugin, ok := plugin.(*execCredentialPlugin)
		assert.True(t, ok, "Should return execCredentialPlugin type")
		assert.Nil(t, execPlugin.o.GetPoPTokenCache(), "Should not create cache when PoP is disabled")
	})

	t.Run("PoP enabled with valid cache directory", func(t *testing.T) {
		// Use a temporary directory for cache
		tmpDir := t.TempDir()

		options := &Options{
			LoginMethod:        DeviceCodeLogin,
			ClientID:           "clientID",
			ServerID:           "serverID",
			TenantID:           "tenantID",
			IsPoPTokenEnabled:  true,
			AuthRecordCacheDir: tmpDir,
		}

		plugin, err := New(options)

		// New() must never fail, regardless of cache creation success/failure
		assert.NoError(t, err, "Must succeed regardless of cache creation outcome")
		assert.NotNil(t, plugin, "Must return valid plugin")

		execPlugin, ok := plugin.(*execCredentialPlugin)
		assert.True(t, ok, "Should return execCredentialPlugin type")

		// Log the actual outcome for debugging
		if execPlugin.o.GetPoPTokenCache() != nil {
			t.Log("Cache creation succeeded - secure storage available")
		} else {
			t.Log("Cache creation failed (gracefully) - likely container environment or keyring restrictions")
		}
	})

	t.Run("Validates fallback mechanism behavior", func(t *testing.T) {
		// This test demonstrates that the behavior is consistent regardless of environment
		testCases := []struct {
			name     string
			cacheDir string
		}{
			{"temp directory", t.TempDir()},
			{"invalid directory", "/proc/non-existent-test-dir"},
			{"root directory (typically restricted)", "/root/cache-test"},
		}

		for _, tc := range testCases {
			t.Run(tc.name, func(t *testing.T) {
				options := &Options{
					LoginMethod:        DeviceCodeLogin,
					ClientID:           "clientID",
					ServerID:           "serverID",
					TenantID:           "tenantID",
					IsPoPTokenEnabled:  true,
					AuthRecordCacheDir: tc.cacheDir,
				}

				plugin, err := New(options)

				// The universal requirement: New() must NEVER fail
				assert.NoError(t, err, "New() must succeed in all environments for container compatibility")
				assert.NotNil(t, plugin, "Must return valid plugin")

				execPlugin, ok := plugin.(*execCredentialPlugin)
				assert.True(t, ok, "Should return execCredentialPlugin type")

				// Document the behavior for each scenario
				cacheState := "succeeded"
				if execPlugin.o.GetPoPTokenCache() == nil {
					cacheState = "failed (graceful fallback)"
				}
				t.Logf("Directory '%s': cache creation %s", tc.cacheDir, cacheState)
			})
		}
	})
}
0707010000009C000081A4000000000000000000000001691F8CFD000009B7000000000000000000000000000000000000003C00000000kubelogin-0.2.13/pkg/internal/token/execCredentialWriter.gopackage token

//go:generate sh -c "mockgen -destination mock_$GOPACKAGE/execCredentialWriter.go github.com/Azure/kubelogin/pkg/internal/token ExecCredentialWriter"

import (
	"encoding/json"
	"fmt"
	"io"
	"os"

	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/pkg/apis/clientauthentication"
	v1 "k8s.io/client-go/pkg/apis/clientauthentication/v1"
	"k8s.io/client-go/pkg/apis/clientauthentication/v1beta1"
)

const (
	apiV1       string = "client.authentication.k8s.io/v1"
	apiV1beta1  string = "client.authentication.k8s.io/v1beta1"
	execInfoEnv string = "KUBERNETES_EXEC_INFO"
)

type ExecCredentialWriter interface {
	Write(token azcore.AccessToken, writer io.Writer) error
}

type execCredentialWriter struct{}

// Write writes the ExecCredential to standard output for kubectl.
func (*execCredentialWriter) Write(accessToken azcore.AccessToken, writer io.Writer) error {
	apiVersionFromEnv, err := getAPIVersionFromExecInfoEnv()
	if err != nil {
		return err
	}
	// Support both apiVersions of client.authentication.k8s.io/v1beta1 and client.authentication.k8s.io/v1
	var ec interface{}
	t := metav1.NewTime(accessToken.ExpiresOn)
	switch apiVersionFromEnv {
	case apiV1beta1:
		ec = &v1beta1.ExecCredential{
			TypeMeta: metav1.TypeMeta{
				APIVersion: apiV1beta1,
				Kind:       "ExecCredential",
			},
			Status: &v1beta1.ExecCredentialStatus{
				Token:               accessToken.Token,
				ExpirationTimestamp: &t,
			},
		}
	case apiV1:
		ec = &v1.ExecCredential{
			TypeMeta: metav1.TypeMeta{
				APIVersion: apiV1,
				Kind:       "ExecCredential",
			},
			Status: &v1.ExecCredentialStatus{
				Token:               accessToken.Token,
				ExpirationTimestamp: &t,
			},
		}
	}

	e := json.NewEncoder(writer)
	if err := e.Encode(ec); err != nil {
		return fmt.Errorf("could not write the ExecCredential: %w", err)
	}
	return nil
}

func getAPIVersionFromExecInfoEnv() (string, error) {
	env := os.Getenv(execInfoEnv)
	if env == "" {
		return apiV1beta1, nil
	}
	var execCredential clientauthentication.ExecCredential
	if err := json.Unmarshal([]byte(env), &execCredential); err != nil {
		return "", fmt.Errorf("cannot unmarshal %q to ExecCredential: %w", env, err)
	}
	switch execCredential.APIVersion {
	case "":
		return apiV1beta1, nil
	case apiV1, apiV1beta1:
		return execCredential.APIVersion, nil
	default:
		return "", fmt.Errorf("api version: %s is not supported", execCredential.APIVersion)
	}
}
0707010000009D000081A4000000000000000000000001691F8CFD000008BE000000000000000000000000000000000000004100000000kubelogin-0.2.13/pkg/internal/token/execCredentialWriter_test.gopackage token

import (
	"bytes"
	"encoding/json"
	"os"
	"testing"

	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
	"k8s.io/client-go/pkg/apis/clientauthentication"
)

func TestExecCredentialWriterAPIVersion(t *testing.T) {
	testData := []struct {
		name               string
		execInfoEnvTest    string
		expectedAPIVersion string
	}{
		{
			name:               "KUBERNETES_EXEC_INFO is empty",
			execInfoEnvTest:    "",
			expectedAPIVersion: "client.authentication.k8s.io/v1beta1",
		},
		{
			name:               "KUBERNETES_EXEC_INFO is present and apiVersion is absent",
			execInfoEnvTest:    `{"kind":"ExecCredential","spec":{"interactive":true},"apiVersion":""}`,
			expectedAPIVersion: "client.authentication.k8s.io/v1beta1",
		},
		{
			name:               "KUBERNETES_EXEC_INFO is present and apiVersion is neither v1 or v1beta1",
			execInfoEnvTest:    `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1alpha1","spec":{"interactive":true}}`,
			expectedAPIVersion: "",
		},
		{
			name:               "KUBERNETES_EXEC_INFO is present and apiVersion is v1beta1",
			execInfoEnvTest:    `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{"interactive":true}}`,
			expectedAPIVersion: "client.authentication.k8s.io/v1beta1",
		},
		{
			name:               "KUBERNETES_EXEC_INFO is present and apiVersion is v1",
			execInfoEnvTest:    `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1","spec":{"interactive":true}}`,
			expectedAPIVersion: "client.authentication.k8s.io/v1",
		},
	}

	for _, data := range testData {
		t.Run(data.name, func(t *testing.T) {
			os.Setenv("KUBERNETES_EXEC_INFO", data.execInfoEnvTest)
			defer os.Unsetenv("KUBERNETES_EXEC_INFO")
			ecw := execCredentialWriter{}
			stringBufferTest := new(bytes.Buffer)
			azToken := azcore.AccessToken{
				Token: "access-token",
			}
			ecw.Write(azToken, stringBufferTest)
			var execCredential clientauthentication.ExecCredential
			json.Unmarshal(stringBufferTest.Bytes(), &execCredential)
			if execCredential.TypeMeta.APIVersion != data.expectedAPIVersion {
				t.Fatalf("expected: %s, actual: %s", data.expectedAPIVersion, execCredential.TypeMeta.APIVersion)
			}
		})
	}
}
0707010000009E000041ED000000000000000000000002691F8CFD00000000000000000000000000000000000000000000002D00000000kubelogin-0.2.13/pkg/internal/token/fixtures0707010000009F000081A4000000000000000000000001691F8CFD00000ACA000000000000000000000000000000000000003600000000kubelogin-0.2.13/pkg/internal/token/fixtures/cert.pem-----BEGIN CERTIFICATE-----
MIIC3DCCAcSgAwIBAgIBATANBgkqhkiG9w0BAQsFADAWMRQwEgYDVQQKEwtFeGFt
cGxlIE9yZzAeFw0yNTAyMjYxNzM3MTRaFw0yNjAyMjYxNzM3MTRaMBYxFDASBgNV
BAoTC0V4YW1wbGUgT3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
41kp4aOyHwZkDd8o9Q/NRGGFSUXIdlScgLe9ZNtQWwG1r8enUZ0DDHMsTuJBuNR5
VS7E/eIXsbz7nHq7OYwdBHc9INVZ8t/WXRwVbzNe3PmGETNn6Ip+QDeOQXm+R7c6
xpH7d/ATRm5H3HhzEjpX4KbLby1y0ue4RdkWSaWbv7CIPt9g/JzrwecQ9h5q6i3t
QNoUi9ZImlTBbXhJh3/W8zi3dWCYr2JwT7883Iw5bFpkfFg+yXFq7a1tVj3gterL
rgdjgyaRvUPZSgk6lCmBYBuAdhks2Mv4T8T/b2b72+H7vKfjuR/IziPTxP0YOlSZ
DWghT3VrRB1EfvjO1wKA8QIDAQABozUwMzAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0l
BAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAQEA
SfXdt6O6HmlTWypTdxNisALn4lMO7RhXn3ixI08ufI6RwVqhSvWhGwKL+uR+HGww
w+uiaWgUxXmp8ocQmsoRns+Wx1pvjD2+/27si39rJuLWzAE/DViN1KYqsB7b0Su9
JmpZBruIT6lWYKfG4KmJBo15++ttAeyfnL/5eyl1Uu9s+JsPsgr6vWAH7IZV0GRC
rVstolS2nBcQOsO+nHABJFavjYQP3Wo5Ei+50im7BdlaVmA+BpV5WrGkEGTyI0Qv
Tld5IEcrZeuVb5jBM2fpbLXA8a3aGzU8eetIBbw7p8jTqeE1Y58ZW00KcJVtR3/i
xkQDJ9mGcA6z1D0/2SJlFw==
-----END CERTIFICATE-----
-----BEGIN PRIVATE KEY-----
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDjWSnho7IfBmQN
3yj1D81EYYVJRch2VJyAt71k21BbAbWvx6dRnQMMcyxO4kG41HlVLsT94hexvPuc
ers5jB0Edz0g1Vny39ZdHBVvM17c+YYRM2foin5AN45Beb5HtzrGkft38BNGbkfc
eHMSOlfgpstvLXLS57hF2RZJpZu/sIg+32D8nOvB5xD2HmrqLe1A2hSL1kiaVMFt
eEmHf9bzOLd1YJivYnBPvzzcjDlsWmR8WD7JcWrtrW1WPeC16suuB2ODJpG9Q9lK
CTqUKYFgG4B2GSzYy/hPxP9vZvvb4fu8p+O5H8jOI9PE/Rg6VJkNaCFPdWtEHUR+
+M7XAoDxAgMBAAECggEBAJOLNib9uYdw+lYWUdpY2vpZ38phw2soFjljBUMDIe8t
+N+PTwMkRuo5hLPdGpH9MpEZvTaXGJF8+D3hWMhMKjjPuZgpXirFIbVjmHWGfTYj
qeJX3kOIPc8nPuc3P0oDm91quCxqXdgDkhd/BA78VjOxi60nbbF6OJj7/f7lv2Jy
0YR1jq6xEcRChIvuQN/IONtwLQDGJ/okQIsbcptwlvS62QkbxDFmB4Gb67tqlw+T
HZS0McMw9357y0yQOh/xRCsrd1q3WoDEnxFi+1mnHuGmAgxBkBSRHPoq++mASw+v
Lp5zJS0sJlMsY6G/RjbS0hWPuqk1MGzcJ7EPMOg9nBECgYEA9m1EiU47bh9Mayaa
VZrrjfkN5a6Igknlf/N238ORsmraF4mAOEgJ53+Y0wMtj5ODsBUrv0/ykCxkPWKz
WfmZMgwZGsMdXaEt0kGCFPXNYhuoX01Nvek97wZORUCruCu8zuHI1D6BXLpekRy2
N7u0EbyUBzjrw1t8fJT0cKJ0Lf8CgYEA7C4olUEHd8bWNPOjHMLLGIwYTwY2xw8K
9o5TYa65YLb4SoYstrWIAb+70iyieHpsc015W5S7ran8D6nMmUXqrLK2xhnGYqXa
qDXBHhQ4lS/cAaFYccOPkV5o/c8f3+SqX7GSBQEG8GcnR+rIBVF6dX9IxdcrrW8Q
nE4hupaaMQ8CgYEAoBB/iRRYxAHueFcdvmcfhBt27G6+2qo3OTIiT9xPYe3H9avp
riUU0druoHma+Xye+Bv4S48Zho5fGgRnbRUUbg6vq9N62ptcEQtd/FFg8831QtoK
mkXLZdvZsV/9amZ2gIbmFP74tguUa3uT3IW+xBqSqFJPyZg+Nwnl8NeE0W8CgYBv
mRP2Cocz0pFu7dyZ/3UmATSPSplhj7sXa4L8uinACFTkKehA4SAF64odMpgGo5pR
FT+NxYa09Yg0AoC5v+62ca+phY5XGJVsPBInW1itLbMgVhPLlaNmBh1IKLGgApV6
qBYeJTEZMTS/Z7HhyXE6yc0iJpzt03ge01BesUDkgwKBgQDLa02L362TiegTYNN9
2CrJR9Z+OSg+Nmc4lCxGLx3KSMDAmJDVOpE+VB7znRo1K8U+qglfShtEB8WWaWMB
GsVvxMny+90qyUDAo7gZi86x908oYUy7BKv36ZjV9f2TFEsHFAWnuWyPg7arYXn2
Omaw1wxaRHrnjgZ1GB9z00x3tQ==
-----END PRIVATE KEY-----
070701000000A0000081A4000000000000000000000001691F8CFD00002E87000000000000000000000000000000000000004900000000kubelogin-0.2.13/pkg/internal/token/fixtures/client_cert_credential.yaml---
version: 2
interactions:
    - id: 0
      request:
        proto: HTTP/1.1
        proto_major: 1
        proto_minor: 1
        content_length: 0
        transfer_encoding: []
        trailer: {}
        host: login.microsoftonline.com
        remote_addr: ""
        request_uri: ""
        body: ""
        form: {}
        headers:
            Accept-Encoding:
                - gzip
            Client-Request-Id:
                - 4834edb5-48f7-4a5d-9813-6cdbfa084f0c
            Return-Client-Request-Id:
                - "false"
            User-Agent:
                - azsdk-go-azidentity/v1.8.0 (go1.22.9; linux)
            X-Client-Cpu:
                - amd64
            X-Client-Os:
                - linux
            X-Client-Sku:
                - MSAL.Go
            X-Client-Ver:
                - 1.2.0
        url: https://login.microsoftonline.com/common/discovery/instance?api-version=1.1&authorization_endpoint=https%3A%2F%2Flogin.microsoftonline.com%2F00000000-0000-0000-0000-000000000000%2Foauth2%2Fv2.0%2Fauthorize
        method: GET
      response:
        proto: HTTP/2.0
        proto_major: 2
        proto_minor: 0
        transfer_encoding: []
        trailer: {}
        content_length: 980
        uncompressed: false
        body: '{"tenant_discovery_endpoint":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/v2.0/.well-known/openid-configuration","api-version":"1.1","metadata":[{"preferred_network":"login.microsoftonline.com","preferred_cache":"login.windows.net","aliases":["login.microsoftonline.com","login.windows.net","login.microsoft.com","sts.windows.net"]},{"preferred_network":"login.partner.microsoftonline.cn","preferred_cache":"login.partner.microsoftonline.cn","aliases":["login.partner.microsoftonline.cn","login.chinacloudapi.cn"]},{"preferred_network":"login.microsoftonline.de","preferred_cache":"login.microsoftonline.de","aliases":["login.microsoftonline.de"]},{"preferred_network":"login.microsoftonline.us","preferred_cache":"login.microsoftonline.us","aliases":["login.microsoftonline.us","login.usgovcloudapi.net"]},{"preferred_network":"login-us.microsoftonline.com","preferred_cache":"login-us.microsoftonline.com","aliases":["login-us.microsoftonline.com"]}]}'
        headers:
            Access-Control-Allow-Methods:
                - GET, OPTIONS
            Access-Control-Allow-Origin:
                - '*'
            Cache-Control:
                - max-age=86400, private
            Client-Request-Id:
                - 4834edb5-48f7-4a5d-9813-6cdbfa084f0c
            Content-Length:
                - "980"
            Content-Security-Policy-Report-Only:
                - object-src 'none'; base-uri 'self'; script-src 'self' 'nonce-THS4Pc6YgxpG7SHDuZ5OPw' 'unsafe-inline' 'unsafe-eval' https://*.msauth.net https://*.msftauth.net https://*.msftauthimages.net https://*.msauthimages.net https://*.msidentity.com https://*.microsoftonline-p.com https://*.microsoftazuread-sso.com https://*.azureedge.net https://*.outlook.com https://*.office.com https://*.office365.com https://*.microsoft.com https://*.bing.com 'report-sample'; report-uri https://csp.microsoft.com/report/ESTS-UX-All
            Content-Type:
                - application/json; charset=utf-8
            Date:
                - Wed, 19 Feb 2025 00:18:08 GMT
            P3p:
                - CP="DSP CUR OTPi IND OTRi ONL FIN"
            Strict-Transport-Security:
                - max-age=31536000; includeSubDomains
            X-Content-Type-Options:
                - nosniff
            X-Ms-Ests-Server:
                - 2.1.20106.4 - EUS ProdSlices
            X-Ms-Srs:
                - 1.P
            X-Xss-Protection:
                - "0"
        status: 200 OK
        code: 200
        duration: 310.985686ms
    - id: 1
      request:
        proto: HTTP/1.1
        proto_major: 1
        proto_minor: 1
        content_length: 0
        transfer_encoding: []
        trailer: {}
        host: login.microsoftonline.com
        remote_addr: ""
        request_uri: ""
        body: ""
        form: {}
        headers:
            Accept-Encoding:
                - gzip
            Client-Request-Id:
                - eb28ecfd-0c6d-4247-854e-70c89ee1d29b
            Return-Client-Request-Id:
                - "false"
            User-Agent:
                - azsdk-go-azidentity/v1.8.0 (go1.22.9; linux)
            X-Client-Cpu:
                - amd64
            X-Client-Os:
                - linux
            X-Client-Sku:
                - MSAL.Go
            X-Client-Ver:
                - 1.2.0
        url: https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/v2.0/.well-known/openid-configuration
        method: GET
      response:
        proto: HTTP/2.0
        proto_major: 2
        proto_minor: 0
        transfer_encoding: []
        trailer: {}
        content_length: 1753
        uncompressed: false
        body: '{"token_endpoint":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/v2.0/token","token_endpoint_auth_methods_supported":["client_secret_post","private_key_jwt","client_secret_basic"],"jwks_uri":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/discovery/v2.0/keys","response_modes_supported":["query","fragment","form_post"],"subject_types_supported":["pairwise"],"id_token_signing_alg_values_supported":["RS256"],"response_types_supported":["code","id_token","code id_token","id_token token"],"scopes_supported":["openid","profile","email","offline_access"],"issuer":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/v2.0","request_uri_parameter_supported":false,"userinfo_endpoint":"https://graph.microsoft.com/oidc/userinfo","authorization_endpoint":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/v2.0/authorize","device_authorization_endpoint":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/v2.0/devicecode","http_logout_supported":true,"frontchannel_logout_supported":true,"end_session_endpoint":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/v2.0/logout","claims_supported":["sub","iss","cloud_instance_name","cloud_instance_host_name","cloud_graph_host_name","msgraph_host","aud","exp","iat","auth_time","acr","nonce","preferred_username","name","tid","ver","at_hash","c_hash","email"],"kerberos_endpoint":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/kerberos","tenant_region_scope":"NA","cloud_instance_name":"microsoftonline.com","cloud_graph_host_name":"graph.windows.net","msgraph_host":"graph.microsoft.com","rbac_url":"https://pas.windows.net"}'
        headers:
            Access-Control-Allow-Methods:
                - GET, OPTIONS
            Access-Control-Allow-Origin:
                - '*'
            Cache-Control:
                - max-age=86400, private
            Client-Request-Id:
                - eb28ecfd-0c6d-4247-854e-70c89ee1d29b
            Content-Length:
                - "1753"
            Content-Security-Policy-Report-Only:
                - object-src 'none'; base-uri 'self'; script-src 'self' 'nonce-cHJcUTLuBn9G7NU_FsKtvA' 'unsafe-inline' 'unsafe-eval' https://*.msauth.net https://*.msftauth.net https://*.msftauthimages.net https://*.msauthimages.net https://*.msidentity.com https://*.microsoftonline-p.com https://*.microsoftazuread-sso.com https://*.azureedge.net https://*.outlook.com https://*.office.com https://*.office365.com https://*.microsoft.com https://*.bing.com 'report-sample'; report-uri https://csp.microsoft.com/report/ESTS-UX-All
            Content-Type:
                - application/json; charset=utf-8
            Date:
                - Wed, 19 Feb 2025 00:18:09 GMT
            P3p:
                - CP="DSP CUR OTPi IND OTRi ONL FIN"
            Strict-Transport-Security:
                - max-age=31536000; includeSubDomains
            X-Content-Type-Options:
                - nosniff
            X-Ms-Ests-Server:
                - 2.1.20106.4 - EUS ProdSlices
            X-Ms-Srs:
                - 1.P
            X-Xss-Protection:
                - "0"
        status: 200 OK
        code: 200
        duration: 113.101786ms
    - id: 2
      request:
        proto: HTTP/1.1
        proto_major: 1
        proto_minor: 1
        content_length: 2541
        transfer_encoding: []
        trailer: {}
        host: login.microsoftonline.com
        remote_addr: ""
        request_uri: ""
        body: client_assertion=[REDACTED]&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer&client_id=[REDACTED]&client_info=1&grant_type=client_credentials&scope=6dae42f8-4368-4678-94ff-3960e28e3630%2F.default+openid+offline_access+profile
        form:
            client_assertion:
                - '[REDACTED]'
            client_assertion_type:
                - urn:ietf:params:oauth:client-assertion-type:jwt-bearer
            client_id:
                - '[REDACTED]'
            client_info:
                - "1"
            grant_type:
                - client_credentials
            scope:
                - 6dae42f8-4368-4678-94ff-3960e28e3630/.default openid offline_access profile
        headers:
            Accept-Encoding:
                - gzip
            Client-Request-Id:
                - 45ef19b5-74e4-43ef-8937-1455a7034fb6
            Content-Length:
                - "2541"
            Content-Type:
                - application/x-www-form-urlencoded; charset=utf-8
            Return-Client-Request-Id:
                - "false"
            User-Agent:
                - azsdk-go-azidentity/v1.8.0 (go1.22.9; linux)
            X-Client-Cpu:
                - amd64
            X-Client-Os:
                - linux
            X-Client-Sku:
                - MSAL.Go
            X-Client-Ver:
                - 1.2.0
        url: https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/v2.0/token
        method: POST
      response:
        proto: HTTP/2.0
        proto_major: 2
        proto_minor: 0
        transfer_encoding: []
        trailer: {}
        content_length: 1405
        uncompressed: false
        body: '{"access_token":"TEST_ACCESS_TOKEN","expires_in":3599,"ext_expires_in":3599,"token_type":"Bearer"}'
        headers:
            Cache-Control:
                - no-store, no-cache
            Client-Request-Id:
                - 45ef19b5-74e4-43ef-8937-1455a7034fb6
            Content-Length:
                - "1405"
            Content-Security-Policy-Report-Only:
                - object-src 'none'; base-uri 'self'; script-src 'self' 'nonce-RvSJlAthIxU9BLHNwwqUXQ' 'unsafe-inline' 'unsafe-eval' https://*.msauth.net https://*.msftauth.net https://*.msftauthimages.net https://*.msauthimages.net https://*.msidentity.com https://*.microsoftonline-p.com https://*.microsoftazuread-sso.com https://*.azureedge.net https://*.outlook.com https://*.office.com https://*.office365.com https://*.microsoft.com https://*.bing.com 'report-sample'; report-uri https://csp.microsoft.com/report/ESTS-UX-All
            Content-Type:
                - application/json; charset=utf-8
            Date:
                - Wed, 19 Feb 2025 00:18:09 GMT
            Expires:
                - "-1"
            P3p:
                - CP="DSP CUR OTPi IND OTRi ONL FIN"
            Pragma:
                - no-cache
            Strict-Transport-Security:
                - max-age=31536000; includeSubDomains
            X-Content-Type-Options:
                - nosniff
            X-Ms-Clitelem:
                - 1,0,0,,
            X-Ms-Ests-Server:
                - 2.1.20106.4 - EUS ProdSlices
            X-Ms-Srs:
                - 1.P
            X-Xss-Protection:
                - "0"
        status: 200 OK
        code: 200
        duration: 162.20102ms
070701000000A1000081A4000000000000000000000001691F8CFD00002D82000000000000000000000000000000000000004B00000000kubelogin-0.2.13/pkg/internal/token/fixtures/client_secret_credential.yaml---
version: 2
interactions:
    - id: 0
      request:
        proto: HTTP/1.1
        proto_major: 1
        proto_minor: 1
        content_length: 0
        transfer_encoding: []
        trailer: {}
        host: login.microsoftonline.com
        remote_addr: ""
        request_uri: ""
        body: ""
        form: {}
        headers:
            Accept-Encoding:
                - gzip
            Client-Request-Id:
                - 1b92af2f-ae8a-450f-af74-a4ef00336305
            Return-Client-Request-Id:
                - "false"
            User-Agent:
                - azsdk-go-azidentity/v1.8.0 (go1.22.9; linux)
            X-Client-Cpu:
                - amd64
            X-Client-Os:
                - linux
            X-Client-Sku:
                - MSAL.Go
            X-Client-Ver:
                - 1.2.0
        url: https://login.microsoftonline.com/common/discovery/instance?api-version=1.1&authorization_endpoint=https%3A%2F%2Flogin.microsoftonline.com%2F00000000-0000-0000-0000-000000000000%2Foauth2%2Fv2.0%2Fauthorize
        method: GET
      response:
        proto: HTTP/2.0
        proto_major: 2
        proto_minor: 0
        transfer_encoding: []
        trailer: {}
        content_length: 980
        uncompressed: false
        body: '{"tenant_discovery_endpoint":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/v2.0/.well-known/openid-configuration","api-version":"1.1","metadata":[{"preferred_network":"login.microsoftonline.com","preferred_cache":"login.windows.net","aliases":["login.microsoftonline.com","login.windows.net","login.microsoft.com","sts.windows.net"]},{"preferred_network":"login.partner.microsoftonline.cn","preferred_cache":"login.partner.microsoftonline.cn","aliases":["login.partner.microsoftonline.cn","login.chinacloudapi.cn"]},{"preferred_network":"login.microsoftonline.de","preferred_cache":"login.microsoftonline.de","aliases":["login.microsoftonline.de"]},{"preferred_network":"login.microsoftonline.us","preferred_cache":"login.microsoftonline.us","aliases":["login.microsoftonline.us","login.usgovcloudapi.net"]},{"preferred_network":"login-us.microsoftonline.com","preferred_cache":"login-us.microsoftonline.com","aliases":["login-us.microsoftonline.com"]}]}'
        headers:
            Access-Control-Allow-Methods:
                - GET, OPTIONS
            Access-Control-Allow-Origin:
                - '*'
            Cache-Control:
                - max-age=86400, private
            Client-Request-Id:
                - 1b92af2f-ae8a-450f-af74-a4ef00336305
            Content-Length:
                - "980"
            Content-Security-Policy-Report-Only:
                - object-src 'none'; base-uri 'self'; script-src 'self' 'nonce-W0pn7Aylpcwv_QbgA7aadw' 'unsafe-inline' 'unsafe-eval' https://*.msauth.net https://*.msftauth.net https://*.msftauthimages.net https://*.msauthimages.net https://*.msidentity.com https://*.microsoftonline-p.com https://*.microsoftazuread-sso.com https://*.azureedge.net https://*.outlook.com https://*.office.com https://*.office365.com https://*.microsoft.com https://*.bing.com 'report-sample'; report-uri https://csp.microsoft.com/report/ESTS-UX-All
            Content-Type:
                - application/json; charset=utf-8
            Date:
                - Wed, 19 Feb 2025 03:53:28 GMT
            P3p:
                - CP="DSP CUR OTPi IND OTRi ONL FIN"
            Strict-Transport-Security:
                - max-age=31536000; includeSubDomains
            X-Content-Type-Options:
                - nosniff
            X-Ms-Ests-Server:
                - 2.1.20106.4 - NCUS ProdSlices
            X-Ms-Srs:
                - 1.P
            X-Xss-Protection:
                - "0"
        status: 200 OK
        code: 200
        duration: 341.493421ms
    - id: 1
      request:
        proto: HTTP/1.1
        proto_major: 1
        proto_minor: 1
        content_length: 0
        transfer_encoding: []
        trailer: {}
        host: login.microsoftonline.com
        remote_addr: ""
        request_uri: ""
        body: ""
        form: {}
        headers:
            Accept-Encoding:
                - gzip
            Client-Request-Id:
                - e3d5513c-38f5-466f-bb1b-ef08971e9535
            Return-Client-Request-Id:
                - "false"
            User-Agent:
                - azsdk-go-azidentity/v1.8.0 (go1.22.9; linux)
            X-Client-Cpu:
                - amd64
            X-Client-Os:
                - linux
            X-Client-Sku:
                - MSAL.Go
            X-Client-Ver:
                - 1.2.0
        url: https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/v2.0/.well-known/openid-configuration
        method: GET
      response:
        proto: HTTP/2.0
        proto_major: 2
        proto_minor: 0
        transfer_encoding: []
        trailer: {}
        content_length: 1753
        uncompressed: false
        body: '{"token_endpoint":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/v2.0/token","token_endpoint_auth_methods_supported":["client_secret_post","private_key_jwt","client_secret_basic"],"jwks_uri":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/discovery/v2.0/keys","response_modes_supported":["query","fragment","form_post"],"subject_types_supported":["pairwise"],"id_token_signing_alg_values_supported":["RS256"],"response_types_supported":["code","id_token","code id_token","id_token token"],"scopes_supported":["openid","profile","email","offline_access"],"issuer":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/v2.0","request_uri_parameter_supported":false,"userinfo_endpoint":"https://graph.microsoft.com/oidc/userinfo","authorization_endpoint":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/v2.0/authorize","device_authorization_endpoint":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/v2.0/devicecode","http_logout_supported":true,"frontchannel_logout_supported":true,"end_session_endpoint":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/v2.0/logout","claims_supported":["sub","iss","cloud_instance_name","cloud_instance_host_name","cloud_graph_host_name","msgraph_host","aud","exp","iat","auth_time","acr","nonce","preferred_username","name","tid","ver","at_hash","c_hash","email"],"kerberos_endpoint":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/kerberos","tenant_region_scope":"NA","cloud_instance_name":"microsoftonline.com","cloud_graph_host_name":"graph.windows.net","msgraph_host":"graph.microsoft.com","rbac_url":"https://pas.windows.net"}'
        headers:
            Access-Control-Allow-Methods:
                - GET, OPTIONS
            Access-Control-Allow-Origin:
                - '*'
            Cache-Control:
                - max-age=86400, private
            Client-Request-Id:
                - e3d5513c-38f5-466f-bb1b-ef08971e9535
            Content-Length:
                - "1753"
            Content-Security-Policy-Report-Only:
                - object-src 'none'; base-uri 'self'; script-src 'self' 'nonce-uEUQF1eVnpgyCiCa8oyLCQ' 'unsafe-inline' 'unsafe-eval' https://*.msauth.net https://*.msftauth.net https://*.msftauthimages.net https://*.msauthimages.net https://*.msidentity.com https://*.microsoftonline-p.com https://*.microsoftazuread-sso.com https://*.azureedge.net https://*.outlook.com https://*.office.com https://*.office365.com https://*.microsoft.com https://*.bing.com 'report-sample'; report-uri https://csp.microsoft.com/report/ESTS-UX-All
            Content-Type:
                - application/json; charset=utf-8
            Date:
                - Wed, 19 Feb 2025 03:53:28 GMT
            P3p:
                - CP="DSP CUR OTPi IND OTRi ONL FIN"
            Strict-Transport-Security:
                - max-age=31536000; includeSubDomains
            X-Content-Type-Options:
                - nosniff
            X-Ms-Ests-Server:
                - 2.1.20106.4 - SCUS ProdSlices
            X-Ms-Srs:
                - 1.P
            X-Xss-Protection:
                - "0"
        status: 200 OK
        code: 200
        duration: 78.161229ms
    - id: 2
      request:
        proto: HTTP/1.1
        proto_major: 1
        proto_minor: 1
        content_length: 215
        transfer_encoding: []
        trailer: {}
        host: login.microsoftonline.com
        remote_addr: ""
        request_uri: ""
        body: client_id=[REDACTED]&client_secret=[REDACTED]&grant_type=client_credentials&scope=6dae42f8-4368-4678-94ff-3960e28e3630%2F.default+openid+offline_access+profile
        form:
            client_id:
                - '[REDACTED]'
            client_secret:
                - '[REDACTED]'
            grant_type:
                - client_credentials
            scope:
                - 6dae42f8-4368-4678-94ff-3960e28e3630/.default openid offline_access profile
        headers:
            Accept-Encoding:
                - gzip
            Client-Request-Id:
                - 39e4c37b-acff-469e-bf35-2fc35ee16201
            Content-Length:
                - "215"
            Content-Type:
                - application/x-www-form-urlencoded; charset=utf-8
            Return-Client-Request-Id:
                - "false"
            User-Agent:
                - azsdk-go-azidentity/v1.8.0 (go1.22.9; linux)
            X-Client-Cpu:
                - amd64
            X-Client-Os:
                - linux
            X-Client-Sku:
                - MSAL.Go
            X-Client-Ver:
                - 1.2.0
        url: https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/v2.0/token
        method: POST
      response:
        proto: HTTP/2.0
        proto_major: 2
        proto_minor: 0
        transfer_encoding: []
        trailer: {}
        content_length: 1405
        uncompressed: false
        body: '{"access_token":"TEST_ACCESS_TOKEN","expires_in":3599,"ext_expires_in":3599,"token_type":"Bearer"}'
        headers:
            Cache-Control:
                - no-store, no-cache
            Client-Request-Id:
                - 39e4c37b-acff-469e-bf35-2fc35ee16201
            Content-Length:
                - "1405"
            Content-Security-Policy-Report-Only:
                - object-src 'none'; base-uri 'self'; script-src 'self' 'nonce-JIv-XV7UBaBFzdntLLrByw' 'unsafe-inline' 'unsafe-eval' https://*.msauth.net https://*.msftauth.net https://*.msftauthimages.net https://*.msauthimages.net https://*.msidentity.com https://*.microsoftonline-p.com https://*.microsoftazuread-sso.com https://*.azureedge.net https://*.outlook.com https://*.office.com https://*.office365.com https://*.microsoft.com https://*.bing.com 'report-sample'; report-uri https://csp.microsoft.com/report/ESTS-UX-All
            Content-Type:
                - application/json; charset=utf-8
            Date:
                - Wed, 19 Feb 2025 03:53:28 GMT
            Expires:
                - "-1"
            P3p:
                - CP="DSP CUR OTPi IND OTRi ONL FIN"
            Pragma:
                - no-cache
            Strict-Transport-Security:
                - max-age=31536000; includeSubDomains
            X-Content-Type-Options:
                - nosniff
            X-Ms-Clitelem:
                - 1,0,0,,
            X-Ms-Ests-Server:
                - 2.1.20106.4 - WUS3 ProdSlices
            X-Ms-Srs:
                - 1.P
            X-Xss-Protection:
                - "0"
        status: 200 OK
        code: 200
        duration: 156.727923ms
070701000000A2000081A4000000000000000000000001691F8CFD0000415E000000000000000000000000000000000000004900000000kubelogin-0.2.13/pkg/internal/token/fixtures/device_code_credential.yaml---
version: 2
interactions:
    - id: 0
      request:
        proto: HTTP/1.1
        proto_major: 1
        proto_minor: 1
        content_length: 0
        transfer_encoding: []
        trailer: {}
        host: login.microsoftonline.com
        remote_addr: ""
        request_uri: ""
        body: ""
        form: {}
        headers:
            Accept-Encoding:
                - gzip
            Client-Request-Id:
                - 51efc8c0-7007-4820-bfca-9053f7d39059
            Return-Client-Request-Id:
                - "false"
            User-Agent:
                - azsdk-go-azidentity/v1.8.0 (go1.22.9; linux)
            X-Client-Cpu:
                - amd64
            X-Client-Os:
                - linux
            X-Client-Sku:
                - MSAL.Go
            X-Client-Ver:
                - 1.2.0
        url: https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/v2.0/.well-known/openid-configuration
        method: GET
      response:
        proto: HTTP/2.0
        proto_major: 2
        proto_minor: 0
        transfer_encoding: []
        trailer: {}
        content_length: 1753
        uncompressed: false
        body: '{"token_endpoint":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/v2.0/token","token_endpoint_auth_methods_supported":["client_secret_post","private_key_jwt","client_secret_basic"],"jwks_uri":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/discovery/v2.0/keys","response_modes_supported":["query","fragment","form_post"],"subject_types_supported":["pairwise"],"id_token_signing_alg_values_supported":["RS256"],"response_types_supported":["code","id_token","code id_token","id_token token"],"scopes_supported":["openid","profile","email","offline_access"],"issuer":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/v2.0","request_uri_parameter_supported":false,"userinfo_endpoint":"https://graph.microsoft.com/oidc/userinfo","authorization_endpoint":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/v2.0/authorize","device_authorization_endpoint":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/v2.0/devicecode","http_logout_supported":true,"frontchannel_logout_supported":true,"end_session_endpoint":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/v2.0/logout","claims_supported":["sub","iss","cloud_instance_name","cloud_instance_host_name","cloud_graph_host_name","msgraph_host","aud","exp","iat","auth_time","acr","nonce","preferred_username","name","tid","ver","at_hash","c_hash","email"],"kerberos_endpoint":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/kerberos","tenant_region_scope":"WW","cloud_instance_name":"microsoftonline.com","cloud_graph_host_name":"graph.windows.net","msgraph_host":"graph.microsoft.com","rbac_url":"https://pas.windows.net"}'
        headers:
            Access-Control-Allow-Methods:
                - GET, OPTIONS
            Access-Control-Allow-Origin:
                - '*'
            Cache-Control:
                - max-age=86400, private
            Client-Request-Id:
                - 51efc8c0-7007-4820-bfca-9053f7d39059
            Content-Length:
                - "1753"
            Content-Security-Policy-Report-Only:
                - object-src 'none'; base-uri 'self'; script-src 'self' 'nonce-WnBbcnrrs82ARZRlz6SvYg' 'unsafe-inline' 'unsafe-eval' https://*.msauth.net https://*.msftauth.net https://*.msftauthimages.net https://*.msauthimages.net https://*.msidentity.com https://*.microsoftonline-p.com https://*.microsoftazuread-sso.com https://*.azureedge.net https://*.outlook.com https://*.office.com https://*.office365.com https://*.microsoft.com https://*.bing.com 'report-sample'; report-uri https://csp.microsoft.com/report/ESTS-UX-All
            Content-Type:
                - application/json; charset=utf-8
            Date:
                - Wed, 19 Feb 2025 03:40:27 GMT
            P3p:
                - CP="DSP CUR OTPi IND OTRi ONL FIN"
            Strict-Transport-Security:
                - max-age=31536000; includeSubDomains
            X-Content-Type-Options:
                - nosniff
            X-Ms-Ests-Server:
                - 2.1.20106.4 - SCUS ProdSlices
            X-Ms-Srs:
                - 1.P
            X-Xss-Protection:
                - "0"
        status: 200 OK
        code: 200
        duration: 242.809232ms
    - id: 1
      request:
        proto: HTTP/1.1
        proto_major: 1
        proto_minor: 1
        content_length: 130
        transfer_encoding: []
        trailer: {}
        host: login.microsoftonline.com
        remote_addr: ""
        request_uri: ""
        body: client_id=[REDACTED]&scope=6dae42f8-4368-4678-94ff-3960e28e3630%2F.default+openid+offline_access+profile
        form:
            client_id:
                - '[REDACTED]'
            scope:
                - 6dae42f8-4368-4678-94ff-3960e28e3630/.default openid offline_access profile
        headers:
            Accept-Encoding:
                - gzip
            Client-Request-Id:
                - 8afe3606-ea18-4ef4-bbd0-1c13a67be41b
            Content-Length:
                - "130"
            Content-Type:
                - application/x-www-form-urlencoded; charset=utf-8
            Return-Client-Request-Id:
                - "false"
            User-Agent:
                - azsdk-go-azidentity/v1.8.0 (go1.22.9; linux)
            X-Client-Cpu:
                - amd64
            X-Client-Os:
                - linux
            X-Client-Sku:
                - MSAL.Go
            X-Client-Ver:
                - 1.2.0
        url: https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/v2.0/devicecode
        method: POST
      response:
        proto: HTTP/2.0
        proto_major: 2
        proto_minor: 0
        transfer_encoding: []
        trailer: {}
        content_length: 473
        uncompressed: false
        body: '{"device_code":"[REDACTED]","expires_in":900,"interval":5,"message":"To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code BL6XB575V to authenticate.","user_code":"BL6XB575V","verification_uri":"https://microsoft.com/devicelogin"}'
        headers:
            Cache-Control:
                - no-store, no-cache
            Client-Request-Id:
                - 8afe3606-ea18-4ef4-bbd0-1c13a67be41b
            Content-Length:
                - "473"
            Content-Security-Policy-Report-Only:
                - object-src 'none'; base-uri 'self'; script-src 'self' 'nonce-bUONB85aTQuRnhxPDWJq4w' 'unsafe-inline' 'unsafe-eval' https://*.msauth.net https://*.msftauth.net https://*.msftauthimages.net https://*.msauthimages.net https://*.msidentity.com https://*.microsoftonline-p.com https://*.microsoftazuread-sso.com https://*.azureedge.net https://*.outlook.com https://*.office.com https://*.office365.com https://*.microsoft.com https://*.bing.com 'report-sample'; report-uri https://csp.microsoft.com/report/ESTS-UX-All
            Content-Type:
                - application/json; charset=utf-8
            Date:
                - Wed, 19 Feb 2025 03:40:28 GMT
            Expires:
                - "-1"
            P3p:
                - CP="DSP CUR OTPi IND OTRi ONL FIN"
            Pragma:
                - no-cache
            Strict-Transport-Security:
                - max-age=31536000; includeSubDomains
            X-Content-Type-Options:
                - nosniff
            X-Ms-Clitelem:
                - 1,0,0,,
            X-Ms-Ests-Server:
                - 2.1.20106.4 - WUS3 ProdSlices
            X-Ms-Srs:
                - 1.P
            X-Xss-Protection:
                - "0"
        status: 200 OK
        code: 200
        duration: 220.915149ms
    - id: 2
      request:
        proto: HTTP/1.1
        proto_major: 1
        proto_minor: 1
        content_length: 387
        transfer_encoding: []
        trailer: {}
        host: login.microsoftonline.com
        remote_addr: ""
        request_uri: ""
        body: client_id=[REDACTED]&client_info=1&device_code=[REDACTED]&grant_type=device_code&scope=6dae42f8-4368-4678-94ff-3960e28e3630%2F.default+openid+offline_access+profile
        form:
            client_id:
                - '[REDACTED]'
            client_info:
                - "1"
            device_code:
                - '[REDACTED]'
            grant_type:
                - device_code
            scope:
                - 6dae42f8-4368-4678-94ff-3960e28e3630/.default openid offline_access profile
        headers:
            Accept-Encoding:
                - gzip
            Client-Request-Id:
                - 243bab01-2a73-40fb-aae3-944b689626c9
            Content-Length:
                - "387"
            Content-Type:
                - application/x-www-form-urlencoded; charset=utf-8
            Return-Client-Request-Id:
                - "false"
            User-Agent:
                - azsdk-go-azidentity/v1.8.0 (go1.22.9; linux)
            X-Client-Cpu:
                - amd64
            X-Client-Os:
                - linux
            X-Client-Sku:
                - MSAL.Go
            X-Client-Ver:
                - 1.2.0
        url: https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/v2.0/token
        method: POST
      response:
        proto: HTTP/2.0
        proto_major: 2
        proto_minor: 0
        transfer_encoding: []
        trailer: {}
        content_length: 501
        uncompressed: false
        body: '{"error":"authorization_pending","error_description":"AADSTS70016: OAuth 2.0 device flow error. Authorization is pending. Continue polling. Trace ID: 214d175e-ce4c-48f4-ab71-a7c6bcb37300 Correlation ID: 8afe3606-ea18-4ef4-bbd0-1c13a67be41b Timestamp: 2025-02-19 03:40:30Z","error_codes":[70016],"timestamp":"2025-02-19 03:40:30Z","trace_id":"214d175e-ce4c-48f4-ab71-a7c6bcb37300","correlation_id":"8afe3606-ea18-4ef4-bbd0-1c13a67be41b","error_uri":"https://login.microsoftonline.com/error?code=70016"}'
        headers:
            Cache-Control:
                - no-store, no-cache
            Client-Request-Id:
                - 8afe3606-ea18-4ef4-bbd0-1c13a67be41b
            Content-Length:
                - "501"
            Content-Security-Policy-Report-Only:
                - object-src 'none'; base-uri 'self'; script-src 'self' 'nonce-HvxvCgEFrB8Yz6_Op0ICcw' 'unsafe-inline' 'unsafe-eval' https://*.msauth.net https://*.msftauth.net https://*.msftauthimages.net https://*.msauthimages.net https://*.msidentity.com https://*.microsoftonline-p.com https://*.microsoftazuread-sso.com https://*.azureedge.net https://*.outlook.com https://*.office.com https://*.office365.com https://*.microsoft.com https://*.bing.com 'report-sample'; report-uri https://csp.microsoft.com/report/ESTS-UX-All
            Content-Type:
                - application/json; charset=utf-8
            Date:
                - Wed, 19 Feb 2025 03:40:29 GMT
            Expires:
                - "-1"
            P3p:
                - CP="DSP CUR OTPi IND OTRi ONL FIN"
            Pragma:
                - no-cache
            Strict-Transport-Security:
                - max-age=31536000; includeSubDomains
            X-Content-Type-Options:
                - nosniff
            X-Ms-Clitelem:
                - 1,70016,0,,
            X-Ms-Ests-Server:
                - 2.1.20106.4 - EUS ProdSlices
            X-Ms-Srs:
                - 1.P
            X-Xss-Protection:
                - "0"
        status: 400 Bad Request
        code: 400
        duration: 1.281511469s
    - id: 3
      request:
        proto: HTTP/1.1
        proto_major: 1
        proto_minor: 1
        content_length: 387
        transfer_encoding: []
        trailer: {}
        host: login.microsoftonline.com
        remote_addr: ""
        request_uri: ""
        body: client_id=[REDACTED]&client_info=1&device_code=[REDACTED]&grant_type=device_code&scope=6dae42f8-4368-4678-94ff-3960e28e3630%2F.default+openid+offline_access+profile
        form:
            client_id:
                - '[REDACTED]'
            client_info:
                - "1"
            device_code:
                - '[REDACTED]'
            grant_type:
                - device_code
            scope:
                - 6dae42f8-4368-4678-94ff-3960e28e3630/.default openid offline_access profile
        headers:
            Accept-Encoding:
                - gzip
            Client-Request-Id:
                - b533c512-bcd7-4833-a29d-fc051555e720
            Content-Length:
                - "387"
            Content-Type:
                - application/x-www-form-urlencoded; charset=utf-8
            Return-Client-Request-Id:
                - "false"
            User-Agent:
                - azsdk-go-azidentity/v1.8.0 (go1.22.9; linux)
            X-Client-Cpu:
                - amd64
            X-Client-Os:
                - linux
            X-Client-Sku:
                - MSAL.Go
            X-Client-Ver:
                - 1.2.0
        url: https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/v2.0/token
        method: POST
      response:
        proto: HTTP/2.0
        proto_major: 2
        proto_minor: 0
        transfer_encoding: []
        trailer: {}
        content_length: 5393
        uncompressed: false
        body: '{"access_token":"TEST_ACCESS_TOKEN","client_info":"eyJ1aWQiOiJjNzNjNmYyOC1hZTVmLTQxM2QtYTlhMi1lMTFlNWFmNjY4ZjgiLCJ1dGlkIjoiZTBiZDIzMjEtMDdmYS00Y2YwLTg3YjgtMDBhYTJhNzQ3MzI5In0","expires_in":5302,"ext_expires_in":5302,"id_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Imwzc1EtNTBjQ0g0eEJWWkxIVEd3blNSNzY4MCJ9.eyJhdWQiOiIwNGIwNzc5NS04ZGRiLTQ2MWEtYmJlZS0wMmY5ZTFiZjdiNDYiLCJpc3MiOiJodHRwczovL2xvZ2luLm1pY3Jvc29mdG9ubGluZS5jb20vYzU0ZmFjODgtM2RkMy00NjFmLWE3YzQtOGEzNjhlMDM0MGIzL3YyLjAiLCJpYXQiOjE2MzcxOTEyMTIsIm5iZiI6MTYzNzE5MTIxMiwiZXhwIjoxNjM3MTk1MTEyLCJhaW8iOiJBVVFBdS84VEFBQUFQMExOZGNRUXQxNmJoSkFreXlBdjFoUGJuQVhtT0o3RXJDVHV4N0hNTjhHd2VMb2FYMWR1cDJhQ2Y0a0p5bDFzNmovSzF5R05DZmVIQlBXM21QUWlDdz09IiwiaWRwIjoiaHR0cHM6Ly9zdHMud2luZG93cy5uZXQvZTBiZDIzMjEtMDdmYS00Y2YwLTg3YjgtMDBhYTJhNzQ3MzI5LyIsIm5hbWUiOiJJZGVudGl0eSBUZXN0IFVzZXIiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJpZGVudGl0eXRlc3R1c2VyQGF6dXJlc2Rrb3V0bG9vay5vbm1pY3Jvc29mdC5jb20iLCJyaCI6IjAuQVMwQWlLeFB4ZE05SDBhbnhJbzJqZ05BczVWM3NBVGJqUnBHdS00Qy1lR19lMFl0QUxFLiIsInN1YiI6ImMxYTBsY2xtbWxCYW9wc0MwVmlaLVpPMjFCT2dSUXE3SG9HRUtOOXloZnMiLCJ0aWQiOiJjNTRmYWM4OC0zZGQzLTQ2MWYtYTdjNC04YTM2OGUwMzQwYjMiLCJ1dGkiOiI5TXFOSWI5WjdrQy1QVHRtai11X0FBIiwidmVyIjoiMi4wIn0.hh5Exz9MBjTXrTuTZnz7vceiuQjcC_oRSTeBIC9tYgSO2c2sqQRpZi91qBZFQD9okayLPPKcwqXgEJD9p0-c4nUR5UQN7YSeDLmYtZUYMG79EsA7IMiQaiy94AyIe2E-oBDcLwFycGwh1iIOwwOwjbanmu2Dx3HfQx831lH9uVjagf0Aow0wTkTVCsedGSZvG-cRUceFLj-kFN-feFH3NuScuOfLR2Magf541pJda7X7oStwL_RNUFqjJFTdsiFV4e-VHK5qo--3oPU06z0rS9bosj0pFSATIVHrrS4gY7jiSvgMbG837CDBQkz5b08GUN5GlLN9jlygl1plBmbgww","refresh_token":"TEST_ACCESS_TOKEN","scope":"6dae42f8-4368-4678-94ff-3960e28e3630/user.read 6dae42f8-4368-4678-94ff-3960e28e3630/.default","token_type":"Bearer"}'
        headers:
            Cache-Control:
                - no-store, no-cache
            Client-Request-Id:
                - 8afe3606-ea18-4ef4-bbd0-1c13a67be41b
            Content-Length:
                - "5393"
            Content-Security-Policy-Report-Only:
                - object-src 'none'; base-uri 'self'; script-src 'self' 'nonce-RkPoga2NEeRg2bMx90ZRcg' 'unsafe-inline' 'unsafe-eval' https://*.msauth.net https://*.msftauth.net https://*.msftauthimages.net https://*.msauthimages.net https://*.msidentity.com https://*.microsoftonline-p.com https://*.microsoftazuread-sso.com https://*.azureedge.net https://*.outlook.com https://*.office.com https://*.office365.com https://*.microsoft.com https://*.bing.com 'report-sample'; report-uri https://csp.microsoft.com/report/ESTS-UX-All
            Content-Type:
                - application/json; charset=utf-8
            Date:
                - Wed, 19 Feb 2025 03:40:48 GMT
            Expires:
                - "-1"
            P3p:
                - CP="DSP CUR OTPi IND OTRi ONL FIN"
            Pragma:
                - no-cache
            Strict-Transport-Security:
                - max-age=31536000; includeSubDomains
            X-Content-Type-Options:
                - nosniff
            X-Ms-Clitelem:
                - 1,0,0,,
            X-Ms-Ests-Server:
                - 2.1.20106.4 - SCUS ProdSlices
            X-Ms-Srs:
                - 1.P
            X-Xss-Protection:
                - "0"
        status: 200 OK
        code: 200
        duration: 507.460906ms
070701000000A3000081A4000000000000000000000001691F8CFD000005C5000000000000000000000000000000000000004D00000000kubelogin-0.2.13/pkg/internal/token/fixtures/managedidentity_credential.yaml---
version: 2
interactions:
    - id: 0
      request:
        proto: HTTP/1.1
        proto_major: 1
        proto_minor: 1
        content_length: 0
        transfer_encoding: []
        trailer: {}
        host: 169.254.169.254
        remote_addr: ""
        request_uri: ""
        body: ""
        form: {}
        headers:
            Metadata:
                - "true"
            User-Agent:
                - azsdk-go-azidentity/v1.8.0 (go1.22.9; linux)
        url: http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&client_id=49a6a7eb-d4f9-444a-a216-7b966e31bb05&resource=6dae42f8-4368-4678-94ff-3960e28e3630
        method: GET
      response:
        proto: HTTP/1.1
        proto_major: 1
        proto_minor: 1
        transfer_encoding: []
        trailer: {}
        content_length: 2048
        uncompressed: false
        body: '{"access_token":"TEST_ACCESS_TOKEN","client_id":"49a6a7eb-d4f9-444a-a216-7b966e31bb05","expires_in":"86400","expires_on":"1740028277","ext_expires_in":"86399","not_before":"1739941577","resource":"6dae42f8-4368-4678-94ff-3960e28e3630","token_type":"Bearer"}'
        headers:
            Content-Length:
                - "2048"
            Content-Type:
                - application/json; charset=utf-8
            Date:
                - Wed, 19 Feb 2025 05:11:17 GMT
            Server:
                - IMDS/150.870.65.1497
        status: 200 OK
        code: 200
        duration: 144.213894ms070701000000A4000081A4000000000000000000000001691F8CFD00002CAB000000000000000000000000000000000000004E00000000kubelogin-0.2.13/pkg/internal/token/fixtures/usernamepassword_credential.yaml---
version: 2
interactions:
    - id: 0
      request:
        proto: HTTP/1.1
        proto_major: 1
        proto_minor: 1
        content_length: 0
        transfer_encoding: []
        trailer: {}
        host: login.microsoftonline.com
        remote_addr: ""
        request_uri: ""
        body: ""
        form: {}
        headers:
            Accept-Encoding:
                - gzip
            Client-Request-Id:
                - 71d197c3-e97b-4a7b-b048-b485cc7228a8
            Return-Client-Request-Id:
                - "false"
            User-Agent:
                - azsdk-go-azidentity/v1.8.0 (go1.22.9; linux)
            X-Client-Cpu:
                - amd64
            X-Client-Os:
                - linux
            X-Client-Sku:
                - MSAL.Go
            X-Client-Ver:
                - 1.2.0
        url: https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/v2.0/.well-known/openid-configuration
        method: GET
      response:
        proto: HTTP/2.0
        proto_major: 2
        proto_minor: 0
        transfer_encoding: []
        trailer: {}
        content_length: 1753
        uncompressed: false
        body: '{"token_endpoint":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/v2.0/token","token_endpoint_auth_methods_supported":["client_secret_post","private_key_jwt","client_secret_basic"],"jwks_uri":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/discovery/v2.0/keys","response_modes_supported":["query","fragment","form_post"],"subject_types_supported":["pairwise"],"id_token_signing_alg_values_supported":["RS256"],"response_types_supported":["code","id_token","code id_token","id_token token"],"scopes_supported":["openid","profile","email","offline_access"],"issuer":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/v2.0","request_uri_parameter_supported":false,"userinfo_endpoint":"https://graph.microsoft.com/oidc/userinfo","authorization_endpoint":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/v2.0/authorize","device_authorization_endpoint":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/v2.0/devicecode","http_logout_supported":true,"frontchannel_logout_supported":true,"end_session_endpoint":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/v2.0/logout","claims_supported":["sub","iss","cloud_instance_name","cloud_instance_host_name","cloud_graph_host_name","msgraph_host","aud","exp","iat","auth_time","acr","nonce","preferred_username","name","tid","ver","at_hash","c_hash","email"],"kerberos_endpoint":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/kerberos","tenant_region_scope":"NA","cloud_instance_name":"microsoftonline.com","cloud_graph_host_name":"graph.windows.net","msgraph_host":"graph.microsoft.com","rbac_url":"https://pas.windows.net"}'
        headers:
            Access-Control-Allow-Methods:
                - GET, OPTIONS
            Access-Control-Allow-Origin:
                - '*'
            Cache-Control:
                - max-age=86400, private
            Client-Request-Id:
                - 71d197c3-e97b-4a7b-b048-b485cc7228a8
            Content-Length:
                - "1753"
            Content-Security-Policy-Report-Only:
                - object-src 'none'; base-uri 'self'; script-src 'self' 'nonce-fR-WzXKmTMoCTK7_B5nLxQ' 'unsafe-inline' 'unsafe-eval' https://*.msauth.net https://*.msftauth.net https://*.msftauthimages.net https://*.msauthimages.net https://*.msidentity.com https://*.microsoftonline-p.com https://*.microsoftazuread-sso.com https://*.azureedge.net https://*.outlook.com https://*.office.com https://*.office365.com https://*.microsoft.com https://*.bing.com 'report-sample'; report-uri https://csp.microsoft.com/report/ESTS-UX-All
            Content-Type:
                - application/json; charset=utf-8
            Date:
                - Wed, 19 Feb 2025 03:49:11 GMT
            P3p:
                - CP="DSP CUR OTPi IND OTRi ONL FIN"
            Strict-Transport-Security:
                - max-age=31536000; includeSubDomains
            X-Content-Type-Options:
                - nosniff
            X-Ms-Ests-Server:
                - 2.1.20106.4 - SCUS ProdSlices
            X-Ms-Srs:
                - 1.P
            X-Xss-Protection:
                - "0"
        status: 200 OK
        code: 200
        duration: 612.663239ms
    - id: 1
      request:
        proto: HTTP/1.1
        proto_major: 1
        proto_minor: 1
        content_length: 0
        transfer_encoding: []
        trailer: {}
        host: login.microsoftonline.com
        remote_addr: ""
        request_uri: ""
        body: ""
        form: {}
        headers:
            Accept-Encoding:
                - gzip
            Client-Request-Id:
                - 302ec217-0491-498d-96a5-fea43adb8e9a
            Return-Client-Request-Id:
                - "false"
            User-Agent:
                - azsdk-go-azidentity/v1.8.0 (go1.22.9; linux)
            X-Client-Cpu:
                - amd64
            X-Client-Os:
                - linux
            X-Client-Sku:
                - MSAL.Go
            X-Client-Ver:
                - 1.2.0
        url: https://login.microsoftonline.com/common/UserRealm/user@example.com?api-version=1.0
        method: GET
      response:
        proto: HTTP/2.0
        proto_major: 2
        proto_minor: 0
        transfer_encoding: []
        trailer: {}
        content_length: 192
        uncompressed: false
        body: '{"ver":"1.0","account_type":"Managed","domain_name":"azureredhatopenshift849.onmicrosoft.com","cloud_instance_name":"microsoftonline.com","cloud_audience_urn":"urn:federation:MicrosoftOnline"}'
        headers:
            Cache-Control:
                - no-store, no-cache
            Client-Request-Id:
                - 302ec217-0491-498d-96a5-fea43adb8e9a
            Content-Disposition:
                - inline; filename=userrealm.json
            Content-Length:
                - "192"
            Content-Security-Policy-Report-Only:
                - object-src 'none'; base-uri 'self'; script-src 'self' 'nonce-fDJMSqYR5s_RTU9lD3Bxiw' 'unsafe-inline' 'unsafe-eval' https://*.msauth.net https://*.msftauth.net https://*.msftauthimages.net https://*.msauthimages.net https://*.msidentity.com https://*.microsoftonline-p.com https://*.microsoftazuread-sso.com https://*.azureedge.net https://*.outlook.com https://*.office.com https://*.office365.com https://*.microsoft.com https://*.bing.com 'report-sample'; report-uri https://csp.microsoft.com/report/ESTS-UX-All
            Content-Type:
                - application/json; charset=utf-8
            Date:
                - Wed, 19 Feb 2025 03:49:11 GMT
            Expires:
                - "-1"
            P3p:
                - CP="DSP CUR OTPi IND OTRi ONL FIN"
            Pragma:
                - no-cache
            Strict-Transport-Security:
                - max-age=31536000; includeSubDomains
            X-Content-Type-Options:
                - nosniff
            X-Ms-Ests-Server:
                - 2.1.20106.4 - SCUS ProdSlices
            X-Ms-Srs:
                - 1.P
            X-Xss-Protection:
                - "0"
        status: 200 OK
        code: 200
        duration: 124.006508ms
    - id: 2
      request:
        proto: HTTP/1.1
        proto_major: 1
        proto_minor: 1
        content_length: 243
        transfer_encoding: []
        trailer: {}
        host: login.microsoftonline.com
        remote_addr: ""
        request_uri: ""
        body: client_id=[REDACTED]&client_info=1&grant_type=password&password=[REDACTED]&scope=6dae42f8-4368-4678-94ff-3960e28e3630%2F.default+openid+offline_access+profile&username=user@example.com
        form:
            client_id:
                - '[REDACTED]'
            client_info:
                - "1"
            grant_type:
                - password
            password:
                - '[REDACTED]'
            scope:
                - 6dae42f8-4368-4678-94ff-3960e28e3630/.default openid offline_access profile
            username:
                - '[REDACTED]'
        headers:
            Accept-Encoding:
                - gzip
            Client-Request-Id:
                - 10148fe9-b8bd-4c03-922f-8d351298ffa7
            Content-Length:
                - "243"
            Content-Type:
                - application/x-www-form-urlencoded; charset=utf-8
            Return-Client-Request-Id:
                - "false"
            User-Agent:
                - azsdk-go-azidentity/v1.8.0 (go1.22.9; linux)
            X-Client-Cpu:
                - amd64
            X-Client-Os:
                - linux
            X-Client-Sku:
                - MSAL.Go
            X-Client-Ver:
                - 1.2.0
        url: https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/v2.0/token
        method: POST
      response:
        proto: HTTP/2.0
        proto_major: 2
        proto_minor: 0
        transfer_encoding: []
        trailer: {}
        content_length: 635
        uncompressed: false
        body: '{"error":"invalid_grant","error_description":"AADSTS50076: Due to a configuration change made by your administrator, or because you moved to a new location, you must use multi-factor authentication to access ''6dae42f8-4368-4678-94ff-3960e28e3630''. Trace ID: d828e473-16cc-480a-82f2-1278faf5e100 Correlation ID: 10148fe9-b8bd-4c03-922f-8d351298ffa7 Timestamp: 2025-02-19 03:49:12Z","error_codes":[50076],"timestamp":"2025-02-19 03:49:12Z","trace_id":"d828e473-16cc-480a-82f2-1278faf5e100","correlation_id":"10148fe9-b8bd-4c03-922f-8d351298ffa7","error_uri":"https://login.microsoftonline.com/error?code=50076","suberror":"basic_action"}'
        headers:
            Cache-Control:
                - no-store, no-cache
            Client-Request-Id:
                - 10148fe9-b8bd-4c03-922f-8d351298ffa7
            Content-Length:
                - "635"
            Content-Security-Policy-Report-Only:
                - object-src 'none'; base-uri 'self'; script-src 'self' 'nonce-_8miMZeHC5H3y1HPCLTxgw' 'unsafe-inline' 'unsafe-eval' https://*.msauth.net https://*.msftauth.net https://*.msftauthimages.net https://*.msauthimages.net https://*.msidentity.com https://*.microsoftonline-p.com https://*.microsoftazuread-sso.com https://*.azureedge.net https://*.outlook.com https://*.office.com https://*.office365.com https://*.microsoft.com https://*.bing.com 'report-sample'; report-uri https://csp.microsoft.com/report/ESTS-UX-All
            Content-Type:
                - application/json; charset=utf-8
            Date:
                - Wed, 19 Feb 2025 03:49:11 GMT
            Expires:
                - "-1"
            P3p:
                - CP="DSP CUR OTPi IND OTRi ONL FIN"
            Pragma:
                - no-cache
            Strict-Transport-Security:
                - max-age=31536000; includeSubDomains
            X-Content-Type-Options:
                - nosniff
            X-Ms-Clitelem:
                - 1,50076,0,,
            X-Ms-Ests-Server:
                - 2.1.20106.4 - NCUS ProdSlices
            X-Ms-Srs:
                - 1.P
            X-Xss-Protection:
                - "0"
        status: 400 Bad Request
        code: 400
        duration: 229.357562ms
070701000000A5000081A4000000000000000000000001691F8CFD00002E8C000000000000000000000000000000000000004E00000000kubelogin-0.2.13/pkg/internal/token/fixtures/workloadidentity_credential.yaml---
version: 2
interactions:
    - id: 0
      request:
        proto: HTTP/1.1
        proto_major: 1
        proto_minor: 1
        content_length: 0
        transfer_encoding: []
        trailer: {}
        host: login.microsoftonline.com
        remote_addr: ""
        request_uri: ""
        body: ""
        form: {}
        headers:
            Accept-Encoding:
                - gzip
            Client-Request-Id:
                - 77c874a5-7647-48df-8307-d95f7b066109
            Return-Client-Request-Id:
                - "false"
            User-Agent:
                - azsdk-go-azidentity/v1.8.0 (go1.22.9; linux)
            X-Client-Cpu:
                - amd64
            X-Client-Os:
                - linux
            X-Client-Sku:
                - MSAL.Go
            X-Client-Ver:
                - 1.2.0
        url: https://login.microsoftonline.com/common/discovery/instance?api-version=1.1&authorization_endpoint=https%3A%2F%2Flogin.microsoftonline.com%2F00000000-0000-0000-0000-000000000000%2Foauth2%2Fv2.0%2Fauthorize
        method: GET
      response:
        proto: HTTP/2.0
        proto_major: 2
        proto_minor: 0
        transfer_encoding: []
        trailer: {}
        content_length: 980
        uncompressed: false
        body: '{"tenant_discovery_endpoint":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/v2.0/.well-known/openid-configuration","api-version":"1.1","metadata":[{"preferred_network":"login.microsoftonline.com","preferred_cache":"login.windows.net","aliases":["login.microsoftonline.com","login.windows.net","login.microsoft.com","sts.windows.net"]},{"preferred_network":"login.partner.microsoftonline.cn","preferred_cache":"login.partner.microsoftonline.cn","aliases":["login.partner.microsoftonline.cn","login.chinacloudapi.cn"]},{"preferred_network":"login.microsoftonline.de","preferred_cache":"login.microsoftonline.de","aliases":["login.microsoftonline.de"]},{"preferred_network":"login.microsoftonline.us","preferred_cache":"login.microsoftonline.us","aliases":["login.microsoftonline.us","login.usgovcloudapi.net"]},{"preferred_network":"login-us.microsoftonline.com","preferred_cache":"login-us.microsoftonline.com","aliases":["login-us.microsoftonline.com"]}]}'
        headers:
            Access-Control-Allow-Methods:
                - GET, OPTIONS
            Access-Control-Allow-Origin:
                - '*'
            Cache-Control:
                - max-age=86400, private
            Client-Request-Id:
                - 77c874a5-7647-48df-8307-d95f7b066109
            Content-Length:
                - "980"
            Content-Security-Policy-Report-Only:
                - object-src 'none'; base-uri 'self'; script-src 'self' 'nonce-opJX6NtvONImkvZrrIpfPA' 'unsafe-inline' 'unsafe-eval' https://*.msauth.net https://*.msftauth.net https://*.msftauthimages.net https://*.msauthimages.net https://*.msidentity.com https://*.microsoftonline-p.com https://*.microsoftazuread-sso.com https://*.azureedge.net https://*.outlook.com https://*.office.com https://*.office365.com https://*.microsoft.com https://*.bing.com 'report-sample'; report-uri https://csp.microsoft.com/report/ESTS-UX-All
            Content-Type:
                - application/json; charset=utf-8
            Date:
                - Wed, 19 Feb 2025 04:33:12 GMT
            P3p:
                - CP="DSP CUR OTPi IND OTRi ONL FIN"
            Strict-Transport-Security:
                - max-age=31536000; includeSubDomains
            X-Content-Type-Options:
                - nosniff
            X-Ms-Ests-Server:
                - 2.1.20051.5 - NCUS ProdSlices
            X-Ms-Srs:
                - 1.P
            X-Xss-Protection:
                - "0"
        status: 200 OK
        code: 200
        duration: 340.052528ms
    - id: 1
      request:
        proto: HTTP/1.1
        proto_major: 1
        proto_minor: 1
        content_length: 0
        transfer_encoding: []
        trailer: {}
        host: login.microsoftonline.com
        remote_addr: ""
        request_uri: ""
        body: ""
        form: {}
        headers:
            Accept-Encoding:
                - gzip
            Client-Request-Id:
                - 045b3798-fa6e-4fb7-bfb3-77208b03416a
            Return-Client-Request-Id:
                - "false"
            User-Agent:
                - azsdk-go-azidentity/v1.8.0 (go1.22.9; linux)
            X-Client-Cpu:
                - amd64
            X-Client-Os:
                - linux
            X-Client-Sku:
                - MSAL.Go
            X-Client-Ver:
                - 1.2.0
        url: https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/v2.0/.well-known/openid-configuration
        method: GET
      response:
        proto: HTTP/2.0
        proto_major: 2
        proto_minor: 0
        transfer_encoding: []
        trailer: {}
        content_length: 1753
        uncompressed: false
        body: '{"token_endpoint":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/v2.0/token","token_endpoint_auth_methods_supported":["client_secret_post","private_key_jwt","client_secret_basic"],"jwks_uri":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/discovery/v2.0/keys","response_modes_supported":["query","fragment","form_post"],"subject_types_supported":["pairwise"],"id_token_signing_alg_values_supported":["RS256"],"response_types_supported":["code","id_token","code id_token","id_token token"],"scopes_supported":["openid","profile","email","offline_access"],"issuer":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/v2.0","request_uri_parameter_supported":false,"userinfo_endpoint":"https://graph.microsoft.com/oidc/userinfo","authorization_endpoint":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/v2.0/authorize","device_authorization_endpoint":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/v2.0/devicecode","http_logout_supported":true,"frontchannel_logout_supported":true,"end_session_endpoint":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/v2.0/logout","claims_supported":["sub","iss","cloud_instance_name","cloud_instance_host_name","cloud_graph_host_name","msgraph_host","aud","exp","iat","auth_time","acr","nonce","preferred_username","name","tid","ver","at_hash","c_hash","email"],"kerberos_endpoint":"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/kerberos","tenant_region_scope":"WW","cloud_instance_name":"microsoftonline.com","cloud_graph_host_name":"graph.windows.net","msgraph_host":"graph.microsoft.com","rbac_url":"https://pas.windows.net"}'
        headers:
            Access-Control-Allow-Methods:
                - GET, OPTIONS
            Access-Control-Allow-Origin:
                - '*'
            Cache-Control:
                - max-age=86400, private
            Client-Request-Id:
                - 045b3798-fa6e-4fb7-bfb3-77208b03416a
            Content-Length:
                - "1753"
            Content-Security-Policy-Report-Only:
                - object-src 'none'; base-uri 'self'; script-src 'self' 'nonce-9z_rTAo_nA4n9vWbGt2mFg' 'unsafe-inline' 'unsafe-eval' https://*.msauth.net https://*.msftauth.net https://*.msftauthimages.net https://*.msauthimages.net https://*.msidentity.com https://*.microsoftonline-p.com https://*.microsoftazuread-sso.com https://*.azureedge.net https://*.outlook.com https://*.office.com https://*.office365.com https://*.microsoft.com https://*.bing.com 'report-sample'; report-uri https://csp.microsoft.com/report/ESTS-UX-All
            Content-Type:
                - application/json; charset=utf-8
            Date:
                - Wed, 19 Feb 2025 04:33:12 GMT
            P3p:
                - CP="DSP CUR OTPi IND OTRi ONL FIN"
            Strict-Transport-Security:
                - max-age=31536000; includeSubDomains
            X-Content-Type-Options:
                - nosniff
            X-Ms-Ests-Server:
                - 2.1.20106.4 - WUS3 ProdSlices
            X-Ms-Srs:
                - 1.P
            X-Xss-Protection:
                - "0"
        status: 200 OK
        code: 200
        duration: 48.183889ms
    - id: 2
      request:
        proto: HTTP/1.1
        proto_major: 1
        proto_minor: 1
        content_length: 1649
        transfer_encoding: []
        trailer: {}
        host: login.microsoftonline.com
        remote_addr: ""
        request_uri: ""
        body: client_assertion=[REDACTED]&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer&client_id=[REDACTED]&client_info=1&grant_type=client_credentials&scope=6dae42f8-4368-4678-94ff-3960e28e3630%2F.default+openid+offline_access+profile
        form:
            client_assertion:
                - '[REDACTED]'
            client_assertion_type:
                - urn:ietf:params:oauth:client-assertion-type:jwt-bearer
            client_id:
                - '[REDACTED]'
            client_info:
                - "1"
            grant_type:
                - client_credentials
            scope:
                - 6dae42f8-4368-4678-94ff-3960e28e3630/.default openid offline_access profile
        headers:
            Accept-Encoding:
                - gzip
            Client-Request-Id:
                - 4644ee85-42a4-4f5b-ac35-b5a3843a7ace
            Content-Length:
                - "1649"
            Content-Type:
                - application/x-www-form-urlencoded; charset=utf-8
            Return-Client-Request-Id:
                - "false"
            User-Agent:
                - azsdk-go-azidentity/v1.8.0 (go1.22.9; linux)
            X-Client-Cpu:
                - amd64
            X-Client-Os:
                - linux
            X-Client-Sku:
                - MSAL.Go
            X-Client-Ver:
                - 1.2.0
        url: https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/v2.0/token
        method: POST
      response:
        proto: HTTP/2.0
        proto_major: 2
        proto_minor: 0
        transfer_encoding: []
        trailer: {}
        content_length: 1626
        uncompressed: false
        body: '{"access_token":"TEST_ACCESS_TOKEN","expires_in":86399,"ext_expires_in":86399,"token_type":"Bearer"}'
        headers:
            Cache-Control:
                - no-store, no-cache
            Client-Request-Id:
                - 4644ee85-42a4-4f5b-ac35-b5a3843a7ace
            Content-Length:
                - "1626"
            Content-Security-Policy-Report-Only:
                - object-src 'none'; base-uri 'self'; script-src 'self' 'nonce-U3JWdpDjbBzvPa_OFvgECA' 'unsafe-inline' 'unsafe-eval' https://*.msauth.net https://*.msftauth.net https://*.msftauthimages.net https://*.msauthimages.net https://*.msidentity.com https://*.microsoftonline-p.com https://*.microsoftazuread-sso.com https://*.azureedge.net https://*.outlook.com https://*.office.com https://*.office365.com https://*.microsoft.com https://*.bing.com 'report-sample'; report-uri https://csp.microsoft.com/report/ESTS-UX-All
            Content-Type:
                - application/json; charset=utf-8
            Date:
                - Wed, 19 Feb 2025 04:33:12 GMT
            Expires:
                - "-1"
            P3p:
                - CP="DSP CUR OTPi IND OTRi ONL FIN"
            Pragma:
                - no-cache
            Strict-Transport-Security:
                - max-age=31536000; includeSubDomains
            X-Content-Type-Options:
                - nosniff
            X-Ms-Clitelem:
                - 1,0,0,,
            X-Ms-Ests-Server:
                - 2.1.20106.4 - WUS3 ProdSlices
            X-Ms-Srs:
                - 1.P
            X-Xss-Protection:
                - "0"
        status: 200 OK
        code: 200
        duration: 267.028594ms
070701000000A6000081A4000000000000000000000001691F8CFD00000EDB000000000000000000000000000000000000003F00000000kubelogin-0.2.13/pkg/internal/token/githubactionscredential.gopackage token

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"

	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
	"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
	"github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential"
)

type githubTokenResponse struct {
	Value string `json:"value"`
}

type GithubActionsCredential struct {
	client confidential.Client
}

var _ CredentialProvider = (*GithubActionsCredential)(nil)

func newGithubActionsCredential(opts *Options) (CredentialProvider, error) {
	if opts.ClientID == "" {
		return nil, fmt.Errorf("client ID cannot be empty")
	}
	if opts.TenantID == "" {
		return nil, fmt.Errorf("tenant ID cannot be empty")
	}
	cred := confidential.NewCredFromAssertionCallback(func(ctx context.Context, _ confidential.AssertionRequestOptions) (string, error) {
		return getGitHubToken(ctx)
	})

	o := []confidential.Option{
		confidential.WithInstanceDiscovery(!opts.DisableInstanceDiscovery),
	}
	if opts.httpClient != nil {
		o = append(o, confidential.WithHTTPClient(opts.httpClient))
	}
	client, err := confidential.New(
		fmt.Sprintf("%s%s/", opts.GetCloudConfiguration().ActiveDirectoryAuthorityHost, opts.TenantID),
		opts.ClientID, cred, o...)
	if err != nil {
		return nil, fmt.Errorf("failed to create github actions credential: %w", err)
	}

	return &GithubActionsCredential{client: client}, nil
}

func (c *GithubActionsCredential) Name() string {
	return "GithubActionsCredential"
}

func (c *GithubActionsCredential) Authenticate(ctx context.Context, opts *policy.TokenRequestOptions) (azidentity.AuthenticationRecord, error) {
	return azidentity.AuthenticationRecord{}, errAuthenticateNotSupported
}

func (c *GithubActionsCredential) GetToken(ctx context.Context, opts policy.TokenRequestOptions) (azcore.AccessToken, error) {
	result, err := c.client.AcquireTokenByCredential(ctx, opts.Scopes)
	if err != nil {
		return azcore.AccessToken{}, err
	}

	return azcore.AccessToken{Token: result.AccessToken, ExpiresOn: result.ExpiresOn}, nil
}

func (c *GithubActionsCredential) NeedAuthenticate() bool {
	return false
}

func getGitHubToken(ctx context.Context) (string, error) {
	reqToken := os.Getenv(actionsIDTokenRequestToken)
	reqURL := os.Getenv(actionsIDTokenRequestURL)

	if reqToken == "" || reqURL == "" {
		return "", errors.New("ACTIONS_ID_TOKEN_REQUEST_TOKEN or ACTIONS_ID_TOKEN_REQUEST_URL is not set")
	}

	u, err := url.Parse(reqURL)
	if err != nil {
		return "", fmt.Errorf("unable to parse ACTIONS_ID_TOKEN_REQUEST_URL: %w", err)
	}
	q := u.Query()
	q.Set("audience", azureADAudience)
	u.RawQuery = q.Encode()

	req, err := http.NewRequestWithContext(ctx, "GET", u.String(), nil)
	if err != nil {
		return "", err
	}

	// reference:
	// https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect
	req.Header.Set("Authorization", fmt.Sprintf("bearer %s", reqToken))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Accept", "application/json; api-version=2.0")

	client := http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		var body string
		b, err := io.ReadAll(resp.Body)
		if err != nil {
			body = err.Error()
		} else {
			body = string(b)
		}

		return "", fmt.Errorf("github actions ID token request failed with status code: %d, response body: %s", resp.StatusCode, body)
	}

	var tokenResp githubTokenResponse
	if err := json.NewDecoder(resp.Body).Decode(&tokenResp); err != nil {
		return "", err
	}

	if tokenResp.Value == "" {
		return "", errors.New("github actions ID token is empty")
	}

	return tokenResp.Value, nil
}
070701000000A7000081A4000000000000000000000001691F8CFD00000B9E000000000000000000000000000000000000004400000000kubelogin-0.2.13/pkg/internal/token/githubactionscredential_test.gopackage token

import (
	"context"
	"net/http"
	"net/http/httptest"
	"os"
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestNewGithubActionsCredential(t *testing.T) {
	t.Run("valid options", func(t *testing.T) {
		opts := &Options{
			ClientID: "test-client-id",
			TenantID: "test-tenant-id",
		}

		cred, err := newGithubActionsCredential(opts)
		assert.NoError(t, err)
		assert.NotNil(t, cred)
		assert.Equal(t, "GithubActionsCredential", cred.Name())
	})

	t.Run("missing client ID", func(t *testing.T) {
		opts := &Options{
			TenantID: "test-tenant-id",
		}

		cred, err := newGithubActionsCredential(opts)
		assert.Error(t, err)
		assert.Nil(t, cred)
		assert.Equal(t, "client ID cannot be empty", err.Error())
	})

	t.Run("missing tenant ID", func(t *testing.T) {
		opts := &Options{
			ClientID: "test-client-id",
		}

		cred, err := newGithubActionsCredential(opts)
		assert.Error(t, err)
		assert.Nil(t, cred)
		assert.Equal(t, "tenant ID cannot be empty", err.Error())
	})
}

func TestGetGitHubToken(t *testing.T) {
	t.Run("valid token", func(t *testing.T) {
		ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			w.Header().Set("Content-Type", "application/json")
			w.WriteHeader(http.StatusOK)
			w.Write([]byte(`{"value":"TEST_ACCESS_TOKEN"}`))
		}))
		defer ts.Close()

		os.Setenv("ACTIONS_ID_TOKEN_REQUEST_TOKEN", "test-token")
		os.Setenv("ACTIONS_ID_TOKEN_REQUEST_URL", ts.URL)

		token, err := getGitHubToken(context.Background())
		assert.NoError(t, err)
		assert.Equal(t, "TEST_ACCESS_TOKEN", token)
	})

	t.Run("invalid token", func(t *testing.T) {
		ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			w.Header().Set("Content-Type", "application/json")
			w.WriteHeader(http.StatusOK)
			w.Write([]byte(`{"value":""}`))
		}))
		defer ts.Close()

		os.Setenv("ACTIONS_ID_TOKEN_REQUEST_TOKEN", "test-token")
		os.Setenv("ACTIONS_ID_TOKEN_REQUEST_URL", ts.URL)

		token, err := getGitHubToken(context.Background())
		assert.Error(t, err)
		assert.Equal(t, "", token)
		assert.Equal(t, "github actions ID token is empty", err.Error())
	})

	t.Run("http request failure", func(t *testing.T) {
		os.Setenv("ACTIONS_ID_TOKEN_REQUEST_TOKEN", "test-token")
		os.Setenv("ACTIONS_ID_TOKEN_REQUEST_URL", "http://invalid-url")

		token, err := getGitHubToken(context.Background())
		assert.Error(t, err)
		assert.Equal(t, "", token)
	})

	t.Run("invalid response from GitHub", func(t *testing.T) {
		ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			w.Header().Set("Content-Type", "application/json")
			w.WriteHeader(http.StatusOK)
			w.Write([]byte(`{"invalid":"response"}`))
		}))
		defer ts.Close()

		os.Setenv("ACTIONS_ID_TOKEN_REQUEST_TOKEN", "test-token")
		os.Setenv("ACTIONS_ID_TOKEN_REQUEST_URL", ts.URL)

		token, err := getGitHubToken(context.Background())
		assert.Error(t, err)
		assert.Equal(t, "", token)
	})
}
070701000000A8000081A4000000000000000000000001691F8CFD00000892000000000000000000000000000000000000004400000000kubelogin-0.2.13/pkg/internal/token/interactivebrowsercredential.gopackage token

import (
	"context"
	"fmt"

	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
	"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
	"github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache"
	"k8s.io/klog/v2"
)

type InteractiveBrowserCredential struct {
	cred *azidentity.InteractiveBrowserCredential
}

var _ CredentialProvider = (*InteractiveBrowserCredential)(nil)

func newInteractiveBrowserCredential(opts *Options, record azidentity.AuthenticationRecord) (CredentialProvider, error) {
	if opts.ClientID == "" {
		return nil, fmt.Errorf("client ID cannot be empty")
	}
	if opts.TenantID == "" {
		return nil, fmt.Errorf("tenant ID cannot be empty")
	}
	var (
		c   azidentity.Cache
		err error
	)
	if opts.UsePersistentCache {
		c, err = cache.New(nil)
		if err != nil {
			klog.V(5).Infof("failed to create cache: %v", err)
		}
	}

	azOpts := &azidentity.InteractiveBrowserCredentialOptions{
		ClientOptions:            azcore.ClientOptions{Cloud: opts.GetCloudConfiguration()},
		AuthenticationRecord:     record,
		Cache:                    c,
		ClientID:                 opts.ClientID,
		TenantID:                 opts.TenantID,
		DisableInstanceDiscovery: opts.DisableInstanceDiscovery,
		RedirectURL:              opts.RedirectURL,
		LoginHint:                opts.LoginHint,
	}

	if opts.httpClient != nil {
		azOpts.Transport = opts.httpClient
	}

	cred, err := azidentity.NewInteractiveBrowserCredential(azOpts)
	if err != nil {
		return nil, fmt.Errorf("failed to create interactive browser credential: %w", err)
	}
	return &InteractiveBrowserCredential{cred: cred}, nil
}

func (c *InteractiveBrowserCredential) Name() string {
	return "InteractiveBrowserCredential"
}

func (c *InteractiveBrowserCredential) Authenticate(ctx context.Context, opts *policy.TokenRequestOptions) (azidentity.AuthenticationRecord, error) {
	return c.cred.Authenticate(ctx, opts)
}

func (c *InteractiveBrowserCredential) GetToken(ctx context.Context, opts policy.TokenRequestOptions) (azcore.AccessToken, error) {
	return c.cred.GetToken(ctx, opts)
}

func (c *InteractiveBrowserCredential) NeedAuthenticate() bool {
	return true
}
070701000000A9000081A4000000000000000000000001691F8CFD00000440000000000000000000000000000000000000004900000000kubelogin-0.2.13/pkg/internal/token/interactivebrowsercredential_test.gopackage token

import (
	"context"
	"os"
	"testing"

	"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
	"github.com/stretchr/testify/assert"
)

func TestInteractiveBrowserCredential_GetToken(t *testing.T) {
	if _, ok := os.LookupEnv("KUBELOGIN_MANUAL_TEST"); !ok {
		t.Skip("skipping test because KUBELOGIN_MANUAL_TEST is not set")
	}

	liveTestTenantID := os.Getenv("KUBELOGIN_LIVETEST_TENANT_ID")

	if liveTestTenantID == "" {
		t.Skip("skipping test because KUBELOGIN_LIVETEST_TENANT_ID is not set")
	}

	opts := &Options{
		ClientID: "80faf920-1908-4b52-b5ef-a8e7bedfc67a",
		ServerID: "6dae42f8-4368-4678-94ff-3960e28e3630",
		TenantID: liveTestTenantID,
	}
	record := azidentity.AuthenticationRecord{}
	cred, err := newInteractiveBrowserCredential(opts, record)
	if err != nil {
		t.Fatalf("failed to create credential: %v", err)
	}

	token, err := cred.GetToken(context.Background(), policy.TokenRequestOptions{
		Scopes: []string{opts.ServerID + "/.default"},
	})
	assert.NoError(t, err)
	assert.NotEmpty(t, token.Token)
}
070701000000AA000081A4000000000000000000000001691F8CFD00000C5B000000000000000000000000000000000000004B00000000kubelogin-0.2.13/pkg/internal/token/interactivebrowsercredentialwithpop.gopackage token

import (
	"context"
	"fmt"
	"net/url"
	"time"

	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
	"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
	"github.com/Azure/kubelogin/pkg/internal/pop"
	"github.com/AzureAD/microsoft-authentication-library-for-go/apps/public"
)

type InteractiveBrowserCredentialWithPoP struct {
	popClaims   map[string]string
	client      public.Client
	options     *pop.MsalClientOptions
	keyProvider PoPKeyProvider
}

var _ CredentialProvider = (*InteractiveBrowserCredentialWithPoP)(nil)

func newInteractiveBrowserCredentialWithPoP(opts *Options) (CredentialProvider, error) {
	if opts.ClientID == "" {
		return nil, fmt.Errorf("client ID cannot be empty")
	}
	if opts.TenantID == "" {
		return nil, fmt.Errorf("tenant ID cannot be empty")
	}
	popClaimsMap, err := parsePoPClaims(opts.PoPTokenClaims)
	if err != nil {
		return nil, fmt.Errorf("unable to parse PoP claims: %w", err)
	}
	if len(popClaimsMap) == 0 {
		return nil, fmt.Errorf("number of pop claims is invalid: %d", len(popClaimsMap))
	}

	// Construct authority URL properly to avoid malformation
	authorityURL, err := url.JoinPath(opts.GetCloudConfiguration().ActiveDirectoryAuthorityHost, opts.TenantID)
	if err != nil {
		return nil, fmt.Errorf("unable to construct authority URL: %w", err)
	}

	msalOpts := &pop.MsalClientOptions{
		Authority:                authorityURL,
		ClientID:                 opts.ClientID,
		TenantID:                 opts.TenantID,
		DisableInstanceDiscovery: opts.DisableInstanceDiscovery,
	}
	if opts.httpClient != nil {
		msalOpts.Options.Transport = opts.httpClient
	}

	// Get cache from Options
	popCache := opts.GetPoPTokenCache()

	client, err := pop.NewPublicClient(
		msalOpts,
		pop.WithCustomCachePublic(popCache),
	)
	if err != nil {
		return nil, fmt.Errorf("unable to create public client: %w", err)
	}

	return &InteractiveBrowserCredentialWithPoP{
		options:     msalOpts,
		client:      client,
		popClaims:   popClaimsMap,
		keyProvider: opts.GetPoPKeyProvider(),
	}, nil
}

func (c *InteractiveBrowserCredentialWithPoP) Name() string {
	return "InteractiveBrowserCredentialWithPoP"
}

func (c *InteractiveBrowserCredentialWithPoP) Authenticate(ctx context.Context, opts *policy.TokenRequestOptions) (azidentity.AuthenticationRecord, error) {
	return azidentity.AuthenticationRecord{}, errAuthenticateNotSupported
}

func (c *InteractiveBrowserCredentialWithPoP) GetToken(ctx context.Context, opts policy.TokenRequestOptions) (azcore.AccessToken, error) {
	// Get PoP key using centralized key provider
	popKey, err := c.keyProvider.GetPoPKey()
	if err != nil {
		return azcore.AccessToken{}, err
	}

	token, expirationTimeUnix, err := pop.AcquirePoPTokenInteractive(
		ctx,
		c.popClaims,
		opts.Scopes,
		c.client,
		c.options,
		popKey,
	)
	if err != nil {
		return azcore.AccessToken{}, fmt.Errorf("failed to create PoP token using interactive login: %w", err)
	}
	return azcore.AccessToken{Token: token, ExpiresOn: time.Unix(expirationTimeUnix, 0)}, nil
}

func (c *InteractiveBrowserCredentialWithPoP) NeedAuthenticate() bool {
	return false
}
070701000000AB000081A4000000000000000000000001691F8CFD00000979000000000000000000000000000000000000005000000000kubelogin-0.2.13/pkg/internal/token/interactivebrowsercredentialwithpop_test.gopackage token

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestNewInteractiveBrowserCredentialWithPoP(t *testing.T) {
	testCases := []struct {
		name           string
		opts           *Options
		expectErrorMsg string
		expectName     string
	}{
		{
			name: "valid options",
			opts: &Options{
				ClientID:          "test-client-id",
				TenantID:          "test-tenant-id",
				IsPoPTokenEnabled: true,
				PoPTokenClaims:    "u=test-cluster",
			},
			expectName: "InteractiveBrowserCredentialWithPoP",
		},
		{
			name: "missing client ID",
			opts: &Options{
				TenantID:          "test-tenant-id",
				IsPoPTokenEnabled: true,
				PoPTokenClaims:    "u=test-cluster",
			},
			expectErrorMsg: "client ID cannot be empty",
		},
		{
			name: "missing tenant ID",
			opts: &Options{
				ClientID:          "test-client-id",
				IsPoPTokenEnabled: true,
				PoPTokenClaims:    "u=test-cluster",
			},
			expectErrorMsg: "tenant ID cannot be empty",
		},
		{
			name: "missing PoP claims",
			opts: &Options{
				ClientID:          "test-client-id",
				TenantID:          "test-tenant-id",
				IsPoPTokenEnabled: true,
			},
			expectErrorMsg: "unable to parse PoP claims: failed to parse PoP token claims: no claims provided",
		},
		{
			name: "invalid PoP claims format",
			opts: &Options{
				ClientID:          "test-client-id",
				TenantID:          "test-tenant-id",
				IsPoPTokenEnabled: true,
				PoPTokenClaims:    "invalid-format",
			},
			expectErrorMsg: "unable to parse PoP claims: failed to parse PoP token claims. Ensure the claims are formatted as `key=value` with no extra whitespace",
		},
		{
			name: "missing required u-claim",
			opts: &Options{
				ClientID:          "test-client-id",
				TenantID:          "test-tenant-id",
				IsPoPTokenEnabled: true,
				PoPTokenClaims:    "key=value",
			},
			expectErrorMsg: "unable to parse PoP claims: required u-claim not provided for PoP token flow. Please provide the ARM ID of the cluster in the format `u=<ARM_ID>`",
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			cred, err := newInteractiveBrowserCredentialWithPoP(tc.opts)
			if tc.expectErrorMsg != "" {
				assert.Error(t, err)
				assert.Equal(t, tc.expectErrorMsg, err.Error())
				assert.Nil(t, cred)
			} else {
				assert.NoError(t, err)
				assert.NotNil(t, cred)
				assert.Equal(t, tc.expectName, cred.Name())
			}
		})
	}
}
070701000000AC000041ED000000000000000000000002691F8CFD00000000000000000000000000000000000000000000002F00000000kubelogin-0.2.13/pkg/internal/token/mock_token070701000000AD000081A4000000000000000000000001691F8CFD000006C6000000000000000000000000000000000000004700000000kubelogin-0.2.13/pkg/internal/token/mock_token/execCredentialPlugin.go// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/Azure/kubelogin/pkg/internal/token (interfaces: ExecCredentialPlugin)
//
// Generated by this command:
//
//	mockgen -destination mock_token/execCredentialPlugin.go github.com/Azure/kubelogin/pkg/internal/token ExecCredentialPlugin
//

// Package mock_token is a generated GoMock package.
package mock_token

import (
	context "context"
	reflect "reflect"

	gomock "go.uber.org/mock/gomock"
)

// MockExecCredentialPlugin is a mock of ExecCredentialPlugin interface.
type MockExecCredentialPlugin struct {
	ctrl     *gomock.Controller
	recorder *MockExecCredentialPluginMockRecorder
}

// MockExecCredentialPluginMockRecorder is the mock recorder for MockExecCredentialPlugin.
type MockExecCredentialPluginMockRecorder struct {
	mock *MockExecCredentialPlugin
}

// NewMockExecCredentialPlugin creates a new mock instance.
func NewMockExecCredentialPlugin(ctrl *gomock.Controller) *MockExecCredentialPlugin {
	mock := &MockExecCredentialPlugin{ctrl: ctrl}
	mock.recorder = &MockExecCredentialPluginMockRecorder{mock}
	return mock
}

// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockExecCredentialPlugin) EXPECT() *MockExecCredentialPluginMockRecorder {
	return m.recorder
}

// Do mocks base method.
func (m *MockExecCredentialPlugin) Do(arg0 context.Context) error {
	m.ctrl.T.Helper()
	ret := m.ctrl.Call(m, "Do", arg0)
	ret0, _ := ret[0].(error)
	return ret0
}

// Do indicates an expected call of Do.
func (mr *MockExecCredentialPluginMockRecorder) Do(arg0 any) *gomock.Call {
	mr.mock.ctrl.T.Helper()
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Do", reflect.TypeOf((*MockExecCredentialPlugin)(nil).Do), arg0)
}
070701000000AE000081A4000000000000000000000001691F8CFD00000729000000000000000000000000000000000000004700000000kubelogin-0.2.13/pkg/internal/token/mock_token/execCredentialWriter.go// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/Azure/kubelogin/pkg/internal/token (interfaces: ExecCredentialWriter)
//
// Generated by this command:
//
//	mockgen -destination mock_token/execCredentialWriter.go github.com/Azure/kubelogin/pkg/internal/token ExecCredentialWriter
//

// Package mock_token is a generated GoMock package.
package mock_token

import (
	io "io"
	reflect "reflect"

	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
	gomock "go.uber.org/mock/gomock"
)

// MockExecCredentialWriter is a mock of ExecCredentialWriter interface.
type MockExecCredentialWriter struct {
	ctrl     *gomock.Controller
	recorder *MockExecCredentialWriterMockRecorder
}

// MockExecCredentialWriterMockRecorder is the mock recorder for MockExecCredentialWriter.
type MockExecCredentialWriterMockRecorder struct {
	mock *MockExecCredentialWriter
}

// NewMockExecCredentialWriter creates a new mock instance.
func NewMockExecCredentialWriter(ctrl *gomock.Controller) *MockExecCredentialWriter {
	mock := &MockExecCredentialWriter{ctrl: ctrl}
	mock.recorder = &MockExecCredentialWriterMockRecorder{mock}
	return mock
}

// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockExecCredentialWriter) EXPECT() *MockExecCredentialWriterMockRecorder {
	return m.recorder
}

// Write mocks base method.
func (m *MockExecCredentialWriter) Write(arg0 azcore.AccessToken, arg1 io.Writer) error {
	m.ctrl.T.Helper()
	ret := m.ctrl.Call(m, "Write", arg0, arg1)
	ret0, _ := ret[0].(error)
	return ret0
}

// Write indicates an expected call of Write.
func (mr *MockExecCredentialWriterMockRecorder) Write(arg0, arg1 any) *gomock.Call {
	mr.mock.ctrl.T.Helper()
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Write", reflect.TypeOf((*MockExecCredentialWriter)(nil).Write), arg0, arg1)
}
070701000000AF000081A4000000000000000000000001691F8CFD00000E31000000000000000000000000000000000000003B00000000kubelogin-0.2.13/pkg/internal/token/mock_token/provider.go// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/Azure/kubelogin/pkg/internal/token (interfaces: CredentialProvider)
//
// Generated by this command:
//
//	mockgen -destination mock_token/provider.go github.com/Azure/kubelogin/pkg/internal/token CredentialProvider
//

// Package mock_token is a generated GoMock package.
package mock_token

import (
	context "context"
	reflect "reflect"

	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
	"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
	azidentity "github.com/Azure/azure-sdk-for-go/sdk/azidentity"
	gomock "go.uber.org/mock/gomock"
)

// MockCredentialProvider is a mock of CredentialProvider interface.
type MockCredentialProvider struct {
	ctrl     *gomock.Controller
	recorder *MockCredentialProviderMockRecorder
}

// MockCredentialProviderMockRecorder is the mock recorder for MockCredentialProvider.
type MockCredentialProviderMockRecorder struct {
	mock *MockCredentialProvider
}

// NewMockCredentialProvider creates a new mock instance.
func NewMockCredentialProvider(ctrl *gomock.Controller) *MockCredentialProvider {
	mock := &MockCredentialProvider{ctrl: ctrl}
	mock.recorder = &MockCredentialProviderMockRecorder{mock}
	return mock
}

// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockCredentialProvider) EXPECT() *MockCredentialProviderMockRecorder {
	return m.recorder
}

// Authenticate mocks base method.
func (m *MockCredentialProvider) Authenticate(arg0 context.Context, arg1 *policy.TokenRequestOptions) (azidentity.AuthenticationRecord, error) {
	m.ctrl.T.Helper()
	ret := m.ctrl.Call(m, "Authenticate", arg0, arg1)
	ret0, _ := ret[0].(azidentity.AuthenticationRecord)
	ret1, _ := ret[1].(error)
	return ret0, ret1
}

// Authenticate indicates an expected call of Authenticate.
func (mr *MockCredentialProviderMockRecorder) Authenticate(arg0, arg1 any) *gomock.Call {
	mr.mock.ctrl.T.Helper()
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Authenticate", reflect.TypeOf((*MockCredentialProvider)(nil).Authenticate), arg0, arg1)
}

// GetToken mocks base method.
func (m *MockCredentialProvider) GetToken(arg0 context.Context, arg1 policy.TokenRequestOptions) (azcore.AccessToken, error) {
	m.ctrl.T.Helper()
	ret := m.ctrl.Call(m, "GetToken", arg0, arg1)
	ret0, _ := ret[0].(azcore.AccessToken)
	ret1, _ := ret[1].(error)
	return ret0, ret1
}

// GetToken indicates an expected call of GetToken.
func (mr *MockCredentialProviderMockRecorder) GetToken(arg0, arg1 any) *gomock.Call {
	mr.mock.ctrl.T.Helper()
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetToken", reflect.TypeOf((*MockCredentialProvider)(nil).GetToken), arg0, arg1)
}

// Name mocks base method.
func (m *MockCredentialProvider) Name() string {
	m.ctrl.T.Helper()
	ret := m.ctrl.Call(m, "Name")
	ret0, _ := ret[0].(string)
	return ret0
}

// Name indicates an expected call of Name.
func (mr *MockCredentialProviderMockRecorder) Name() *gomock.Call {
	mr.mock.ctrl.T.Helper()
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockCredentialProvider)(nil).Name))
}

// NeedAuthenticate mocks base method.
func (m *MockCredentialProvider) NeedAuthenticate() bool {
	m.ctrl.T.Helper()
	ret := m.ctrl.Call(m, "NeedAuthenticate")
	ret0, _ := ret[0].(bool)
	return ret0
}

// NeedAuthenticate indicates an expected call of NeedAuthenticate.
func (mr *MockCredentialProviderMockRecorder) NeedAuthenticate() *gomock.Call {
	mr.mock.ctrl.T.Helper()
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NeedAuthenticate", reflect.TypeOf((*MockCredentialProvider)(nil).NeedAuthenticate))
}
070701000000B0000081A4000000000000000000000001691F8CFD00000663000000000000000000000000000000000000003500000000kubelogin-0.2.13/pkg/internal/token/msicredential.gopackage token

import (
	"context"
	"fmt"

	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
	"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
)

type ManagedIdentityCredential struct {
	cred *azidentity.ManagedIdentityCredential
}

var _ CredentialProvider = (*ManagedIdentityCredential)(nil)

func newManagedIdentityCredential(opts *Options) (CredentialProvider, error) {
	var id azidentity.ManagedIDKind
	if opts.ClientID != "" {
		id = azidentity.ClientID(opts.ClientID)
	} else if opts.IdentityResourceID != "" {
		id = azidentity.ResourceID(opts.IdentityResourceID)
	}

	azOpts := &azidentity.ManagedIdentityCredentialOptions{
		ClientOptions: azcore.ClientOptions{Cloud: opts.GetCloudConfiguration()},
		ID:            id,
	}

	if opts.httpClient != nil {
		azOpts.Transport = opts.httpClient
	}

	cred, err := azidentity.NewManagedIdentityCredential(azOpts)
	if err != nil {
		return nil, fmt.Errorf("failed to create managed identity credential: %w", err)
	}
	return &ManagedIdentityCredential{cred: cred}, nil
}

func (c *ManagedIdentityCredential) Name() string {
	return "ManagedIdentityCredential"
}

func (c *ManagedIdentityCredential) Authenticate(ctx context.Context, opts *policy.TokenRequestOptions) (azidentity.AuthenticationRecord, error) {
	return azidentity.AuthenticationRecord{}, errAuthenticateNotSupported
}

func (c *ManagedIdentityCredential) GetToken(ctx context.Context, opts policy.TokenRequestOptions) (azcore.AccessToken, error) {
	return c.cred.GetToken(ctx, opts)
}

func (c *ManagedIdentityCredential) NeedAuthenticate() bool {
	return false
}
070701000000B1000081A4000000000000000000000001691F8CFD000003A1000000000000000000000000000000000000003A00000000kubelogin-0.2.13/pkg/internal/token/msicredential_test.gopackage token

import (
	"context"
	"testing"

	"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
	"github.com/Azure/kubelogin/pkg/internal/testutils"
	"github.com/stretchr/testify/assert"
)

func TestManagedIdentityCredential_GetToken(t *testing.T) {
	rec, err := testutils.GetVCRHttpClient("fixtures/managedidentity_credential", testutils.TestTenantID)
	if err != nil {
		t.Fatalf("failed to create recorder: %v", err)
	}
	defer rec.Stop()

	opts := &Options{
		ClientID:   "49a6a7eb-d4f9-444a-a216-7b966e31bb05",
		ServerID:   testutils.TestServerID,
		httpClient: rec.GetDefaultClient(),
	}
	cred, err := newManagedIdentityCredential(opts)
	if err != nil {
		t.Fatalf("failed to create credential: %v", err)
	}

	token, err := cred.GetToken(context.Background(), policy.TokenRequestOptions{
		Scopes: []string{opts.ServerID + "/.default"},
	})
	assert.NoError(t, err)
	assert.Equal(t, testutils.TestToken, token.Token)
}
070701000000B2000081A4000000000000000000000001691F8CFD0000412B000000000000000000000000000000000000002F00000000kubelogin-0.2.13/pkg/internal/token/options.gopackage token

import (
	"fmt"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud"
	"github.com/spf13/cobra"
	"github.com/spf13/pflag"
	"k8s.io/client-go/util/homedir"

	"github.com/Azure/kubelogin/pkg/internal/env"
	"github.com/Azure/kubelogin/pkg/internal/pop"
	popcache "github.com/Azure/kubelogin/pkg/internal/pop/cache"
)

// PoPKeyProvider provides PoP keys based on the configured cache policy
type PoPKeyProvider interface {
	GetPoPKey() (*pop.SwKey, error)
}

type Options struct {
	LoginMethod                       string
	ClientID                          string
	ClientSecret                      string
	ClientCert                        string
	ClientCertPassword                string
	Username                          string
	Password                          string
	ServerID                          string
	TenantID                          string
	Environment                       string
	IsLegacy                          bool
	Timeout                           time.Duration
	AuthRecordCacheDir                string
	authRecordCacheFile               string
	IdentityResourceID                string
	FederatedTokenFile                string
	AuthorityHost                     string
	UseAzureRMTerraformEnv            bool
	IsPoPTokenEnabled                 bool
	PoPTokenClaims                    string
	DisableEnvironmentOverride        bool
	UsePersistentCache                bool
	DisableInstanceDiscovery          bool
	httpClient                        *http.Client
	RedirectURL                       string
	LoginHint                         string
	AzurePipelinesServiceConnectionID string
	// Private field to store the PoP token cache, set during initialization. Stores MSAL tokens for token caching
	popTokenCache *popcache.Cache
}

const (
	defaultEnvironmentName = "AzurePublicCloud"

	DeviceCodeLogin        = "devicecode"
	InteractiveLogin       = "interactive"
	ServicePrincipalLogin  = "spn"
	ROPCLogin              = "ropc"
	MSILogin               = "msi"
	AzureCLILogin          = "azurecli"
	AzureDeveloperCLILogin = "azd"
	WorkloadIdentityLogin  = "workloadidentity"
	AzurePipelinesLogin    = "azurepipelines"
)

var (
	supportedLogin            []string
	DefaultAuthRecordCacheDir = homedir.HomeDir() + "/.kube/cache/kubelogin/"
)

func init() {
	supportedLogin = []string{DeviceCodeLogin, InteractiveLogin, ServicePrincipalLogin, ROPCLogin, MSILogin, AzureCLILogin, AzureDeveloperCLILogin, WorkloadIdentityLogin, AzurePipelinesLogin}
}

func GetSupportedLogins() string {
	return strings.Join(supportedLogin, ", ")
}

func NewOptions(usePersistentCache bool) Options {
	envAuthRecordCacheDir := os.Getenv("KUBECACHEDIR")
	return Options{
		LoginMethod: DeviceCodeLogin,
		Environment: defaultEnvironmentName,
		AuthRecordCacheDir: func() string {
			if envAuthRecordCacheDir != "" {
				return envAuthRecordCacheDir
			}
			return DefaultAuthRecordCacheDir
		}(),
		UsePersistentCache: usePersistentCache,
	}
}

func (o *Options) AddFlags(fs *pflag.FlagSet) {
	fs.StringVarP(&o.LoginMethod, "login", "l", o.LoginMethod,
		fmt.Sprintf("Login method. Supported methods: %s. It may be specified in %s environment variable", GetSupportedLogins(), env.LoginMethod))
	fs.StringVar(&o.ClientID, "client-id", o.ClientID,
		fmt.Sprintf("AAD client application ID. It may be specified in %s or %s environment variable. For Azure Pipelines login, it may be specified in %s environment variable", env.KubeloginClientID, env.AzureClientID, env.AzureSubscriptionClientID))
	fs.StringVar(&o.ClientSecret, "client-secret", o.ClientSecret,
		fmt.Sprintf("AAD client application secret. Used in spn login. It may be specified in %s or %s environment variable", env.KubeloginClientSecret, env.AzureClientSecret))
	fs.StringVar(&o.ClientCert, "client-certificate", o.ClientCert,
		fmt.Sprintf("AAD client cert in pfx or PEM. Used in spn login. It may be specified in %s or %s environment variable", env.KubeloginClientCertificatePath, env.AzureClientCertificatePath))
	fs.StringVar(&o.ClientCertPassword, "client-certificate-password", o.ClientCertPassword,
		fmt.Sprintf("Password for AAD client cert. Used in spn login. It may be specified in %s or %s environment variable. Only used for PFX encoded certs.", env.KubeloginClientCertificatePassword, env.AzureClientCertificatePassword))
	fs.StringVar(&o.Username, "username", o.Username,
		fmt.Sprintf("user name for ropc login flow. It may be specified in %s or %s environment variable", env.KubeloginROPCUsername, env.AzureUsername))
	fs.StringVar(&o.Password, "password", o.Password,
		fmt.Sprintf("password for ropc login flow. It may be specified in %s or %s environment variable", env.KubeloginROPCPassword, env.AzurePassword))
	fs.StringVar(&o.IdentityResourceID, "identity-resource-id", o.IdentityResourceID, "Managed Identity resource id.")
	fs.StringVar(&o.ServerID, "server-id", o.ServerID, "AAD server application ID")
	fs.StringVar(&o.FederatedTokenFile, "federated-token-file", o.FederatedTokenFile,
		fmt.Sprintf("Workload Identity federated token file. It may be specified in %s environment variable", env.AzureFederatedTokenFile))
	fs.StringVar(&o.AuthorityHost, "authority-host", o.AuthorityHost,
		fmt.Sprintf("Workload Identity authority host. It may be specified in %s environment variable", env.AzureAuthorityHost))
	fs.StringVar(&o.AzurePipelinesServiceConnectionID, "azure-pipelines-service-connection-id", o.AzurePipelinesServiceConnectionID,
		fmt.Sprintf("Service connection (resource) ID used by azurepipelines login method. It may be specified in %s environment variable", env.AzureSubscriptionServiceConnectionID))
	fs.StringVar(&o.AuthRecordCacheDir, "token-cache-dir", o.AuthRecordCacheDir, "directory to cache authentication record")
	_ = fs.MarkDeprecated("token-cache-dir", "use --cache-dir instead")
	fs.StringVar(&o.AuthRecordCacheDir, "cache-dir", o.AuthRecordCacheDir, "directory to cache authentication record")
	fs.StringVarP(&o.TenantID, "tenant-id", "t", o.TenantID, fmt.Sprintf("AAD tenant ID. It may be specified in %s environment variable. For Azure Pipelines login, it may be specified in %s environment variable", env.AzureTenantID, env.AzureSubscriptionTenantID))
	fs.StringVarP(&o.Environment, "environment", "e", o.Environment, "Azure environment name")
	fs.BoolVar(&o.IsLegacy, "legacy", o.IsLegacy, "set to true to get token with 'spn:' prefix in audience claim")
	fs.BoolVar(&o.UseAzureRMTerraformEnv, "use-azurerm-env-vars", o.UseAzureRMTerraformEnv,
		"Use environment variable names of Terraform Azure Provider (ARM_CLIENT_ID, ARM_CLIENT_SECRET, ARM_CLIENT_CERTIFICATE_PATH, ARM_CLIENT_CERTIFICATE_PASSWORD, ARM_TENANT_ID)")
	fs.BoolVar(&o.IsPoPTokenEnabled, "pop-enabled", o.IsPoPTokenEnabled, "set to true to use a PoP token for authentication or false to use a regular bearer token")
	fs.DurationVar(&o.Timeout, "timeout", 60*time.Second,
		fmt.Sprintf("Timeout duration for Azure CLI token requests. It may be specified in %s environment variable", "AZURE_CLI_TIMEOUT"))
	fs.StringVar(&o.PoPTokenClaims, "pop-claims", o.PoPTokenClaims, "contains a comma-separated list of claims to attach to the pop token in the format `key=val,key2=val2`. At minimum, specify the ARM ID of the cluster as `u=ARM_ID`")
	fs.BoolVar(&o.DisableEnvironmentOverride, "disable-environment-override", o.DisableEnvironmentOverride, "Enable or disable the use of env-variables. Default false")
	fs.BoolVar(&o.DisableInstanceDiscovery, "disable-instance-discovery", o.DisableInstanceDiscovery, "set to true to disable instance discovery in environments with their own simple Identity Provider (not AAD) that do not have instance metadata discovery endpoint. Default false")
	fs.StringVar(&o.RedirectURL, "redirect-url", o.RedirectURL, "The URL Microsoft Entra ID will redirect to with the access token. This is only used for interactive login. This is an optional parameter.")
	fs.StringVar(&o.LoginHint, "login-hint", o.LoginHint, "The login hint to pre-fill the username in the interactive login flow.")
}

func (o *Options) Validate() error {
	foundValidLoginMethod := false
	for _, v := range supportedLogin {
		if o.LoginMethod == v {
			foundValidLoginMethod = true
		}
	}

	if !foundValidLoginMethod {
		return fmt.Errorf("'%s' is not a supported login method. Supported method is one of %s", o.LoginMethod, GetSupportedLogins())
	}

	if o.AuthorityHost != "" {
		u, err := url.ParseRequestURI(o.AuthorityHost)
		if err != nil {
			return fmt.Errorf("authority host %q is not valid: %s", o.AuthorityHost, err)
		}
		if u.Scheme == "" || u.Host == "" {
			return fmt.Errorf("authority host %q is not valid", o.AuthorityHost)
		}
		if !strings.HasSuffix(o.AuthorityHost, "/") {
			return fmt.Errorf("authority host %q should have a trailing slash", o.AuthorityHost)
		}
	}

	// both of the following checks ensure that --pop-enabled and --pop-claims flags are provided together
	if o.IsPoPTokenEnabled && o.PoPTokenClaims == "" {
		return fmt.Errorf("if enabling pop token mode, please provide the pop-claims flag containing the PoP token claims as a comma-separated string: `u=popClaimHost,key1=val1`")
	}

	if o.PoPTokenClaims != "" && !o.IsPoPTokenEnabled {
		return fmt.Errorf("pop-enabled flag is required to use the PoP token feature. Please provide both pop-enabled and pop-claims flags")
	}

	if o.Timeout <= 0 {
		return fmt.Errorf("timeout must be greater than 0")
	}

	return nil
}

func (o *Options) UpdateFromEnv() {
	o.authRecordCacheFile = getAuthenticationRecordFileName(o)

	if o.DisableEnvironmentOverride {
		return
	}

	if o.UseAzureRMTerraformEnv {
		if v, ok := os.LookupEnv(env.TerraformClientID); ok {
			o.ClientID = v
		}
		if v, ok := os.LookupEnv(env.TerraformClientSecret); ok {
			o.ClientSecret = v
		}
		if v, ok := os.LookupEnv(env.TerraformClientCertificatePath); ok {
			o.ClientCert = v
		}
		if v, ok := os.LookupEnv(env.TerraformClientCertificatePassword); ok {
			o.ClientCertPassword = v
		}
		if v, ok := os.LookupEnv(env.TerraformTenantID); ok {
			o.TenantID = v
		}
	} else {
		if v, ok := os.LookupEnv(env.KubeloginClientID); ok {
			o.ClientID = v
		}
		if v, ok := os.LookupEnv(env.AzureClientID); ok {
			o.ClientID = v
		}
		if v, ok := os.LookupEnv(env.KubeloginClientSecret); ok {
			o.ClientSecret = v
		}
		if v, ok := os.LookupEnv(env.AzureClientSecret); ok {
			o.ClientSecret = v
		}
		if v, ok := os.LookupEnv(env.KubeloginClientCertificatePath); ok {
			o.ClientCert = v
		}
		if v, ok := os.LookupEnv(env.AzureClientCertificatePath); ok {
			o.ClientCert = v
		}
		if v, ok := os.LookupEnv(env.KubeloginClientCertificatePassword); ok {
			o.ClientCertPassword = v
		}
		if v, ok := os.LookupEnv(env.AzureClientCertificatePassword); ok {
			o.ClientCertPassword = v
		}
		if v, ok := os.LookupEnv(env.AzureTenantID); ok {
			o.TenantID = v
		}
	}

	if v, ok := os.LookupEnv(env.KubeloginROPCUsername); ok {
		o.Username = v
	}
	if v, ok := os.LookupEnv(env.AzureUsername); ok {
		o.Username = v
	}
	if v, ok := os.LookupEnv(env.KubeloginROPCPassword); ok {
		o.Password = v
	}
	if v, ok := os.LookupEnv(env.AzurePassword); ok {
		o.Password = v
	}
	if v, ok := os.LookupEnv(env.LoginMethod); ok {
		o.LoginMethod = v
	}

	if o.LoginMethod == WorkloadIdentityLogin {
		if v, ok := os.LookupEnv(env.AzureClientID); ok {
			o.ClientID = v
		}
		if v, ok := os.LookupEnv(env.AzureFederatedTokenFile); ok {
			o.FederatedTokenFile = v
		}
		if v, ok := os.LookupEnv(env.AzureAuthorityHost); ok {
			o.AuthorityHost = v
		}
	}

	if o.LoginMethod == AzurePipelinesLogin {
		if o.ClientID == "" {
			if v, ok := os.LookupEnv(env.AzureSubscriptionClientID); ok {
				o.ClientID = v
			}
		}
		if o.TenantID == "" {
			if v, ok := os.LookupEnv(env.AzureSubscriptionTenantID); ok {
				o.TenantID = v
			}
		}
		if o.AzurePipelinesServiceConnectionID == "" {
			if v, ok := os.LookupEnv(env.AzureSubscriptionServiceConnectionID); ok {
				o.AzurePipelinesServiceConnectionID = v
			}
		}
	}

	if v, ok := os.LookupEnv("AZURE_CLI_TIMEOUT"); ok {
		if timeout, err := time.ParseDuration(v); err == nil {
			o.Timeout = timeout
		}
	}
}

func (o *Options) GetCloudConfiguration() cloud.Configuration {
	if o.AuthorityHost != "" {
		return cloud.Configuration{
			ActiveDirectoryAuthorityHost: o.AuthorityHost,
		}
	}

	switch strings.ToUpper(o.Environment) {
	case "AZURECLOUD":
		fallthrough
	case "AZUREPUBLIC":
		fallthrough
	case "AZUREPUBLICCLOUD":
		return cloud.AzurePublic
	case "AZUREUSGOVERNMENT":
		fallthrough
	case "AZUREUSGOVERNMENTCLOUD":
		return cloud.AzureGovernment
	case "AZURECHINACLOUD":
		return cloud.AzureChina
	}
	return cloud.AzurePublic
}

func (o *Options) ToString() string {
	azureConfigDir := os.Getenv("AZURE_CONFIG_DIR")

	parts := []string{
		fmt.Sprintf("Login Method: %s", o.LoginMethod),
		fmt.Sprintf("Environment: %s", o.Environment),
		fmt.Sprintf("TenantID: %s", o.TenantID),
		fmt.Sprintf("ServerID: %s", o.ServerID),
		fmt.Sprintf("ClientID: %s", o.ClientID),
		fmt.Sprintf("IsLegacy: %t", o.IsLegacy),
		fmt.Sprintf("msiResourceID: %s", o.IdentityResourceID),
		fmt.Sprintf("Timeout: %v", o.Timeout),
		fmt.Sprintf("authRecordCacheDir: %s", o.AuthRecordCacheDir),
		fmt.Sprintf("tokenauthRecordFile: %s", o.authRecordCacheFile),
		fmt.Sprintf("AZURE_CONFIG_DIR: %s", azureConfigDir),
		fmt.Sprintf("RedirectURL: %s", o.RedirectURL),
		fmt.Sprintf("LoginHint: %s", o.LoginHint),
	}

	return strings.Join(parts, ", ")
}

func getAuthenticationRecordFileName(o *Options) string {
	return filepath.Join(o.AuthRecordCacheDir, "auth.json")
}

// parsePoPClaims parses the pop token claims. Pop token claims are passed in as a
// comma-separated string in the format "key1=val1,key2=val2"
func parsePoPClaims(popClaims string) (map[string]string, error) {
	if strings.TrimSpace(popClaims) == "" {
		return nil, fmt.Errorf("failed to parse PoP token claims: no claims provided")
	}
	claimsArray := strings.Split(popClaims, ",")
	claimsMap := make(map[string]string)
	for _, claim := range claimsArray {
		claimPair := strings.Split(claim, "=")
		if len(claimPair) < 2 {
			return nil, fmt.Errorf("failed to parse PoP token claims. Ensure the claims are formatted as `key=value` with no extra whitespace")
		}
		key := strings.TrimSpace(claimPair[0])
		val := strings.TrimSpace(claimPair[1])
		if key == "" || val == "" {
			return nil, fmt.Errorf("failed to parse PoP token claims. Ensure the claims are formatted as `key=value` with no extra whitespace")
		}
		claimsMap[key] = val
	}
	if claimsMap["u"] == "" {
		return nil, fmt.Errorf("required u-claim not provided for PoP token flow. Please provide the ARM ID of the cluster in the format `u=<ARM_ID>`")
	}
	return claimsMap, nil
}

func (o *Options) AddCompletions(cmd *cobra.Command) {
	_ = cmd.RegisterFlagCompletionFunc("login", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
		return supportedLogin, cobra.ShellCompDirectiveNoFileComp
	})
	_ = cmd.MarkFlagFilename("client-certificate", "pfx", "cert")
	_ = cmd.MarkFlagFilename("federated-token-file", "")
	_ = cmd.MarkFlagDirname("token-cache-dir")

	cmd.Flags().VisitAll(func(flag *pflag.Flag) {
		// Set a default completion function if none was set. We don't look
		// up if it does already have one set, because Cobra does this for
		// us, and returns an error (which we ignore for this reason).
		_ = cmd.RegisterFlagCompletionFunc(flag.Name, cobra.NoFileCompletions)
	})
}

// GetPoPTokenCache returns the PoP token cache if available.
// Returns nil if PoP is disabled or cache creation failed (e.g., container environments).
func (o *Options) GetPoPTokenCache() *popcache.Cache {
	return o.popTokenCache
}

// SetPoPTokenCache sets the PoP token cache. This is used internally during initialization.
func (o *Options) setPoPTokenCache(cache *popcache.Cache) {
	o.popTokenCache = cache
}

// GetPoPKeyProvider returns a PoPKeyProvider based on the current cache configuration.
// This centralizes the key provider logic.
func (o *Options) GetPoPKeyProvider() PoPKeyProvider {
	return &defaultPoPKeyProvider{
		cacheDir: o.getCacheDir(),
	}
}

// getCacheDir returns the cache directory path if caching is enabled, empty string otherwise
func (o *Options) getCacheDir() string {
	if o.popTokenCache != nil {
		return o.AuthRecordCacheDir
	}
	return ""
}

// defaultPoPKeyProvider is the default implementation of PoPKeyProvider
type defaultPoPKeyProvider struct {
	cacheDir string
}

// GetPoPKey implements PoPKeyProvider interface
func (p *defaultPoPKeyProvider) GetPoPKey() (*pop.SwKey, error) {
	return pop.GetPoPKeyByPolicy(p.cacheDir)
}
070701000000B3000081A4000000000000000000000001691F8CFD00005B5F000000000000000000000000000000000000003400000000kubelogin-0.2.13/pkg/internal/token/options_test.gopackage token

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"testing"
	"time"

	"github.com/Azure/kubelogin/pkg/internal/env"
	"github.com/Azure/kubelogin/pkg/internal/testutils"
	"github.com/google/go-cmp/cmp"
	"github.com/spf13/pflag"
)

func TestOptions(t *testing.T) {
	t.Run("Default option should produce token cache file under default token cache directory", func(t *testing.T) {
		o := defaultOptions()
		o.AddFlags(&pflag.FlagSet{})
		o.UpdateFromEnv()
		if err := o.Validate(); err != nil {
			t.Fatalf("option validation failed: %s", err)
		}
		dir, _ := filepath.Split(o.authRecordCacheFile)
		if dir != DefaultAuthRecordCacheDir {
			t.Fatalf("token cache directory is expected to be %s, got %s", DefaultAuthRecordCacheDir, dir)
		}
	})

	t.Run("option with customized token cache dir should produce token cache file under specified token cache directory", func(t *testing.T) {
		o := defaultOptions()
		o.AuthRecordCacheDir = "/tmp/foo/"
		o.AddFlags(&pflag.FlagSet{})
		o.UpdateFromEnv()
		if err := o.Validate(); err != nil {
			t.Fatalf("option validation failed: %s", err)
		}
		dir, _ := filepath.Split(o.authRecordCacheFile)
		if dir != o.AuthRecordCacheDir {
			t.Fatalf("token cache directory is expected to be %s, got %s", o.AuthRecordCacheDir, dir)
		}
	})

	t.Run("invalid login method should return error", func(t *testing.T) {
		o := defaultOptions()
		o.LoginMethod = "unsupported"
		if err := o.Validate(); err == nil || !strings.Contains(err.Error(), "is not a supported login method") {
			t.Fatalf("unsupported login method should return unsupported error. got: %s", err)
		}
	})

	t.Run("pop-enabled flag should return error if pop-claims are not provided", func(t *testing.T) {
		o := defaultOptions()
		o.IsPoPTokenEnabled = true
		if err := o.Validate(); err == nil || !strings.Contains(err.Error(), "please provide the pop-claims flag") {
			t.Fatalf("pop-enabled with no pop claims should return missing pop-claims error. got: %s", err)
		}
	})

	t.Run("pop-claims flag should return error if pop-enabled is not provided", func(t *testing.T) {
		o := defaultOptions()
		o.PoPTokenClaims = "u=testhost"
		if err := o.Validate(); err == nil || !strings.Contains(err.Error(), "pop-enabled flag is required to use the PoP token feature") {
			t.Fatalf("pop-claims provided with no pop-enabled flag should return missing pop-enabled error. got: %s", err)
		}
	})

	t.Run("invalid authority host should return error", func(t *testing.T) {
		o := defaultOptions()
		o.AuthorityHost = "invalid"
		if err := o.Validate(); err == nil || !strings.Contains(err.Error(), `authority host "`+o.AuthorityHost+`" is not valid`) {
			t.Fatalf("invalid authority host should return invalid authority host error. got: %s", err)
		}
	})

	t.Run("setting authority host will set cloud.Configuration properly", func(t *testing.T) {
		o := defaultOptions()
		o.AuthorityHost = "https://login.example.com/"
		if err := o.Validate(); err != nil {
			t.Fatalf("setting authority host should not return error. got: %s", err)
		}
		if o.GetCloudConfiguration().ActiveDirectoryAuthorityHost != o.AuthorityHost {
			t.Fatalf("expected authority host to be %s, got %s",
				o.AuthorityHost, o.GetCloudConfiguration().ActiveDirectoryAuthorityHost)
		}
	})

	t.Run("default cloud.Configuration should be public azure", func(t *testing.T) {
		o := defaultOptions()
		if err := o.Validate(); err != nil {
			t.Fatalf("setting authority host should not return error. got: %s", err)
		}
		defaultAuthorityHost := "https://login.microsoftonline.com/"
		if o.GetCloudConfiguration().ActiveDirectoryAuthorityHost != defaultAuthorityHost {
			t.Fatalf("expected authority host to be %s, got %s",
				defaultAuthorityHost, o.GetCloudConfiguration().ActiveDirectoryAuthorityHost)
		}
	})

	t.Run("invalid timeout should return error", func(t *testing.T) {
		o := defaultOptions()
		o.Timeout = 0
		if err := o.Validate(); err == nil || !strings.Contains(err.Error(), "timeout must be greater than 0") {
			t.Fatalf("timeout of 0 should return error. got: %s", err)
		}
	})

	t.Run("valid PoP token claims should pass validation", func(t *testing.T) {
		o := defaultOptions()
		o.IsPoPTokenEnabled = true
		o.PoPTokenClaims = "u=testhost"
		if err := o.Validate(); err != nil {
			t.Fatalf("valid PoP token claims should not return error. got: %s", err)
		}
	})

	t.Run("azurepipelines login method validation", func(t *testing.T) {
		tests := []struct {
			name           string
			setupEnv       func()
			options        func() Options
			expectError    bool
			errorSubstring string
		}{
			{
				name: "valid azurepipelines login with all parameters",
				setupEnv: func() {
					t.Setenv(env.SystemAccessToken, "test-token")
					t.Setenv(env.SystemOIDCRequestURI, "https://test.oidc.request.uri")
				},
				options: func() Options {
					o := defaultOptions()
					o.LoginMethod = AzurePipelinesLogin
					o.TenantID = "test-tenant"
					o.ClientID = "test-client"
					o.AzurePipelinesServiceConnectionID = "test-service-connection"
					return o
				},
				expectError: false,
			},
			{
				name: "azurepipelines login without tenant ID is valid (can come from env)",
				setupEnv: func() {
					t.Setenv(env.SystemAccessToken, "test-token")
					t.Setenv(env.SystemOIDCRequestURI, "https://test.oidc.request.uri")
					t.Setenv(env.AzureSubscriptionTenantID, "env-tenant-id")
				},
				options: func() Options {
					o := defaultOptions()
					o.LoginMethod = AzurePipelinesLogin
					o.ClientID = "test-client"
					o.AzurePipelinesServiceConnectionID = "test-service-connection"
					return o
				},
				expectError: false,
			},
			{
				name: "azurepipelines login without service connection ID is valid (can come from env)",
				setupEnv: func() {
					t.Setenv(env.SystemAccessToken, "test-token")
					t.Setenv(env.SystemOIDCRequestURI, "https://test.oidc.request.uri")
					t.Setenv(env.AzureSubscriptionServiceConnectionID, "env-service-connection")
				},
				options: func() Options {
					o := defaultOptions()
					o.LoginMethod = AzurePipelinesLogin
					o.TenantID = "test-tenant"
					o.ClientID = "test-client"
					return o
				},
				expectError: false,
			},
		}

		for _, test := range tests {
			t.Run(test.name, func(t *testing.T) {
				// Clean up environment variables before each test
				originalSystemAccessToken := os.Getenv(env.SystemAccessToken)
				originalSystemOIDCRequestURI := os.Getenv(env.SystemOIDCRequestURI)
				originalTenantID := os.Getenv(env.AzureSubscriptionTenantID)
				originalServiceConnectionID := os.Getenv(env.AzureSubscriptionServiceConnectionID)
				defer func() {
					if originalSystemAccessToken != "" {
						os.Setenv(env.SystemAccessToken, originalSystemAccessToken)
					} else {
						os.Unsetenv(env.SystemAccessToken)
					}
					if originalSystemOIDCRequestURI != "" {
						os.Setenv(env.SystemOIDCRequestURI, originalSystemOIDCRequestURI)
					} else {
						os.Unsetenv(env.SystemOIDCRequestURI)
					}
					if originalTenantID != "" {
						os.Setenv(env.AzureSubscriptionTenantID, originalTenantID)
					} else {
						os.Unsetenv(env.AzureSubscriptionTenantID)
					}
					if originalServiceConnectionID != "" {
						os.Setenv(env.AzureSubscriptionServiceConnectionID, originalServiceConnectionID)
					} else {
						os.Unsetenv(env.AzureSubscriptionServiceConnectionID)
					}
				}()

				test.setupEnv()
				o := test.options()
				err := o.Validate()

				if test.expectError {
					if err == nil {
						t.Fatalf("expected error but got none")
					}
					if !strings.Contains(err.Error(), test.errorSubstring) {
						t.Fatalf("expected error to contain '%s', got: %s", test.errorSubstring, err.Error())
					}
				} else {
					if err != nil {
						t.Fatalf("expected no error but got: %s", err)
					}
				}
			})
		}
	})

}

func defaultOptions() Options {
	o := NewOptions(true)
	o.Timeout = 30 * time.Second
	return o
}

func TestOptionsWithEnvVars(t *testing.T) {
	const (
		clientID      = "clientID"
		clientSecret  = "clientSecret"
		certPath      = "certPath"
		certPassword  = "password"
		username      = "username"
		password      = "password"
		tenantID      = "tenantID"
		tokenFile     = "tokenFile"
		authorityHost = "authorityHost"
	)
	testCases := []struct {
		name        string
		envVarMap   map[string]string
		isTerraform bool
		expected    Options
	}{
		{
			name: "setting env var using legacy env var format",
			envVarMap: map[string]string{
				env.KubeloginClientID:                  clientID,
				env.KubeloginClientSecret:              clientSecret,
				env.KubeloginClientCertificatePath:     certPath,
				env.KubeloginClientCertificatePassword: certPassword,
				env.KubeloginROPCUsername:              username,
				env.KubeloginROPCPassword:              password,
				env.AzureTenantID:                      tenantID,
				env.LoginMethod:                        DeviceCodeLogin,
			},
			expected: Options{
				ClientID:            clientID,
				ClientSecret:        clientSecret,
				ClientCert:          certPath,
				ClientCertPassword:  certPassword,
				Username:            username,
				Password:            password,
				TenantID:            tenantID,
				LoginMethod:         DeviceCodeLogin,
				authRecordCacheFile: "auth.json",
				Timeout:             60 * time.Second,
			},
		},
		{
			name:        "setting env var using terraform env var format",
			isTerraform: true,
			envVarMap: map[string]string{
				env.TerraformClientID:                  clientID,
				env.TerraformClientSecret:              clientSecret,
				env.TerraformClientCertificatePath:     certPath,
				env.TerraformClientCertificatePassword: certPassword,
				env.TerraformTenantID:                  tenantID,
				env.LoginMethod:                        DeviceCodeLogin,
			},
			expected: Options{
				UseAzureRMTerraformEnv: true,
				ClientID:               clientID,
				ClientSecret:           clientSecret,
				ClientCert:             certPath,
				ClientCertPassword:     certPassword,
				TenantID:               tenantID,
				LoginMethod:            DeviceCodeLogin,
				authRecordCacheFile:    "auth.json",
				Timeout:                60 * time.Second,
			},
		},
		{
			name: "setting env var using azure sdk env var format",
			envVarMap: map[string]string{
				env.AzureClientID:                  clientID,
				env.AzureClientSecret:              clientSecret,
				env.AzureClientCertificatePath:     certPath,
				env.AzureClientCertificatePassword: certPassword,
				env.AzureUsername:                  username,
				env.AzurePassword:                  password,
				env.AzureTenantID:                  tenantID,
				env.LoginMethod:                    WorkloadIdentityLogin,
				env.AzureFederatedTokenFile:        tokenFile,
				env.AzureAuthorityHost:             authorityHost,
			},
			expected: Options{
				ClientID:            clientID,
				ClientSecret:        clientSecret,
				ClientCert:          certPath,
				ClientCertPassword:  certPassword,
				Username:            username,
				Password:            password,
				TenantID:            tenantID,
				LoginMethod:         WorkloadIdentityLogin,
				AuthorityHost:       authorityHost,
				FederatedTokenFile:  tokenFile,
				authRecordCacheFile: "auth.json",
				Timeout:             60 * time.Second,
			},
		},
	}
	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			for k, v := range tc.envVarMap {
				t.Setenv(k, v)
			}
			o := Options{}
			if tc.isTerraform {
				o.UseAzureRMTerraformEnv = true
			}
			o.AddFlags(&pflag.FlagSet{})
			o.UpdateFromEnv()
			if !cmp.Equal(o, tc.expected, cmp.AllowUnexported(Options{})) {
				t.Fatalf("expected option: %+v, got %+v", tc.expected, o)
			}
		})
	}
}

func TestParsePoPClaims(t *testing.T) {
	testCases := []struct {
		name           string
		popClaims      string
		expectedError  error
		expectedClaims map[string]string
	}{
		{
			name:           "pop-claim parsing should fail on empty string",
			popClaims:      "",
			expectedError:  fmt.Errorf("failed to parse PoP token claims: no claims provided"),
			expectedClaims: nil,
		},
		{
			name:           "pop-claim parsing should fail on whitespace-only string",
			popClaims:      "	    ",
			expectedError:  fmt.Errorf("failed to parse PoP token claims: no claims provided"),
			expectedClaims: nil,
		},
		{
			name:           "pop-claim parsing should fail if claims are not provided in key=value format",
			popClaims:      "claim1=val1,claim2",
			expectedError:  fmt.Errorf("failed to parse PoP token claims. Ensure the claims are formatted as `key=value` with no extra whitespace"),
			expectedClaims: nil,
		},
		{
			name:           "pop-claim parsing should fail if claims are malformed",
			popClaims:      "claim1=  ",
			expectedError:  fmt.Errorf("failed to parse PoP token claims. Ensure the claims are formatted as `key=value` with no extra whitespace"),
			expectedClaims: nil,
		},
		{
			name:           "pop-claim parsing should fail if claims are malformed/commas only",
			popClaims:      ",,,,,,,,",
			expectedError:  fmt.Errorf("failed to parse PoP token claims. Ensure the claims are formatted as `key=value` with no extra whitespace"),
			expectedClaims: nil,
		},
		{
			name:           "pop-claim parsing should fail if u-claim is not provided",
			popClaims:      "1=2,3=4",
			expectedError:  fmt.Errorf("required u-claim not provided for PoP token flow. Please provide the ARM ID of the cluster in the format `u=<ARM_ID>`"),
			expectedClaims: nil,
		},
		{
			name:          "pop-claim parsing should succeed with u-claim and additional claims",
			popClaims:     "u=val1, claim2=val2, claim3=val3",
			expectedError: nil,
			expectedClaims: map[string]string{
				"u":      "val1",
				"claim2": "val2",
				"claim3": "val3",
			},
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			claimsMap, err := parsePoPClaims(tc.popClaims)
			if err != nil {
				if !testutils.ErrorContains(err, tc.expectedError.Error()) {
					t.Fatalf("expected error: %+v, got error: %+v", tc.expectedError, err)
				}
			} else {
				if err != tc.expectedError {
					t.Fatalf("expected error: %+v, got error: %+v", tc.expectedError, err)
				}
			}
			if !cmp.Equal(claimsMap, tc.expectedClaims) {
				t.Fatalf("expected claims map to be %s, got map: %s", tc.expectedClaims, claimsMap)
			}
		})
	}
}

func TestDisableEnvironmentOverride(t *testing.T) {
	t.Run("TestDisableEnvironmentOverride", func(t *testing.T) {
		t.Setenv(env.KubeloginClientID, "client-id from env")
		o := Options{ClientID: "client-id from options"}
		o.DisableEnvironmentOverride = true
		o.UpdateFromEnv()
		if o.ClientID != "client-id from options" {
			t.Fatalf("expected client-id to be 'client-id from options', got %s", o.ClientID)
		}
	})

	t.Run("TestEnableEnvironmentOverride", func(t *testing.T) {
		t.Setenv(env.KubeloginClientID, "client-id from env")
		o := Options{ClientID: "client-id from options"}
		o.DisableEnvironmentOverride = false
		o.UpdateFromEnv()
		if o.ClientID != "client-id from env" {
			t.Fatalf("expected client-id to be 'client-id from env', got %s", o.ClientID)
		}
	})
}

func TestAzurePipelinesEnvironmentVariables(t *testing.T) {
	const (
		testClientID            = "test-client-id"
		testTenantID            = "test-tenant-id"
		testServiceConnectionID = "test-service-connection-id"
	)

	t.Run("Azure Pipelines environment variables are read when LoginMethod is AzurePipelinesLogin", func(t *testing.T) {
		t.Setenv(env.AzureSubscriptionClientID, testClientID)
		t.Setenv(env.AzureSubscriptionTenantID, testTenantID)
		t.Setenv(env.AzureSubscriptionServiceConnectionID, testServiceConnectionID)

		o := Options{LoginMethod: AzurePipelinesLogin}
		o.UpdateFromEnv()

		if o.ClientID != testClientID {
			t.Fatalf("expected ClientID to be '%s', got '%s'", testClientID, o.ClientID)
		}
		if o.TenantID != testTenantID {
			t.Fatalf("expected TenantID to be '%s', got '%s'", testTenantID, o.TenantID)
		}
		if o.AzurePipelinesServiceConnectionID != testServiceConnectionID {
			t.Fatalf("expected AzurePipelinesServiceConnectionID to be '%s', got '%s'", testServiceConnectionID, o.AzurePipelinesServiceConnectionID)
		}
	})

	t.Run("Azure Pipelines environment variables are not read for other login methods", func(t *testing.T) {
		t.Setenv(env.AzureSubscriptionClientID, testClientID)
		t.Setenv(env.AzureSubscriptionTenantID, testTenantID)
		t.Setenv(env.AzureSubscriptionServiceConnectionID, testServiceConnectionID)

		o := Options{LoginMethod: DeviceCodeLogin}
		o.UpdateFromEnv()

		if o.ClientID == testClientID {
			t.Fatalf("Azure Pipelines ClientID should not be read for non-AzurePipelines login method")
		}
		if o.TenantID == testTenantID {
			t.Fatalf("Azure Pipelines TenantID should not be read for non-AzurePipelines login method")
		}
		if o.AzurePipelinesServiceConnectionID == testServiceConnectionID {
			t.Fatalf("Azure Pipelines ServiceConnectionID should not be read for non-AzurePipelines login method")
		}
	})

	t.Run("Command-line flags take precedence over Azure Pipelines environment variables", func(t *testing.T) {
		t.Setenv(env.AzureSubscriptionClientID, "env-client-id")
		t.Setenv(env.AzureSubscriptionTenantID, "env-tenant-id")
		t.Setenv(env.AzureSubscriptionServiceConnectionID, "env-service-connection-id")

		o := Options{
			LoginMethod:                       AzurePipelinesLogin,
			ClientID:                          "flag-client-id",
			TenantID:                          "flag-tenant-id",
			AzurePipelinesServiceConnectionID: "flag-service-connection-id",
		}
		o.UpdateFromEnv()

		// Command-line flags should take precedence (not be overwritten)
		if o.ClientID != "flag-client-id" {
			t.Fatalf("expected ClientID to remain 'flag-client-id', got '%s'", o.ClientID)
		}
		if o.TenantID != "flag-tenant-id" {
			t.Fatalf("expected TenantID to remain 'flag-tenant-id', got '%s'", o.TenantID)
		}
		if o.AzurePipelinesServiceConnectionID != "flag-service-connection-id" {
			t.Fatalf("expected AzurePipelinesServiceConnectionID to remain 'flag-service-connection-id', got '%s'", o.AzurePipelinesServiceConnectionID)
		}
	})

	t.Run("Azure Pipelines environment variables are not read when DisableEnvironmentOverride is true", func(t *testing.T) {
		t.Setenv(env.AzureSubscriptionClientID, testClientID)
		t.Setenv(env.AzureSubscriptionTenantID, testTenantID)
		t.Setenv(env.AzureSubscriptionServiceConnectionID, testServiceConnectionID)

		o := Options{
			LoginMethod:                AzurePipelinesLogin,
			DisableEnvironmentOverride: true,
		}
		o.UpdateFromEnv()

		if o.ClientID != "" {
			t.Fatalf("expected ClientID to be empty when DisableEnvironmentOverride is true, got '%s'", o.ClientID)
		}
		if o.TenantID != "" {
			t.Fatalf("expected TenantID to be empty when DisableEnvironmentOverride is true, got '%s'", o.TenantID)
		}
		if o.AzurePipelinesServiceConnectionID != "" {
			t.Fatalf("expected AzurePipelinesServiceConnectionID to be empty when DisableEnvironmentOverride is true, got '%s'", o.AzurePipelinesServiceConnectionID)
		}
	})

	t.Run("Azure Pipelines environment variables set LoginMethod from env", func(t *testing.T) {
		t.Setenv(env.LoginMethod, AzurePipelinesLogin)
		t.Setenv(env.AzureSubscriptionClientID, testClientID)
		t.Setenv(env.AzureSubscriptionTenantID, testTenantID)
		t.Setenv(env.AzureSubscriptionServiceConnectionID, testServiceConnectionID)

		o := Options{}
		o.UpdateFromEnv()

		if o.LoginMethod != AzurePipelinesLogin {
			t.Fatalf("expected LoginMethod to be '%s', got '%s'", AzurePipelinesLogin, o.LoginMethod)
		}
		if o.ClientID != testClientID {
			t.Fatalf("expected ClientID to be '%s', got '%s'", testClientID, o.ClientID)
		}
		if o.TenantID != testTenantID {
			t.Fatalf("expected TenantID to be '%s', got '%s'", testTenantID, o.TenantID)
		}
		if o.AzurePipelinesServiceConnectionID != testServiceConnectionID {
			t.Fatalf("expected AzurePipelinesServiceConnectionID to be '%s', got '%s'", testServiceConnectionID, o.AzurePipelinesServiceConnectionID)
		}
	})

	t.Run("Azure Pipelines environment variables partially set", func(t *testing.T) {
		t.Setenv(env.AzureSubscriptionClientID, testClientID)
		// Only set ClientID, not TenantID or ServiceConnectionID

		o := Options{LoginMethod: AzurePipelinesLogin}
		o.UpdateFromEnv()

		if o.ClientID != testClientID {
			t.Fatalf("expected ClientID to be '%s', got '%s'", testClientID, o.ClientID)
		}
		if o.TenantID != "" {
			t.Fatalf("expected TenantID to be empty, got '%s'", o.TenantID)
		}
		if o.AzurePipelinesServiceConnectionID != "" {
			t.Fatalf("expected AzurePipelinesServiceConnectionID to be empty, got '%s'", o.AzurePipelinesServiceConnectionID)
		}
	})
}

func TestGetCloudConfiguration(t *testing.T) {
	testCases := []struct {
		name        string
		environment string
		authority   string
		expected    string
	}{
		{
			name:        "AZURECLOUD environment",
			environment: "AZURECLOUD",
			expected:    "https://login.microsoftonline.com/",
		},
		{
			name:        "AZUREPUBLIC environment",
			environment: "AZUREPUBLIC",
			expected:    "https://login.microsoftonline.com/",
		},
		{
			name:        "AZUREPUBLICCLOUD environment",
			environment: "AZUREPUBLICCLOUD",
			expected:    "https://login.microsoftonline.com/",
		},
		{
			name:        "AZUREUSGOVERNMENT environment",
			environment: "AZUREUSGOVERNMENT",
			expected:    "https://login.microsoftonline.us/",
		},
		{
			name:        "AZUREUSGOVERNMENTCLOUD environment",
			environment: "AZUREUSGOVERNMENTCLOUD",
			expected:    "https://login.microsoftonline.us/",
		},
		{
			name:        "AZURECHINACLOUD environment",
			environment: "AZURECHINACLOUD",
			expected:    "https://login.chinacloudapi.cn/",
		},
		{
			name:        "custom authority host",
			environment: "AZURECLOUD",
			authority:   "https://custom.authority.com/",
			expected:    "https://custom.authority.com/",
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			o := Options{
				Environment:   tc.environment,
				AuthorityHost: tc.authority,
			}
			config := o.GetCloudConfiguration()
			if config.ActiveDirectoryAuthorityHost != tc.expected {
				t.Errorf("expected authority host %s, got %s", tc.expected, config.ActiveDirectoryAuthorityHost)
			}
		})
	}
}

func TestAuthorityHostValidation(t *testing.T) {
	testCases := []struct {
		name        string
		authority   string
		shouldError bool
	}{
		{
			name:        "valid authority with trailing slash",
			authority:   "https://login.example.com/",
			shouldError: false,
		},
		{
			name:        "valid authority without trailing slash",
			authority:   "https://login.example.com",
			shouldError: true,
		},
		{
			name:        "invalid authority without scheme",
			authority:   "login.example.com/",
			shouldError: true,
		},
		{
			name:        "invalid authority with malformed URL",
			authority:   "https://login example.com/",
			shouldError: true,
		},
		{
			name:        "empty authority",
			authority:   "",
			shouldError: false,
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			o := defaultOptions()
			o.AuthorityHost = tc.authority
			err := o.Validate()
			if tc.shouldError && err == nil {
				t.Error("expected error but got none")
			}
			if !tc.shouldError && err != nil {
				t.Errorf("expected no error but got: %v", err)
			}
		})
	}
}
070701000000B4000081A4000000000000000000000001691F8CFD00000910000000000000000000000000000000000000003000000000kubelogin-0.2.13/pkg/internal/token/provider.gopackage token

//go:generate sh -c "mockgen -destination mock_$GOPACKAGE/provider.go github.com/Azure/kubelogin/pkg/internal/token CredentialProvider"

import (
	"context"
	"errors"
	"os"

	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
	"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
)

type CredentialProvider interface {
	GetToken(ctx context.Context, options policy.TokenRequestOptions) (azcore.AccessToken, error)

	Authenticate(ctx context.Context, options *policy.TokenRequestOptions) (azidentity.AuthenticationRecord, error)

	NeedAuthenticate() bool

	Name() string
}

func NewAzIdentityCredential(record azidentity.AuthenticationRecord, o *Options) (CredentialProvider, error) {
	switch o.LoginMethod {
	case AzureCLILogin:
		return newAzureCLICredential(o)

	case AzureDeveloperCLILogin:
		return newAzureDeveloperCLICredential(o)

	case DeviceCodeLogin:
		switch {
		case o.IsLegacy:
			return newADALDeviceCodeCredential(o)
		default:
			return newDeviceCodeCredential(o, record)
		}

	case InteractiveLogin:
		switch {
		case o.IsPoPTokenEnabled:
			return newInteractiveBrowserCredentialWithPoP(o)
		default:
			return newInteractiveBrowserCredential(o, record)
		}

	case MSILogin:
		return newManagedIdentityCredential(o)

	case ROPCLogin:
		switch {
		case o.IsPoPTokenEnabled:
			return newUsernamePasswordCredentialWithPoP(o)
		default:
			return newUsernamePasswordCredential(o, record)
		}

	case ServicePrincipalLogin:
		switch {
		case o.IsLegacy && o.ClientCert != "":
			return newADALClientCertCredential(o)
		case o.IsLegacy:
			return newADALClientSecretCredential(o)
		case o.ClientCert != "" && o.IsPoPTokenEnabled:
			return newClientCertificateCredentialWithPoP(o)
		case o.ClientCert != "":
			return newClientCertificateCredential(o)
		case o.IsPoPTokenEnabled:
			return newClientSecretCredentialWithPoP(o)
		default:
			return newClientSecretCredential(o)
		}

	case WorkloadIdentityLogin:
		switch {
		case os.Getenv(actionsIDTokenRequestToken) != "" && os.Getenv(actionsIDTokenRequestURL) != "":
			return newGithubActionsCredential(o)
		default:
			return newWorkloadIdentityCredential(o)
		}

	case AzurePipelinesLogin:
		return newAzurePipelinesCredential(o)
	}

	return nil, errors.New("unsupported token provider")
}
070701000000B5000081A4000000000000000000000001691F8CFD000010EC000000000000000000000000000000000000003500000000kubelogin-0.2.13/pkg/internal/token/provider_test.gopackage token

import (
	"os"
	"testing"

	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
	"github.com/Azure/kubelogin/pkg/internal/env"
	"github.com/stretchr/testify/assert"
)

func TestNewAzIdentityCredential(t *testing.T) {
	certFile := "fixtures/cert.pem"

	// Set up environment variables for Azure Pipelines test
	os.Setenv(env.SystemAccessToken, "test-system-access-token")
	os.Setenv(env.SystemOIDCRequestURI, "https://test.oidc.request.uri")
	defer func() {
		os.Unsetenv(env.SystemAccessToken)
		os.Unsetenv(env.SystemOIDCRequestURI)
	}()

	tests := []struct {
		name       string
		options    *Options
		wantErr    bool
		errMessage string
	}{
		{
			name: "Azure CLI login",
			options: &Options{
				LoginMethod: AzureCLILogin,
				ServerID:    "server-id",
				TenantID:    "tenant-id",
			},
			wantErr: false,
		},
		{
			name: "Device code login",
			options: &Options{
				LoginMethod: DeviceCodeLogin,
				ServerID:    "server-id",
				TenantID:    "tenant-id",
				ClientID:    "client-id",
			},
			wantErr: false,
		},
		{
			name: "Legacy device code login",
			options: &Options{
				LoginMethod: DeviceCodeLogin,
				ServerID:    "server-id",
				TenantID:    "tenant-id",
				ClientID:    "client-id",
				IsLegacy:    true,
			},
			wantErr: false,
		},
		{
			name: "Interactive login with PoP",
			options: &Options{
				LoginMethod:       InteractiveLogin,
				ServerID:          "server-id",
				TenantID:          "tenant-id",
				ClientID:          "client-id",
				IsPoPTokenEnabled: true,
				PoPTokenClaims:    "u=test-cluster",
			},
			wantErr: false,
		},
		{
			name: "MSI login",
			options: &Options{
				LoginMethod: MSILogin,
				ServerID:    "server-id",
			},
			wantErr: false,
		},
		{
			name: "Service Principal with client cert and PoP",
			options: &Options{
				LoginMethod:       ServicePrincipalLogin,
				ServerID:          "server-id",
				TenantID:          "tenant-id",
				ClientID:          "client-id",
				ClientCert:        certFile,
				IsPoPTokenEnabled: true,
				PoPTokenClaims:    "u=test-cluster",
			},
			wantErr: false,
		},
		{
			name: "Service Principal with client secret",
			options: &Options{
				LoginMethod:  ServicePrincipalLogin,
				ServerID:     "server-id",
				TenantID:     "tenant-id",
				ClientID:     "client-id",
				ClientSecret: "secret",
			},
			wantErr: false,
		},
		{
			name: "Unsupported login method",
			options: &Options{
				LoginMethod: "unsupported",
				ServerID:    "server-id",
			},
			wantErr:    true,
			errMessage: "unsupported token provider",
		},
		{
			name: "Azure Pipelines login",
			options: &Options{
				LoginMethod:                       AzurePipelinesLogin,
				ServerID:                          "server-id",
				TenantID:                          "tenant-id",
				ClientID:                          "client-id",
				AzurePipelinesServiceConnectionID: "service-connection-id",
			},
			wantErr: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			record := azidentity.AuthenticationRecord{}
			provider, err := NewAzIdentityCredential(record, tt.options)

			if tt.wantErr {
				assert.Error(t, err)
				if tt.errMessage != "" {
					assert.Equal(t, tt.errMessage, err.Error())
				}
				assert.Nil(t, provider)
			} else {
				assert.NoError(t, err)
				assert.NotNil(t, provider)
			}
		})
	}
}

func TestNewAzIdentityCredentialWithWorkloadIdentity(t *testing.T) {
	// Setup environment variables
	os.Setenv("ACTIONS_ID_TOKEN_REQUEST_TOKEN", "token")
	os.Setenv("ACTIONS_ID_TOKEN_REQUEST_URL", "url")
	defer func() {
		os.Unsetenv("ACTIONS_ID_TOKEN_REQUEST_TOKEN")
		os.Unsetenv("ACTIONS_ID_TOKEN_REQUEST_URL")
	}()

	tests := []struct {
		name    string
		options *Options
		wantErr bool
	}{
		{
			name: "GitHub Actions Workload Identity",
			options: &Options{
				LoginMethod: WorkloadIdentityLogin,
				ServerID:    "server-id",
				TenantID:    "tenant-id",
				ClientID:    "client-id",
			},
			wantErr: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			record := azidentity.AuthenticationRecord{}
			provider, err := NewAzIdentityCredential(record, tt.options)

			if tt.wantErr {
				assert.Error(t, err)
				assert.Nil(t, provider)
			} else {
				assert.NoError(t, err)
				assert.NotNil(t, provider)
			}
		})
	}
}
070701000000B6000081A4000000000000000000000001691F8CFD000008AD000000000000000000000000000000000000004200000000kubelogin-0.2.13/pkg/internal/token/usernamepasswordcredential.gopackage token

import (
	"context"
	"fmt"

	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
	"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
	"github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache"
	"k8s.io/klog/v2"
)

type UsernamePasswordCredential struct {
	cred *azidentity.UsernamePasswordCredential
}

var _ CredentialProvider = (*UsernamePasswordCredential)(nil)

func newUsernamePasswordCredential(opts *Options, record azidentity.AuthenticationRecord) (CredentialProvider, error) {
	if opts.ClientID == "" {
		return nil, fmt.Errorf("client ID cannot be empty")
	}
	if opts.TenantID == "" {
		return nil, fmt.Errorf("tenant ID cannot be empty")
	}
	if opts.Username == "" {
		return nil, fmt.Errorf("username cannot be empty")
	}
	if opts.Password == "" {
		return nil, fmt.Errorf("password cannot be empty")
	}
	var (
		c   azidentity.Cache
		err error
	)
	if opts.UsePersistentCache {
		c, err = cache.New(nil)
		if err != nil {
			klog.V(5).Infof("failed to create cache: %v", err)
		}
	}

	azOpts := &azidentity.UsernamePasswordCredentialOptions{
		ClientOptions:            azcore.ClientOptions{Cloud: opts.GetCloudConfiguration()},
		AuthenticationRecord:     record,
		Cache:                    c,
		DisableInstanceDiscovery: opts.DisableInstanceDiscovery,
	}

	if opts.httpClient != nil {
		azOpts.Transport = opts.httpClient
	}

	cred, err := azidentity.NewUsernamePasswordCredential(
		opts.TenantID, opts.ClientID, opts.Username, opts.Password,
		azOpts)
	if err != nil {
		return nil, fmt.Errorf("failed to create username password credential: %w", err)
	}
	return &UsernamePasswordCredential{cred: cred}, nil
}

func (c *UsernamePasswordCredential) Name() string {
	return "UsernamePasswordCredential"
}

func (c *UsernamePasswordCredential) Authenticate(ctx context.Context, opts *policy.TokenRequestOptions) (azidentity.AuthenticationRecord, error) {
	return c.cred.Authenticate(ctx, opts)
}

func (c *UsernamePasswordCredential) GetToken(ctx context.Context, opts policy.TokenRequestOptions) (azcore.AccessToken, error) {
	return c.cred.GetToken(ctx, opts)
}

func (c *UsernamePasswordCredential) NeedAuthenticate() bool {
	return true
}
070701000000B7000081A4000000000000000000000001691F8CFD000004F9000000000000000000000000000000000000004700000000kubelogin-0.2.13/pkg/internal/token/usernamepasswordcredential_test.gopackage token

import (
	"context"
	"testing"

	"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
	"github.com/Azure/kubelogin/pkg/internal/testutils"
	"github.com/stretchr/testify/assert"
)

func TestUsernamePasswordCredential_GetToken(t *testing.T) {
	rec, err := testutils.GetVCRHttpClient("fixtures/usernamepassword_credential", testutils.TestTenantID)
	if err != nil {
		t.Fatalf("failed to create recorder: %v", err)
	}
	defer rec.Stop()

	opts := &Options{
		ClientID:   testutils.TestClientID,
		ServerID:   testutils.TestServerID,
		Username:   "user@example.come",
		Password:   "password",
		TenantID:   testutils.TestTenantID,
		httpClient: rec.GetDefaultClient(),
	}
	record := azidentity.AuthenticationRecord{}
	cred, err := newUsernamePasswordCredential(opts, record)
	if err != nil {
		t.Fatalf("failed to create credential: %v", err)
	}

	_, err = cred.GetToken(context.Background(), policy.TokenRequestOptions{
		Scopes: []string{opts.ServerID + "/.default"},
	})
	// our test environment requires MFA
	assert.ErrorContains(t, err, "AADSTS50076: Due to a configuration change made by your administrator, or because you moved to a new location, you must use multi-factor authentication to access")
}
070701000000B8000081A4000000000000000000000001691F8CFD00000D79000000000000000000000000000000000000004900000000kubelogin-0.2.13/pkg/internal/token/usernamepasswordcredentialwithpop.gopackage token

import (
	"context"
	"fmt"
	"net/url"
	"time"

	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
	"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
	"github.com/Azure/kubelogin/pkg/internal/pop"
	"github.com/AzureAD/microsoft-authentication-library-for-go/apps/public"
)

type UsernamePasswordCredentialWithPoP struct {
	popClaims   map[string]string
	username    string
	password    string
	client      public.Client
	options     *pop.MsalClientOptions
	keyProvider PoPKeyProvider
}

var _ CredentialProvider = (*UsernamePasswordCredentialWithPoP)(nil)

func newUsernamePasswordCredentialWithPoP(opts *Options) (CredentialProvider, error) {
	if opts.ClientID == "" {
		return nil, fmt.Errorf("client ID cannot be empty")
	}
	if opts.TenantID == "" {
		return nil, fmt.Errorf("tenant ID cannot be empty")
	}
	if opts.Username == "" {
		return nil, fmt.Errorf("username cannot be empty")
	}
	if opts.Password == "" {
		return nil, fmt.Errorf("password cannot be empty")
	}
	popClaimsMap, err := parsePoPClaims(opts.PoPTokenClaims)
	if err != nil {
		return nil, fmt.Errorf("unable to parse PoP claims: %w", err)
	}
	if len(popClaimsMap) == 0 {
		return nil, fmt.Errorf("number of pop claims is invalid: %d", len(popClaimsMap))
	}

	// Construct authority URL properly to avoid malformation
	authorityURL, err := url.JoinPath(opts.GetCloudConfiguration().ActiveDirectoryAuthorityHost, opts.TenantID)
	if err != nil {
		return nil, fmt.Errorf("unable to construct authority URL: %w", err)
	}

	msalOpts := &pop.MsalClientOptions{
		Authority:                authorityURL,
		ClientID:                 opts.ClientID,
		TenantID:                 opts.TenantID,
		DisableInstanceDiscovery: opts.DisableInstanceDiscovery,
	}
	if opts.httpClient != nil {
		msalOpts.Options.Transport = opts.httpClient
	}
	// Get cache from Options
	popCache := opts.GetPoPTokenCache()

	client, err := pop.NewPublicClient(msalOpts, pop.WithCustomCachePublic(popCache))
	if err != nil {
		return nil, fmt.Errorf("unable to create public client: %w", err)
	}
	return &UsernamePasswordCredentialWithPoP{
		options:     msalOpts,
		popClaims:   popClaimsMap,
		username:    opts.Username,
		password:    opts.Password,
		client:      client,
		keyProvider: opts.GetPoPKeyProvider(),
	}, nil
}

func (c *UsernamePasswordCredentialWithPoP) Name() string {
	return "UsernamePasswordCredentialWithPoP"
}

func (c *UsernamePasswordCredentialWithPoP) Authenticate(ctx context.Context, opts *policy.TokenRequestOptions) (azidentity.AuthenticationRecord, error) {
	return azidentity.AuthenticationRecord{}, errAuthenticateNotSupported
}

func (c *UsernamePasswordCredentialWithPoP) GetToken(ctx context.Context, opts policy.TokenRequestOptions) (azcore.AccessToken, error) {
	// Get PoP key using centralized key provider
	popKey, err := c.keyProvider.GetPoPKey()
	if err != nil {
		return azcore.AccessToken{}, err
	}

	token, expirationTimeUnix, err := pop.AcquirePoPTokenByUsernamePassword(
		ctx,
		c.popClaims,
		opts.Scopes,
		c.client,
		c.username,
		c.password,
		c.options,
		popKey,
	)
	if err != nil {
		return azcore.AccessToken{}, fmt.Errorf("failed to create PoP token using username and password credential: %w", err)
	}
	return azcore.AccessToken{Token: token, ExpiresOn: time.Unix(expirationTimeUnix, 0)}, nil
}

func (c *UsernamePasswordCredentialWithPoP) NeedAuthenticate() bool {
	return false
}
070701000000B9000081A4000000000000000000000001691F8CFD00000FA8000000000000000000000000000000000000004E00000000kubelogin-0.2.13/pkg/internal/token/usernamepasswordcredentialwithpop_test.gopackage token

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestNewUsernamePasswordCredentialWithPoP(t *testing.T) {
	testCases := []struct {
		name           string
		opts           *Options
		expectErrorMsg string
		expectName     string
	}{
		{
			name: "valid options",
			opts: &Options{
				ClientID:          "test-client-id",
				TenantID:          "test-tenant-id",
				Username:          "test-user",
				Password:          "test-password",
				IsPoPTokenEnabled: true,
				PoPTokenClaims:    "u=test-cluster",
				AuthorityHost:     "https://login.microsoftonline.com/",
			},
			expectName: "UsernamePasswordCredentialWithPoP",
		},
		{
			name: "missing client ID",
			opts: &Options{
				TenantID:          "test-tenant-id",
				Username:          "test-user",
				Password:          "test-password",
				IsPoPTokenEnabled: true,
				PoPTokenClaims:    "u=test-cluster",
				AuthorityHost:     "https://login.microsoftonline.com/",
			},
			expectErrorMsg: "client ID cannot be empty",
		},
		{
			name: "missing tenant ID",
			opts: &Options{
				ClientID:          "test-client-id",
				Username:          "test-user",
				Password:          "test-password",
				IsPoPTokenEnabled: true,
				PoPTokenClaims:    "u=test-cluster",
				AuthorityHost:     "https://login.microsoftonline.com/",
			},
			expectErrorMsg: "tenant ID cannot be empty",
		},
		{
			name: "missing username",
			opts: &Options{
				ClientID:          "test-client-id",
				TenantID:          "test-tenant-id",
				Password:          "test-password",
				IsPoPTokenEnabled: true,
				PoPTokenClaims:    "u=test-cluster",
				AuthorityHost:     "https://login.microsoftonline.com/",
			},
			expectErrorMsg: "username cannot be empty",
		},
		{
			name: "missing password",
			opts: &Options{
				ClientID:          "test-client-id",
				TenantID:          "test-tenant-id",
				Username:          "test-user",
				IsPoPTokenEnabled: true,
				PoPTokenClaims:    "u=test-cluster",
				AuthorityHost:     "https://login.microsoftonline.com/",
			},
			expectErrorMsg: "password cannot be empty",
		},
		{
			name: "missing PoP claims",
			opts: &Options{
				ClientID:          "test-client-id",
				TenantID:          "test-tenant-id",
				Username:          "test-user",
				Password:          "test-password",
				IsPoPTokenEnabled: true,
				AuthorityHost:     "https://login.microsoftonline.com/",
			},
			expectErrorMsg: "unable to parse PoP claims: failed to parse PoP token claims: no claims provided",
		},
		{
			name: "invalid PoP claims format",
			opts: &Options{
				ClientID:          "test-client-id",
				TenantID:          "test-tenant-id",
				Username:          "test-user",
				Password:          "test-password",
				IsPoPTokenEnabled: true,
				PoPTokenClaims:    "invalid-format",
				AuthorityHost:     "https://login.microsoftonline.com/",
			},
			expectErrorMsg: "unable to parse PoP claims: failed to parse PoP token claims. Ensure the claims are formatted as `key=value` with no extra whitespace",
		},
		{
			name: "missing required u-claim",
			opts: &Options{
				ClientID:          "test-client-id",
				TenantID:          "test-tenant-id",
				Username:          "test-user",
				Password:          "test-password",
				IsPoPTokenEnabled: true,
				PoPTokenClaims:    "key=value",
				AuthorityHost:     "https://login.microsoftonline.com/",
			},
			expectErrorMsg: "unable to parse PoP claims: required u-claim not provided for PoP token flow. Please provide the ARM ID of the cluster in the format `u=<ARM_ID>`",
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			cred, err := newUsernamePasswordCredentialWithPoP(tc.opts)
			if tc.expectErrorMsg != "" {
				assert.Error(t, err)
				assert.Equal(t, tc.expectErrorMsg, err.Error())
				assert.Nil(t, cred)
			} else {
				assert.NoError(t, err)
				assert.NotNil(t, cred)
				assert.Equal(t, tc.expectName, cred.Name())
				assert.False(t, cred.NeedAuthenticate())
			}
		})
	}
}
070701000000BA000081A4000000000000000000000001691F8CFD0000097C000000000000000000000000000000000000004200000000kubelogin-0.2.13/pkg/internal/token/workloadidentitycredential.gopackage token

import (
	"context"
	"fmt"

	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
	"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
	"github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache"
	"k8s.io/klog/v2"
)

const (
	actionsIDTokenRequestToken = "ACTIONS_ID_TOKEN_REQUEST_TOKEN"
	actionsIDTokenRequestURL   = "ACTIONS_ID_TOKEN_REQUEST_URL"
	azureADAudience            = "api://AzureADTokenExchange"
	defaultScope               = "/.default"
)

type WorkloadIdentityCredential struct {
	cred *azidentity.WorkloadIdentityCredential
}

var _ CredentialProvider = (*WorkloadIdentityCredential)(nil)

func newWorkloadIdentityCredential(opts *Options) (CredentialProvider, error) {
	if opts.ClientID == "" {
		return nil, fmt.Errorf("client ID cannot be empty")
	}
	if opts.TenantID == "" {
		return nil, fmt.Errorf("tenant ID cannot be empty")
	}
	if opts.FederatedTokenFile == "" {
		return nil, fmt.Errorf("federated token file cannot be empty")
	}
	var (
		c   azidentity.Cache
		err error
	)
	if opts.UsePersistentCache {
		c, err = cache.New(nil)
		if err != nil {
			klog.V(5).Infof("failed to create cache: %v", err)
		}
	}

	azOpts := &azidentity.WorkloadIdentityCredentialOptions{
		ClientOptions:            azcore.ClientOptions{Cloud: opts.GetCloudConfiguration()},
		Cache:                    c,
		ClientID:                 opts.ClientID,
		TenantID:                 opts.TenantID,
		TokenFilePath:            opts.FederatedTokenFile,
		DisableInstanceDiscovery: opts.DisableInstanceDiscovery,
	}

	if opts.httpClient != nil {
		azOpts.Transport = opts.httpClient
	}

	cred, err := azidentity.NewWorkloadIdentityCredential(azOpts)
	if err != nil {
		return nil, fmt.Errorf("failed to create workload identity credential: %w", err)
	}
	return &WorkloadIdentityCredential{cred: cred}, nil
}

func (c *WorkloadIdentityCredential) Name() string {
	return "WorkloadIdentityCredential"
}

func (c *WorkloadIdentityCredential) Authenticate(ctx context.Context, opts *policy.TokenRequestOptions) (azidentity.AuthenticationRecord, error) {
	return azidentity.AuthenticationRecord{}, errAuthenticateNotSupported
}

func (c *WorkloadIdentityCredential) GetToken(ctx context.Context, opts policy.TokenRequestOptions) (azcore.AccessToken, error) {
	return c.cred.GetToken(ctx, opts)
}

func (c *WorkloadIdentityCredential) NeedAuthenticate() bool {
	return false
}
070701000000BB000081A4000000000000000000000001691F8CFD00000654000000000000000000000000000000000000004700000000kubelogin-0.2.13/pkg/internal/token/workloadidentitycredential_test.gopackage token

import (
	"context"
	"os"
	"path/filepath"
	"testing"

	"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
	"github.com/Azure/kubelogin/pkg/internal/testutils"
	"github.com/stretchr/testify/assert"
)

func TestWorkloadIdentityCredential_GetToken(t *testing.T) {
	var tokenFile string

	liveTokenFile := os.Getenv("KUBELOGIN_LIVETEST_FEDERATED_TOKEN_FILE")
	if liveTokenFile == "" {
		tempDir, err := os.MkdirTemp("", "kubelogin")
		if err != nil {
			t.Fatalf("failed to create temp dir: %v", err)
		}

		tokenFile = filepath.Join(tempDir, "token")
		outFile, err := os.Create(tokenFile)
		if err != nil {
			t.Fatalf("failed to create token file: %v", err)
		}
		_, err = outFile.WriteString("[REDACTED]")
		if err != nil {
			t.Fatalf("failed to write token file: %v", err)
		}
		outFile.Close()
	} else {
		tokenFile = liveTokenFile
	}

	rec, err := testutils.GetVCRHttpClient("fixtures/workloadidentity_credential", testutils.TestTenantID)
	if err != nil {
		t.Fatalf("failed to create recorder: %v", err)
	}
	defer rec.Stop()

	opts := &Options{
		ClientID:           testutils.TestClientID,
		ServerID:           testutils.TestServerID,
		TenantID:           testutils.TestTenantID,
		FederatedTokenFile: tokenFile,
		httpClient:         rec.GetDefaultClient(),
	}
	cred, err := newWorkloadIdentityCredential(opts)
	if err != nil {
		t.Fatalf("failed to create credential: %v", err)
	}

	token, err := cred.GetToken(context.Background(), policy.TokenRequestOptions{
		Scopes: []string{opts.ServerID + "/.default"},
	})
	assert.NoError(t, err)
	assert.Equal(t, testutils.TestToken, token.Token)
}
070701000000BC000041ED000000000000000000000002691F8CFD00000000000000000000000000000000000000000000001900000000kubelogin-0.2.13/pkg/pop070701000000BD000081A4000000000000000000000001691F8CFD00000156000000000000000000000000000000000000002E00000000kubelogin-0.2.13/pkg/pop/msal_confidential.gopackage pop

import (
	"github.com/Azure/kubelogin/pkg/internal/pop"
)

// AcquirePoPTokenConfidential retrieves a Proof of Possession (PoP) token using confidential client credentials.
// It utilizes the internal pop.AcquirePoPTokenConfidential function to obtain the token.
var AcquirePoPTokenConfidential = pop.AcquirePoPTokenConfidential
070701000000BE000081A4000000000000000000000001691F8CFD00000105000000000000000000000000000000000000002500000000kubelogin-0.2.13/pkg/pop/poptoken.gopackage pop

import "github.com/Azure/kubelogin/pkg/internal/pop"

// GetSwPoPKey retrieves a software Proof of Possession (PoP) key using RSA encryption.
// It utilizes the internal pop.GetSwPoPKey function to obtain the key.
var GetSwPoPKey = pop.GetSwPoPKey
070701000000BF000081A4000000000000000000000001691F8CFD000001E1000000000000000000000000000000000000002200000000kubelogin-0.2.13/pkg/pop/types.gopackage pop

import (
	"github.com/Azure/kubelogin/pkg/internal/pop"
)

// This is the MSAL implementation of AuthenticationScheme.
// For more details, see the MSAL repo interface:
// https://github.com/AzureAD/microsoft-authentication-library-for-go/blob/4a4dafcbcbd7d57a69ed3bc59760381232c2be9c/apps/internal/oauth/ops/authority/authority.go#L146
type PoPAuthenticationScheme = pop.PoPAuthenticationScheme

type SwKey = pop.SwKey

type MsalClientOptions = pop.MsalClientOptions
070701000000C0000041ED000000000000000000000002691F8CFD00000000000000000000000000000000000000000000001B00000000kubelogin-0.2.13/pkg/token070701000000C1000081A4000000000000000000000001691F8CFD000003A1000000000000000000000000000000000000002600000000kubelogin-0.2.13/pkg/token/options.gopackage token

import "github.com/Azure/kubelogin/pkg/internal/token"

// list of supported login methods for library consumers

const (
	ServicePrincipalLogin = token.ServicePrincipalLogin
	MSILogin              = token.MSILogin
	WorkloadIdentityLogin = token.WorkloadIdentityLogin
)

// Options defines the options for getting token.
// This struct is a subset of internal/token.Options where its values are copied
// to internal type. See internal/token/options.go for details
type Options struct {
	LoginMethod string

	// shared login settings

	Environment string
	TenantID    string
	ServerID    string
	ClientID    string

	// for ServicePrincipalLogin

	ClientSecret       string
	ClientCert         string
	ClientCertPassword string
	IsPoPTokenEnabled  bool
	PoPTokenClaims     string

	// for MSILogin

	IdentityResourceID string

	// for WorkloadIdentityLogin

	AuthorityHost      string
	FederatedTokenFile string
}
070701000000C2000081A4000000000000000000000001691F8CFD000006FE000000000000000000000000000000000000002B00000000kubelogin-0.2.13/pkg/token/options_ctor.gopackage token

import (
	"os"

	"github.com/Azure/kubelogin/pkg/internal/env"
	"github.com/Azure/kubelogin/pkg/internal/token"
)

// OptionsWithEnv loads options from environment variables.
func OptionsWithEnv() *Options {
	// initial default values
	rv := &Options{
		LoginMethod:        os.Getenv(env.LoginMethod),
		TenantID:           os.Getenv(env.AzureTenantID),
		ClientID:           os.Getenv(env.KubeloginClientID),
		ClientSecret:       os.Getenv(env.KubeloginClientSecret),
		ClientCert:         os.Getenv(env.KubeloginClientCertificatePath),
		ClientCertPassword: os.Getenv(env.KubeloginClientCertificatePassword),
		AuthorityHost:      os.Getenv(env.AzureAuthorityHost),
		FederatedTokenFile: os.Getenv(env.AzureFederatedTokenFile),
	}

	// azure variant overrides
	if v, ok := os.LookupEnv(env.AzureClientID); ok {
		rv.ClientID = v
	}
	if v, ok := os.LookupEnv(env.AzureClientSecret); ok {
		rv.ClientSecret = v
	}
	if v, ok := os.LookupEnv(env.AzureClientCertificatePath); ok {
		rv.ClientCert = v
	}
	if v, ok := os.LookupEnv(env.AzureClientCertificatePassword); ok {
		rv.ClientCertPassword = v
	}

	return rv
}

func (opts *Options) toInternalOptions() *token.Options {
	return &token.Options{
		LoginMethod:        opts.LoginMethod,
		Environment:        opts.Environment,
		TenantID:           opts.TenantID,
		ServerID:           opts.ServerID,
		ClientID:           opts.ClientID,
		ClientSecret:       opts.ClientSecret,
		ClientCert:         opts.ClientCert,
		ClientCertPassword: opts.ClientCertPassword,
		IsPoPTokenEnabled:  opts.IsPoPTokenEnabled,
		PoPTokenClaims:     opts.PoPTokenClaims,
		IdentityResourceID: opts.IdentityResourceID,
		AuthorityHost:      opts.AuthorityHost,
		FederatedTokenFile: opts.FederatedTokenFile,
		UsePersistentCache: false,
	}
}
070701000000C3000081A4000000000000000000000001691F8CFD0000143E000000000000000000000000000000000000003000000000kubelogin-0.2.13/pkg/token/options_ctor_test.gopackage token

import (
	"reflect"
	"testing"

	"github.com/Azure/kubelogin/pkg/internal/env"
	"github.com/Azure/kubelogin/pkg/internal/token"
	"github.com/stretchr/testify/assert"
)

func TestOptionsWithEnv(t *testing.T) {
	t.Run("no env vars", func(t *testing.T) {
		o := OptionsWithEnv()
		assert.Equal(t, &Options{}, o)
	})

	t.Run("with kubelogin variant env vars", func(t *testing.T) {
		for k, v := range map[string]string{
			env.LoginMethod:                        MSILogin,
			env.AzureTenantID:                      "tenant-id",
			env.KubeloginClientID:                  "client-id",
			env.KubeloginClientSecret:              "client-secret",
			env.KubeloginClientCertificatePath:     "client-cert-path",
			env.KubeloginClientCertificatePassword: "client-cert-password",
			env.AzureAuthorityHost:                 "authority-host",
			env.AzureFederatedTokenFile:            "federated-token-file",
		} {
			t.Setenv(k, v)
		}

		o := OptionsWithEnv()
		assert.Equal(t, &Options{
			LoginMethod:        MSILogin,
			TenantID:           "tenant-id",
			ClientID:           "client-id",
			ClientSecret:       "client-secret",
			ClientCert:         "client-cert-path",
			ClientCertPassword: "client-cert-password",
			AuthorityHost:      "authority-host",
			FederatedTokenFile: "federated-token-file",
		}, o)
	})

	t.Run("with azure variant env vars", func(t *testing.T) {
		for k, v := range map[string]string{
			env.LoginMethod:                        MSILogin,
			env.AzureTenantID:                      "tenant-id",
			env.KubeloginClientID:                  "client-id",
			env.AzureClientID:                      "azure-client-id",
			env.KubeloginClientSecret:              "client-secret",
			env.AzureClientSecret:                  "azure-client-secret",
			env.KubeloginClientCertificatePath:     "client-cert-path",
			env.AzureClientCertificatePath:         "azure-client-cert-path",
			env.KubeloginClientCertificatePassword: "client-cert-password",
			env.AzureClientCertificatePassword:     "azure-client-cert-password",
			env.AzureAuthorityHost:                 "authority-host",
			env.AzureFederatedTokenFile:            "federated-token-file",
		} {
			t.Setenv(k, v)
		}

		o := OptionsWithEnv()
		assert.Equal(t, &Options{
			LoginMethod:        MSILogin,
			TenantID:           "tenant-id",
			ClientID:           "azure-client-id",
			ClientSecret:       "azure-client-secret",
			ClientCert:         "azure-client-cert-path",
			ClientCertPassword: "azure-client-cert-password",
			AuthorityHost:      "authority-host",
			FederatedTokenFile: "federated-token-file",
		}, o)
	})
}

func TestOptions_toInternalOptions(t *testing.T) {
	t.Run("basic", func(t *testing.T) {
		o := &Options{
			LoginMethod:        "login-method",
			Environment:        "environment",
			TenantID:           "tenant-id",
			ServerID:           "server-id",
			ClientID:           "client-id",
			ClientSecret:       "client-secret",
			ClientCert:         "client-cert",
			ClientCertPassword: "client-cert-password",
			IsPoPTokenEnabled:  true,
			PoPTokenClaims:     "pop-token-claims",
			IdentityResourceID: "identity-resource-id",
			AuthorityHost:      "authority-host",
			FederatedTokenFile: "federated-token-file",
		}
		assert.Equal(t, &token.Options{
			LoginMethod:        "login-method",
			Environment:        "environment",
			TenantID:           "tenant-id",
			ServerID:           "server-id",
			ClientID:           "client-id",
			ClientSecret:       "client-secret",
			ClientCert:         "client-cert",
			ClientCertPassword: "client-cert-password",
			IsPoPTokenEnabled:  true,
			PoPTokenClaims:     "pop-token-claims",
			IdentityResourceID: "identity-resource-id",
			AuthorityHost:      "authority-host",
			FederatedTokenFile: "federated-token-file",
		}, o.toInternalOptions())
	})

	// this test uses reflection to ensure all fields in *Options
	// are copied to *token.Options without modification.
	t.Run("fields assignment", func(t *testing.T) {
		boolValue := true
		stringValue := "string-value"

		o := &Options{}

		// fill up all fields in *Options
		oType := reflect.TypeOf(o).Elem()
		oValue := reflect.ValueOf(o).Elem()
		for i := 0; i < oValue.NumField(); i++ {
			fieldValue := oValue.Field(i)
			fieldType := oType.Field(i)
			switch k := fieldType.Type.Kind(); k {
			case reflect.Bool:
				// set bool value
				fieldValue.SetBool(boolValue)
			case reflect.String:
				fieldValue.SetString(stringValue)
			default:
				t.Errorf("unexpected type: %s", k)
			}
		}

		internalOpts := o.toInternalOptions()
		assert.NotNil(t, internalOpts)

		internalOptsValue := reflect.ValueOf(internalOpts).Elem()
		for i := 0; i < oValue.NumField(); i++ {
			fieldType := oType.Field(i)
			t.Log(fieldType.Name)
			internalOptsFieldValue := internalOptsValue.FieldByName(fieldType.Name)
			switch k := fieldType.Type.Kind(); k {
			case reflect.Bool:
				assert.Equal(t, boolValue, internalOptsFieldValue.Bool(), "field: %s", fieldType.Name)
			case reflect.String:
				assert.Equal(t, stringValue, internalOptsFieldValue.String(), "field: %s", fieldType.Name)
			default:
				t.Errorf("unexpected type: %s", k)
			}
		}
	})
}
070701000000C4000081A4000000000000000000000001691F8CFD000003B3000000000000000000000000000000000000002700000000kubelogin-0.2.13/pkg/token/provider.gopackage token

import (
	"context"

	"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
	"github.com/Azure/kubelogin/pkg/internal/token"
)

type tokenProviderShim struct {
	opts *token.Options
	cred token.CredentialProvider
}

var _ TokenProvider = (*tokenProviderShim)(nil)

func (tp *tokenProviderShim) GetAccessToken(ctx context.Context) (AccessToken, error) {
	tro := policy.TokenRequestOptions{
		TenantID: tp.opts.TenantID,
		Scopes:   []string{token.GetScope(tp.opts.ServerID)},
	}
	return tp.cred.GetToken(ctx, tro)
}

// GetTokenProvider returns a token provider based on the given options.
func GetTokenProvider(options *Options) (TokenProvider, error) {
	opts := options.toInternalOptions()
	cred, err := token.NewAzIdentityCredential(azidentity.AuthenticationRecord{}, opts)
	if err != nil {
		return nil, err
	}

	return &tokenProviderShim{
		cred: cred,
		opts: opts,
	}, nil
}
070701000000C5000081A4000000000000000000000001691F8CFD00000872000000000000000000000000000000000000002C00000000kubelogin-0.2.13/pkg/token/provider_test.gopackage token

import (
	"context"
	"testing"
	"time"

	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
	"github.com/Azure/kubelogin/pkg/internal/token"
	"github.com/Azure/kubelogin/pkg/internal/token/mock_token"
	"github.com/stretchr/testify/assert"
	"go.uber.org/mock/gomock"
)

func TestGetTokenProvider(t *testing.T) {
	t.Run("invalid login method", func(t *testing.T) {
		opts := &Options{
			LoginMethod: "invalid-login-method",
		}
		tp, err := GetTokenProvider(opts)
		assert.Error(t, err)
		assert.Nil(t, tp)
	})

	t.Run("basic", func(t *testing.T) {
		opts := &Options{
			LoginMethod:        MSILogin,
			ClientID:           "client-id",
			IdentityResourceID: "identity-resource-id",
			ServerID:           "server-id",
		}
		tp, err := GetTokenProvider(opts)
		assert.NoError(t, err)
		assert.NotNil(t, tp)
	})
}

func TestTokenProviderShim_GetAccessToken(t *testing.T) {
	t.Run("failure case", func(t *testing.T) {
		mockCtrl := gomock.NewController(t)
		defer mockCtrl.Finish()

		credProvider := mock_token.NewMockCredentialProvider(mockCtrl)
		credProvider.EXPECT().GetToken(gomock.Any(), gomock.Any()).Return(azcore.AccessToken{}, assert.AnError)

		tp := &tokenProviderShim{
			cred: credProvider,
			opts: &token.Options{
				TenantID: "tenant-id",
				ServerID: "server-id",
			},
		}

		token, err := tp.GetAccessToken(context.Background())
		assert.Equal(t, AccessToken{}, token)
		assert.Equal(t, assert.AnError, err)
	})

	t.Run("success case", func(t *testing.T) {
		mockCtrl := gomock.NewController(t)
		defer mockCtrl.Finish()

		expectedToken := azcore.AccessToken{
			Token:     "access-token",
			ExpiresOn: time.Unix(1700000000, 0),
		}
		credProvider := mock_token.NewMockCredentialProvider(mockCtrl)
		credProvider.EXPECT().GetToken(gomock.Any(), gomock.Any()).Return(expectedToken, nil)

		tp := &tokenProviderShim{
			cred: credProvider,
			opts: &token.Options{
				TenantID: "tenant-id",
				ServerID: "server-id",
			},
		}

		token, err := tp.GetAccessToken(context.Background())
		assert.NoError(t, err)
		assert.Equal(t, expectedToken.Token, token.Token)
		assert.Equal(t, expectedToken.ExpiresOn, token.ExpiresOn)
	})
}
070701000000C6000081A4000000000000000000000001691F8CFD0000019D000000000000000000000000000000000000002400000000kubelogin-0.2.13/pkg/token/types.gopackage token

import (
	"context"

	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
)

// AccessToken represents an Azure service bearer access token with expiry information.
type AccessToken = azcore.AccessToken

// TokenProvider provides access to tokens.
type TokenProvider interface {
	// GetAccessToken returns an access token from given settings.
	GetAccessToken(ctx context.Context) (AccessToken, error)
}
070701000000C7000081A4000000000000000000000001691F8CFD000006F8000000000000000000000000000000000000001C00000000kubelogin-0.2.13/version.gopackage main

import (
	"fmt"
	"runtime"
	"runtime/debug"
)

// gitTag provides the git tag used to build this binary.
// This is set via ldflags at build time, which normally set by the release pipeline.
// For go install binary, this value stays empty.
var gitTag string

type Version struct {
	Version   string
	GoVersion string
	BuildTime string
	Platform  string
}

func loadVersion() Version {
	rv := Version{
		Version:   "unknown",
		GoVersion: "unknown",
		BuildTime: "unknown",
		Platform:  runtime.GOOS + "/" + runtime.GOARCH,
	}
	if gitTag != "" {
		rv.Version = gitTag
	}

	buildInfo, ok := debug.ReadBuildInfo()
	if !ok {
		return rv
	}

	rv.GoVersion = buildInfo.GoVersion

	var (
		modified  bool
		revision  string
		buildTime string
	)
	for _, s := range buildInfo.Settings {
		if s.Value == "" {
			continue
		}

		switch s.Key {
		case "vcs.revision":
			revision = s.Value
		case "vcs.modified":
			modified = s.Value == "true"
		case "vcs.time":
			buildTime = s.Value
		}
	}

	// in Go install mode, this is a known issue that vcs information will not be available.
	// ref: https://github.com/golang/go/issues/51279
	// Fallback to use module version and stop here as vcs information is incomplete.
	if revision == "" {
		if buildInfo.Main.Version != "(devel)" {
			// fallback to use module version (legacy usage)
			rv.Version = buildInfo.Main.Version
		}

		return rv
	}

	if modified {
		revision += "-dirty"
	}
	if gitTag != "" {
		revision = gitTag + "/" + revision
	}
	rv.Version = revision

	if buildTime != "" {
		rv.BuildTime = buildTime
	}

	return rv
}

func (ver Version) String() string {
	return fmt.Sprintf(
		"\ngit hash: %s\nGo version: %s\nBuild time: %s\nPlatform: %s",
		ver.Version,
		ver.GoVersion,
		ver.BuildTime,
		ver.Platform,
	)
}
070701000000C8000081A4000000000000000000000001691F8CFD000000C7000000000000000000000000000000000000002100000000kubelogin-0.2.13/version_test.gopackage main

import "testing"

func Test_loadVersion(t *testing.T) {
	version := loadVersion()
	versionString := version.String()
	if versionString == "" {
		t.Errorf("versionString is empty")
	}
}
07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!1559 blocks
openSUSE Build Service is sponsored by