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
[](https://goreportcard.com/report/github.com/Azure/kubelogin)
[](https://github.com/Azure/kubelogin/actions/workflows/golangci-lint.yml)
[](https://github.com/Azure/kubelogin/actions/workflows/build.yml)
[](https://pkg.go.dev/github.com/Azure/kubelogin)
[](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