mirror of
https://github.com/int128/kubelogin.git
synced 2026-02-28 16:00:19 +00:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8926e8940a | ||
|
|
ce7784b8a0 | ||
|
|
34762216c1 | ||
|
|
878847f937 | ||
|
|
8a392ba25a | ||
|
|
b701a6f0aa | ||
|
|
10091a3238 | ||
|
|
d1b89e3d38 | ||
|
|
e862ac7eac | ||
|
|
d051d80435 | ||
|
|
14e58ac4c2 | ||
|
|
748eb12fc0 | ||
|
|
8b232eeb3e | ||
|
|
0694a1cd0b | ||
|
|
9ddeb33d27 | ||
|
|
64bfc5a465 | ||
|
|
5b2c82fc33 | ||
|
|
1dee4a354e | ||
|
|
336f2b83d5 |
@@ -3,22 +3,22 @@ version: 2.1
|
||||
jobs:
|
||||
test:
|
||||
docker:
|
||||
- image: cimg/go:1.15.2
|
||||
- image: cimg/go:1.15.3
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
keys:
|
||||
- go-sum-{{ checksum "go.sum" }}
|
||||
- run: make -C .circleci install-test-deps
|
||||
- run: make check
|
||||
- run: bash <(curl -s https://codecov.io/bash)
|
||||
- run: make dist
|
||||
- run: mkdir -p /tmp/gotest
|
||||
- run: gotestsum --junitfile /tmp/gotest/gotest.xml -- -v -race -cover -coverprofile=coverage.out ./...
|
||||
- store_test_results:
|
||||
path: /tmp/gotest
|
||||
- save_cache:
|
||||
key: go-sum-{{ checksum "go.sum" }}
|
||||
paths:
|
||||
- ~/go/pkg
|
||||
- store_artifacts:
|
||||
path: gotest.log
|
||||
- run: bash <(curl -s https://codecov.io/bash)
|
||||
|
||||
release:
|
||||
macos:
|
||||
|
||||
2
.github/workflows/golangci-lint.yaml
vendored
2
.github/workflows/golangci-lint.yaml
vendored
@@ -1,5 +1,5 @@
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/workflows/golangci-lint.yaml
|
||||
- '**.go'
|
||||
|
||||
6
.github/workflows/system-test.yaml
vendored
6
.github/workflows/system-test.yaml
vendored
@@ -1,10 +1,14 @@
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/workflows/system-test.yaml
|
||||
- system_test/**
|
||||
- pkg/**
|
||||
- go.*
|
||||
push:
|
||||
# for go mod tidy
|
||||
branches: [master]
|
||||
paths: [go.*]
|
||||
jobs:
|
||||
system-test:
|
||||
# https://help.github.com/en/actions/automating-your-workflow-with-github-actions/software-installed-on-github-hosted-runners#ubuntu-1804-lts
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,7 +4,6 @@
|
||||
|
||||
/dist/output
|
||||
/coverage.out
|
||||
/gotest.log
|
||||
|
||||
/kubelogin
|
||||
/kubectl-oidc_login
|
||||
|
||||
4
Makefile
4
Makefile
@@ -13,10 +13,6 @@ all: $(TARGET)
|
||||
$(TARGET): $(wildcard **/*.go)
|
||||
go build -o $@ -ldflags "$(LDFLAGS)"
|
||||
|
||||
.PHONY: check
|
||||
check:
|
||||
go test -v -race -cover -coverprofile=coverage.out ./... > gotest.log
|
||||
|
||||
.PHONY: dist
|
||||
dist: dist/output
|
||||
dist/output:
|
||||
|
||||
28
README.md
28
README.md
@@ -101,6 +101,21 @@ You got a token with the following claims:
|
||||
}
|
||||
```
|
||||
|
||||
You can increase the log level by `-v1` option.
|
||||
|
||||
```yaml
|
||||
users:
|
||||
- name: oidc
|
||||
user:
|
||||
exec:
|
||||
apiVersion: client.authentication.k8s.io/v1beta1
|
||||
command: kubectl
|
||||
args:
|
||||
- oidc-login
|
||||
- get-token
|
||||
- -v1
|
||||
```
|
||||
|
||||
You can verify kubelogin works with your provider using [acceptance test](acceptance_test).
|
||||
|
||||
|
||||
@@ -123,19 +138,18 @@ You can access the Kubernetes Dashboard using kubelogin and [kauthproxy](https:/
|
||||
This is an open source software licensed under Apache License 2.0.
|
||||
Feel free to open issues and pull requests for improving code and documents.
|
||||
|
||||
Your pull request will be merged into master with squash.
|
||||
|
||||
### Development
|
||||
|
||||
Go 1.13 or later is required.
|
||||
Go 1.15+ is required.
|
||||
|
||||
```sh
|
||||
# Run lint and tests
|
||||
make check
|
||||
|
||||
# Compile and run the command
|
||||
make
|
||||
./kubelogin
|
||||
```
|
||||
|
||||
### System test
|
||||
See also:
|
||||
|
||||
See [system test](system_test) for more.
|
||||
- [system test](system_test)
|
||||
- [acceptance_test](acceptance_test)
|
||||
|
||||
@@ -12,9 +12,11 @@ Flags:
|
||||
--oidc-client-secret string Client secret of the provider
|
||||
--oidc-extra-scope strings Scopes to request to the provider
|
||||
--token-cache-dir string Path to a directory for token cache (default "~/.kube/cache/oidc-login")
|
||||
--certificate-authority string Path to a cert file for the certificate authority
|
||||
--certificate-authority-data string Base64 encoded cert for the certificate authority
|
||||
--certificate-authority stringArray Path to a cert file for the certificate authority
|
||||
--certificate-authority-data stringArray Base64 encoded cert for the certificate authority
|
||||
--insecure-skip-tls-verify If set, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure
|
||||
--tls-renegotiation-once If set, allow a remote server to request renegotiation once per connection
|
||||
--tls-renegotiation-freely If set, allow a remote server to repeatedly request renegotiation
|
||||
--grant-type string Authorization grant type to use. One of (auto|authcode|authcode-keyboard|password) (default "auto")
|
||||
--listen-address strings [authcode] Address to bind to the local server. If multiple addresses are set, it will try binding in order (default [127.0.0.1:8000,127.0.0.1:18000])
|
||||
--skip-open-browser [authcode] Do not open the browser automatically
|
||||
|
||||
9
go.mod
9
go.mod
@@ -3,6 +3,7 @@ module github.com/int128/kubelogin
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/alexflint/go-filemutex v1.1.0
|
||||
github.com/chromedp/chromedp v0.5.3
|
||||
github.com/coreos/go-oidc v2.2.1+incompatible
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
@@ -12,16 +13,16 @@ require (
|
||||
github.com/int128/oauth2cli v1.13.0
|
||||
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4
|
||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
|
||||
github.com/spf13/cobra v1.0.0
|
||||
github.com/spf13/cobra v1.1.1
|
||||
github.com/spf13/pflag v1.0.5
|
||||
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202
|
||||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1
|
||||
google.golang.org/protobuf v1.25.0 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.3.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.3.0
|
||||
k8s.io/apimachinery v0.19.2
|
||||
k8s.io/client-go v0.19.2
|
||||
k8s.io/apimachinery v0.19.3
|
||||
k8s.io/client-go v0.19.3
|
||||
k8s.io/klog v1.0.0
|
||||
)
|
||||
|
||||
84
go.sum
84
go.sum
@@ -22,6 +22,7 @@ cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4g
|
||||
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/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
|
||||
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=
|
||||
@@ -51,9 +52,15 @@ github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/alexflint/go-filemutex v1.1.0 h1:IAWuUuRYL2hETx5b8vCgwnD+xSdlsTQY6s2JjBsqLdg=
|
||||
github.com/alexflint/go-filemutex v1.1.0/go.mod h1:7P4iRhttt/nUvUOrYIhcpMzv2G6CY9UnI16Z+UJqRyk=
|
||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
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/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/chromedp/cdproto v0.0.0-20200116234248-4da64dd111ac h1:T7V5BXqnYd55Hj/g5uhDYumg9Fp3rMTS6bykYtTIFX4=
|
||||
@@ -66,10 +73,10 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
|
||||
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/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjsz9N1i1YxjHAk=
|
||||
github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
@@ -88,6 +95,7 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m
|
||||
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/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
@@ -178,14 +186,32 @@ github.com/google/wire v0.4.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1
|
||||
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/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
|
||||
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
||||
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/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
||||
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
||||
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/imdario/mergo v0.3.5 h1:JboBksRwiiAJWvIYJVo46AfV+IAIKZpfrSzVKj42R4Q=
|
||||
@@ -202,6 +228,7 @@ github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
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/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
||||
@@ -217,12 +244,21 @@ github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM=
|
||||
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
||||
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
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=
|
||||
@@ -239,14 +275,17 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
|
||||
github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4 h1:49lOXmGaUpV9Fz3gd7TFZY106KVlPVa5jcYD1gaQf98=
|
||||
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
|
||||
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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU=
|
||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
@@ -262,22 +301,26 @@ github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40T
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8=
|
||||
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
|
||||
github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4=
|
||||
github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
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/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
|
||||
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
|
||||
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/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
@@ -285,10 +328,9 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
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=
|
||||
@@ -302,6 +344,7 @@ go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
|
||||
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=
|
||||
@@ -341,11 +384,14 @@ 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 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/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-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/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=
|
||||
@@ -354,7 +400,6 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||
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-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
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=
|
||||
@@ -396,9 +441,11 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03i
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
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-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/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=
|
||||
@@ -452,6 +499,7 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3
|
||||
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-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190422233926-fe54fb35175b/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=
|
||||
@@ -462,6 +510,7 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw
|
||||
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-20191112195655-aa38f8e97acc/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=
|
||||
@@ -486,6 +535,7 @@ golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roY
|
||||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
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 h1:W07d4xkoAUSNOkOzdzXCdFGxT7o2rW4q8M34tB2i//k=
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
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=
|
||||
@@ -549,7 +599,6 @@ google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6D
|
||||
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.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
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=
|
||||
@@ -583,6 +632,7 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4=
|
||||
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
@@ -592,6 +642,7 @@ gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
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.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
@@ -603,9 +654,12 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
|
||||
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=
|
||||
k8s.io/api v0.19.2/go.mod h1:IQpK0zFQ1xc5iNIQPqzgoOwuFugaYHK4iCknlAQP9nI=
|
||||
k8s.io/apimachinery v0.19.2/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA=
|
||||
k8s.io/client-go v0.19.2/go.mod h1:S5wPhCqyDNAlzM9CnEdgTGV4OqhsW3jGO1UM1epwfJA=
|
||||
k8s.io/api v0.19.3 h1:GN6ntFnv44Vptj/b+OnMW7FmzkpDoIDLZRvKX3XH9aU=
|
||||
k8s.io/api v0.19.3/go.mod h1:VF+5FT1B74Pw3KxMdKyinLo+zynBaMBiAfGMuldcNDs=
|
||||
k8s.io/apimachinery v0.19.3 h1:bpIQXlKjB4cB/oNpnNnV+BybGPR7iP5oYpsOTEJ4hgc=
|
||||
k8s.io/apimachinery v0.19.3/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA=
|
||||
k8s.io/client-go v0.19.3 h1:ctqR1nQ52NUs6LpI0w+a5U+xjYwflFwA13OJKcicMxg=
|
||||
k8s.io/client-go v0.19.3/go.mod h1:+eEMktZM+MG0KO+PTkci8xnbCZHvj9TqR6Q1XDUIJOM=
|
||||
k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
|
||||
k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
|
||||
k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
|
||||
|
||||
@@ -29,7 +29,7 @@ import (
|
||||
// 4. Verify the output.
|
||||
//
|
||||
func TestCredentialPlugin(t *testing.T) {
|
||||
timeout := 3 * time.Second
|
||||
timeout := 10 * time.Second
|
||||
now := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
tokenCacheDir := t.TempDir()
|
||||
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
// Package certpool provides loading certificates from files or base64 encoded string.
|
||||
package certpool
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/google/wire"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
//go:generate mockgen -destination mock_certpool/mock_certpool.go github.com/int128/kubelogin/pkg/adaptors/certpool Interface
|
||||
|
||||
// Set provides an implementation and interface.
|
||||
var Set = wire.NewSet(
|
||||
wire.Value(NewFunc(New)),
|
||||
wire.Struct(new(CertPool), "*"),
|
||||
wire.Bind(new(Interface), new(*CertPool)),
|
||||
)
|
||||
|
||||
type NewFunc func() Interface
|
||||
|
||||
// New returns an instance which implements the Interface.
|
||||
func New() Interface {
|
||||
return &CertPool{pool: x509.NewCertPool()}
|
||||
}
|
||||
|
||||
type Interface interface {
|
||||
AddFile(filename string) error
|
||||
AddBase64Encoded(s string) error
|
||||
SetRootCAs(cfg *tls.Config)
|
||||
}
|
||||
|
||||
// CertPool represents a pool of certificates.
|
||||
type CertPool struct {
|
||||
pool *x509.CertPool
|
||||
}
|
||||
|
||||
// SetRootCAs sets cfg.RootCAs if it has any certificate.
|
||||
// Otherwise do nothing.
|
||||
func (p *CertPool) SetRootCAs(cfg *tls.Config) {
|
||||
if len(p.pool.Subjects()) > 0 {
|
||||
cfg.RootCAs = p.pool
|
||||
}
|
||||
}
|
||||
|
||||
// AddFile loads the certificate from the file.
|
||||
func (p *CertPool) AddFile(filename string) error {
|
||||
b, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("could not read %s: %w", filename, err)
|
||||
}
|
||||
if !p.pool.AppendCertsFromPEM(b) {
|
||||
return xerrors.Errorf("could not append certificate from %s", filename)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddBase64Encoded loads the certificate from the base64 encoded string.
|
||||
func (p *CertPool) AddBase64Encoded(s string) error {
|
||||
b, err := base64.StdEncoding.DecodeString(s)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("could not decode base64: %w", err)
|
||||
}
|
||||
if !p.pool.AppendCertsFromPEM(b) {
|
||||
return xerrors.Errorf("could not append certificate")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package certpool
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCertPool_AddFile(t *testing.T) {
|
||||
t.Run("Valid", func(t *testing.T) {
|
||||
p := New()
|
||||
if err := p.AddFile("testdata/ca1.crt"); err != nil {
|
||||
t.Errorf("AddFile error: %s", err)
|
||||
}
|
||||
var cfg tls.Config
|
||||
p.SetRootCAs(&cfg)
|
||||
if n := len(cfg.RootCAs.Subjects()); n != 1 {
|
||||
t.Errorf("n wants 1 but was %d", n)
|
||||
}
|
||||
})
|
||||
t.Run("Invalid", func(t *testing.T) {
|
||||
p := New()
|
||||
err := p.AddFile("testdata/Makefile")
|
||||
if err == nil {
|
||||
t.Errorf("AddFile wants an error but was nil")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestCertPool_AddBase64Encoded(t *testing.T) {
|
||||
p := New()
|
||||
if err := p.AddBase64Encoded(readFile(t, "testdata/ca2.crt.base64")); err != nil {
|
||||
t.Errorf("AddBase64Encoded error: %s", err)
|
||||
}
|
||||
var cfg tls.Config
|
||||
p.SetRootCAs(&cfg)
|
||||
if n := len(cfg.RootCAs.Subjects()); n != 1 {
|
||||
t.Errorf("n wants 1 but was %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCertPool_SetRootCAs(t *testing.T) {
|
||||
p := New()
|
||||
var cfg tls.Config
|
||||
p.SetRootCAs(&cfg)
|
||||
if cfg.RootCAs != nil {
|
||||
t.Errorf("cfg.RootCAs wants nil but was %+v", cfg.RootCAs)
|
||||
}
|
||||
}
|
||||
|
||||
func readFile(t *testing.T, filename string) string {
|
||||
t.Helper()
|
||||
b, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
t.Fatalf("ReadFile error: %s", err)
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/int128/kubelogin/pkg/adaptors/certpool (interfaces: Interface)
|
||||
|
||||
// Package mock_certpool is a generated GoMock package.
|
||||
package mock_certpool
|
||||
|
||||
import (
|
||||
tls "crypto/tls"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
reflect "reflect"
|
||||
)
|
||||
|
||||
// MockInterface is a mock of Interface interface.
|
||||
type MockInterface struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockInterfaceMockRecorder
|
||||
}
|
||||
|
||||
// MockInterfaceMockRecorder is the mock recorder for MockInterface.
|
||||
type MockInterfaceMockRecorder struct {
|
||||
mock *MockInterface
|
||||
}
|
||||
|
||||
// NewMockInterface creates a new mock instance.
|
||||
func NewMockInterface(ctrl *gomock.Controller) *MockInterface {
|
||||
mock := &MockInterface{ctrl: ctrl}
|
||||
mock.recorder = &MockInterfaceMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockInterface) EXPECT() *MockInterfaceMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// AddBase64Encoded mocks base method.
|
||||
func (m *MockInterface) AddBase64Encoded(arg0 string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "AddBase64Encoded", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// AddBase64Encoded indicates an expected call of AddBase64Encoded.
|
||||
func (mr *MockInterfaceMockRecorder) AddBase64Encoded(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddBase64Encoded", reflect.TypeOf((*MockInterface)(nil).AddBase64Encoded), arg0)
|
||||
}
|
||||
|
||||
// AddFile mocks base method.
|
||||
func (m *MockInterface) AddFile(arg0 string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "AddFile", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// AddFile indicates an expected call of AddFile.
|
||||
func (mr *MockInterfaceMockRecorder) AddFile(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddFile", reflect.TypeOf((*MockInterface)(nil).AddFile), arg0)
|
||||
}
|
||||
|
||||
// SetRootCAs mocks base method.
|
||||
func (m *MockInterface) SetRootCAs(arg0 *tls.Config) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "SetRootCAs", arg0)
|
||||
}
|
||||
|
||||
// SetRootCAs indicates an expected call of SetRootCAs.
|
||||
func (mr *MockInterfaceMockRecorder) SetRootCAs(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetRootCAs", reflect.TypeOf((*MockInterface)(nil).SetRootCAs), arg0)
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/int128/kubelogin/pkg/testing/logger"
|
||||
"github.com/int128/kubelogin/pkg/tlsclientconfig"
|
||||
"github.com/int128/kubelogin/pkg/usecases/authentication"
|
||||
"github.com/int128/kubelogin/pkg/usecases/authentication/authcode"
|
||||
"github.com/int128/kubelogin/pkg/usecases/authentication/ropc"
|
||||
@@ -95,9 +96,6 @@ func TestCmd_Run(t *testing.T) {
|
||||
KubeconfigFilename: "/path/to/kubeconfig",
|
||||
KubeconfigContext: "hello.k8s.local",
|
||||
KubeconfigUser: "google",
|
||||
CACertFilename: "/path/to/cacert",
|
||||
CACertData: "BASE64ENCODED",
|
||||
SkipTLSVerify: true,
|
||||
GrantOptionSet: authentication.GrantOptionSet{
|
||||
AuthCodeBrowserOption: &authcode.BrowserOption{
|
||||
BindAddress: []string{"127.0.0.1:10080", "127.0.0.1:20080"},
|
||||
@@ -109,6 +107,11 @@ func TestCmd_Run(t *testing.T) {
|
||||
RedirectURLHostname: "localhost",
|
||||
},
|
||||
},
|
||||
TLSClientConfig: tlsclientconfig.Config{
|
||||
CACertFilename: []string{"/path/to/cacert"},
|
||||
CACertData: []string{"BASE64ENCODED"},
|
||||
SkipTLSVerify: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
"GrantType=authcode-keyboard": {
|
||||
@@ -244,14 +247,11 @@ func TestCmd_Run(t *testing.T) {
|
||||
"--password", "PASS",
|
||||
},
|
||||
in: credentialplugin.Input{
|
||||
TokenCacheDir: defaultTokenCacheDir,
|
||||
IssuerURL: "https://issuer.example.com",
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
ExtraScopes: []string{"email", "profile"},
|
||||
CACertFilename: "/path/to/cacert",
|
||||
CACertData: "BASE64ENCODED",
|
||||
SkipTLSVerify: true,
|
||||
TokenCacheDir: defaultTokenCacheDir,
|
||||
IssuerURL: "https://issuer.example.com",
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
ExtraScopes: []string{"email", "profile"},
|
||||
GrantOptionSet: authentication.GrantOptionSet{
|
||||
AuthCodeBrowserOption: &authcode.BrowserOption{
|
||||
BindAddress: []string{"127.0.0.1:10080", "127.0.0.1:20080"},
|
||||
@@ -264,6 +264,11 @@ func TestCmd_Run(t *testing.T) {
|
||||
AuthRequestExtraParams: map[string]string{"ttl": "86400", "reauth": "true"},
|
||||
},
|
||||
},
|
||||
TLSClientConfig: tlsclientconfig.Config{
|
||||
CACertFilename: []string{"/path/to/cacert"},
|
||||
CACertData: []string{"BASE64ENCODED"},
|
||||
SkipTLSVerify: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
"GrantType=authcode-keyboard": {
|
||||
|
||||
@@ -57,15 +57,13 @@ func (cmd *GetToken) New() *cobra.Command {
|
||||
return xerrors.Errorf("get-token: %w", err)
|
||||
}
|
||||
in := credentialplugin.Input{
|
||||
IssuerURL: o.IssuerURL,
|
||||
ClientID: o.ClientID,
|
||||
ClientSecret: o.ClientSecret,
|
||||
ExtraScopes: o.ExtraScopes,
|
||||
CACertFilename: o.tlsOptions.CACertFilename,
|
||||
CACertData: o.tlsOptions.CACertData,
|
||||
SkipTLSVerify: o.tlsOptions.SkipTLSVerify,
|
||||
TokenCacheDir: o.TokenCacheDir,
|
||||
GrantOptionSet: grantOptionSet,
|
||||
IssuerURL: o.IssuerURL,
|
||||
ClientID: o.ClientID,
|
||||
ClientSecret: o.ClientSecret,
|
||||
ExtraScopes: o.ExtraScopes,
|
||||
TokenCacheDir: o.TokenCacheDir,
|
||||
GrantOptionSet: grantOptionSet,
|
||||
TLSClientConfig: o.tlsOptions.tlsClientConfig(),
|
||||
}
|
||||
if err := cmd.GetToken.Do(c.Context(), in); err != nil {
|
||||
return xerrors.Errorf("get-token: %w", err)
|
||||
|
||||
@@ -57,10 +57,8 @@ func (cmd *Root) New() *cobra.Command {
|
||||
KubeconfigFilename: o.Kubeconfig,
|
||||
KubeconfigContext: kubeconfig.ContextName(o.Context),
|
||||
KubeconfigUser: kubeconfig.UserName(o.User),
|
||||
CACertFilename: o.tlsOptions.CACertFilename,
|
||||
CACertData: o.tlsOptions.CACertData,
|
||||
SkipTLSVerify: o.tlsOptions.SkipTLSVerify,
|
||||
GrantOptionSet: grantOptionSet,
|
||||
TLSClientConfig: o.tlsOptions.tlsClientConfig(),
|
||||
}
|
||||
if err := cmd.Standalone.Do(c.Context(), in); err != nil {
|
||||
return xerrors.Errorf("login: %w", err)
|
||||
|
||||
@@ -42,14 +42,12 @@ func (cmd *Setup) New() *cobra.Command {
|
||||
return xerrors.Errorf("setup: %w", err)
|
||||
}
|
||||
in := setup.Stage2Input{
|
||||
IssuerURL: o.IssuerURL,
|
||||
ClientID: o.ClientID,
|
||||
ClientSecret: o.ClientSecret,
|
||||
ExtraScopes: o.ExtraScopes,
|
||||
CACertFilename: o.tlsOptions.CACertFilename,
|
||||
CACertData: o.tlsOptions.CACertData,
|
||||
SkipTLSVerify: o.tlsOptions.SkipTLSVerify,
|
||||
GrantOptionSet: grantOptionSet,
|
||||
IssuerURL: o.IssuerURL,
|
||||
ClientID: o.ClientID,
|
||||
ClientSecret: o.ClientSecret,
|
||||
ExtraScopes: o.ExtraScopes,
|
||||
GrantOptionSet: grantOptionSet,
|
||||
TLSClientConfig: o.tlsOptions.tlsClientConfig(),
|
||||
}
|
||||
if c.Flags().Lookup("listen-address").Changed {
|
||||
in.ListenAddressArgs = o.authenticationOptions.ListenAddress
|
||||
|
||||
@@ -1,15 +1,43 @@
|
||||
package cmd
|
||||
|
||||
import "github.com/spf13/pflag"
|
||||
import (
|
||||
"crypto/tls"
|
||||
|
||||
"github.com/int128/kubelogin/pkg/tlsclientconfig"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
type tlsOptions struct {
|
||||
CACertFilename string
|
||||
CACertData string
|
||||
SkipTLSVerify bool
|
||||
CACertFilename []string
|
||||
CACertData []string
|
||||
SkipTLSVerify bool
|
||||
RenegotiateOnceAsClient bool
|
||||
RenegotiateFreelyAsClient bool
|
||||
}
|
||||
|
||||
func (o *tlsOptions) addFlags(f *pflag.FlagSet) {
|
||||
f.StringVar(&o.CACertFilename, "certificate-authority", "", "Path to a cert file for the certificate authority")
|
||||
f.StringVar(&o.CACertData, "certificate-authority-data", "", "Base64 encoded cert for the certificate authority")
|
||||
f.StringArrayVar(&o.CACertFilename, "certificate-authority", nil, "Path to a cert file for the certificate authority")
|
||||
f.StringArrayVar(&o.CACertData, "certificate-authority-data", nil, "Base64 encoded cert for the certificate authority")
|
||||
f.BoolVar(&o.SkipTLSVerify, "insecure-skip-tls-verify", false, "If set, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure")
|
||||
f.BoolVar(&o.RenegotiateOnceAsClient, "tls-renegotiation-once", false, "If set, allow a remote server to request renegotiation once per connection")
|
||||
f.BoolVar(&o.RenegotiateFreelyAsClient, "tls-renegotiation-freely", false, "If set, allow a remote server to repeatedly request renegotiation")
|
||||
}
|
||||
|
||||
func (o tlsOptions) tlsClientConfig() tlsclientconfig.Config {
|
||||
return tlsclientconfig.Config{
|
||||
CACertFilename: o.CACertFilename,
|
||||
CACertData: o.CACertData,
|
||||
SkipTLSVerify: o.SkipTLSVerify,
|
||||
Renegotiation: o.renegotiationSupport(),
|
||||
}
|
||||
}
|
||||
|
||||
func (o tlsOptions) renegotiationSupport() tls.RenegotiationSupport {
|
||||
if o.RenegotiateOnceAsClient {
|
||||
return tls.RenegotiateOnceAsClient
|
||||
}
|
||||
if o.RenegotiateFreelyAsClient {
|
||||
return tls.RenegotiateFreelyAsClient
|
||||
}
|
||||
return tls.RenegotiateNever
|
||||
}
|
||||
|
||||
92
pkg/adaptors/cmd/tls_test.go
Normal file
92
pkg/adaptors/cmd/tls_test.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/int128/kubelogin/pkg/tlsclientconfig"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
func Test_tlsOptions_tlsClientConfig(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
args []string
|
||||
want tlsclientconfig.Config
|
||||
}{
|
||||
"NoFlag": {},
|
||||
"SkipTLSVerify": {
|
||||
args: []string{
|
||||
"--insecure-skip-tls-verify",
|
||||
},
|
||||
want: tlsclientconfig.Config{
|
||||
SkipTLSVerify: true,
|
||||
},
|
||||
},
|
||||
"CACertFilename1": {
|
||||
args: []string{
|
||||
"--certificate-authority", "/path/to/cert1",
|
||||
},
|
||||
want: tlsclientconfig.Config{
|
||||
CACertFilename: []string{"/path/to/cert1"},
|
||||
},
|
||||
},
|
||||
"CACertFilename2": {
|
||||
args: []string{
|
||||
"--certificate-authority", "/path/to/cert1",
|
||||
"--certificate-authority", "/path/to/cert2",
|
||||
},
|
||||
want: tlsclientconfig.Config{
|
||||
CACertFilename: []string{"/path/to/cert1", "/path/to/cert2"},
|
||||
},
|
||||
},
|
||||
"CACertData1": {
|
||||
args: []string{
|
||||
"--certificate-authority-data", "base64encoded1",
|
||||
},
|
||||
want: tlsclientconfig.Config{
|
||||
CACertData: []string{"base64encoded1"},
|
||||
},
|
||||
},
|
||||
"CACertData2": {
|
||||
args: []string{
|
||||
"--certificate-authority-data", "base64encoded1",
|
||||
"--certificate-authority-data", "base64encoded2",
|
||||
},
|
||||
want: tlsclientconfig.Config{
|
||||
CACertData: []string{"base64encoded1", "base64encoded2"},
|
||||
},
|
||||
},
|
||||
"RenegotiateOnceAsClient": {
|
||||
args: []string{
|
||||
"--tls-renegotiation-once",
|
||||
},
|
||||
want: tlsclientconfig.Config{
|
||||
Renegotiation: tls.RenegotiateOnceAsClient,
|
||||
},
|
||||
},
|
||||
"RenegotiateFreelyAsClient": {
|
||||
args: []string{
|
||||
"--tls-renegotiation-freely",
|
||||
},
|
||||
want: tlsclientconfig.Config{
|
||||
Renegotiation: tls.RenegotiateFreelyAsClient,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, c := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
var o tlsOptions
|
||||
f := pflag.NewFlagSet("", pflag.ContinueOnError)
|
||||
o.addFlags(f)
|
||||
if err := f.Parse(c.args); err != nil {
|
||||
t.Fatalf("Parse error: %s", err)
|
||||
}
|
||||
got := o.tlsClientConfig()
|
||||
if diff := cmp.Diff(c.want, got); diff != "" {
|
||||
t.Errorf("mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
64
pkg/adaptors/mutex/mock_mutex/mock_mutex.go
Normal file
64
pkg/adaptors/mutex/mock_mutex/mock_mutex.go
Normal file
@@ -0,0 +1,64 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/int128/kubelogin/pkg/adaptors/mutex (interfaces: Interface)
|
||||
|
||||
// Package mock_mutex is a generated GoMock package.
|
||||
package mock_mutex
|
||||
|
||||
import (
|
||||
context "context"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
mutex "github.com/int128/kubelogin/pkg/adaptors/mutex"
|
||||
reflect "reflect"
|
||||
)
|
||||
|
||||
// MockInterface is a mock of Interface interface
|
||||
type MockInterface struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockInterfaceMockRecorder
|
||||
}
|
||||
|
||||
// MockInterfaceMockRecorder is the mock recorder for MockInterface
|
||||
type MockInterfaceMockRecorder struct {
|
||||
mock *MockInterface
|
||||
}
|
||||
|
||||
// NewMockInterface creates a new mock instance
|
||||
func NewMockInterface(ctrl *gomock.Controller) *MockInterface {
|
||||
mock := &MockInterface{ctrl: ctrl}
|
||||
mock.recorder = &MockInterfaceMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockInterface) EXPECT() *MockInterfaceMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Acquire mocks base method
|
||||
func (m *MockInterface) Acquire(arg0 context.Context, arg1 string) (*mutex.Lock, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Acquire", arg0, arg1)
|
||||
ret0, _ := ret[0].(*mutex.Lock)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Acquire indicates an expected call of Acquire
|
||||
func (mr *MockInterfaceMockRecorder) Acquire(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Acquire", reflect.TypeOf((*MockInterface)(nil).Acquire), arg0, arg1)
|
||||
}
|
||||
|
||||
// Release mocks base method
|
||||
func (m *MockInterface) Release(arg0 *mutex.Lock) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Release", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Release indicates an expected call of Release
|
||||
func (mr *MockInterfaceMockRecorder) Release(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Release", reflect.TypeOf((*MockInterface)(nil).Release), arg0)
|
||||
}
|
||||
89
pkg/adaptors/mutex/mutex.go
Normal file
89
pkg/adaptors/mutex/mutex.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package mutex
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/alexflint/go-filemutex"
|
||||
"github.com/google/wire"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/logger"
|
||||
"golang.org/x/xerrors"
|
||||
"os"
|
||||
"path"
|
||||
)
|
||||
|
||||
//go:generate mockgen -destination mock_mutex/mock_mutex.go github.com/int128/kubelogin/pkg/adaptors/mutex Interface
|
||||
|
||||
var Set = wire.NewSet(
|
||||
wire.Struct(new(Mutex), "*"),
|
||||
wire.Bind(new(Interface), new(*Mutex)),
|
||||
)
|
||||
|
||||
type Interface interface {
|
||||
Acquire(ctx context.Context, name string) (*Lock, error)
|
||||
Release(lock *Lock) error
|
||||
}
|
||||
|
||||
// Lock holds the lock data.
|
||||
type Lock struct {
|
||||
Data interface{}
|
||||
Name string
|
||||
}
|
||||
|
||||
type Mutex struct {
|
||||
Logger logger.Interface
|
||||
}
|
||||
|
||||
// internalAcquire wait for acquisition of the lock
|
||||
func internalAcquire(fm *filemutex.FileMutex) chan error {
|
||||
result := make(chan error)
|
||||
go func() {
|
||||
if err := fm.Lock(); err != nil {
|
||||
result <- err
|
||||
}
|
||||
close(result)
|
||||
}()
|
||||
return result
|
||||
}
|
||||
|
||||
// internalRelease disposes of resources associated with a lock
|
||||
func internalRelease(fm *filemutex.FileMutex, lfn string, log logger.Interface) error {
|
||||
err := fm.Close()
|
||||
if err != nil {
|
||||
log.V(1).Infof("Error closing lock file %s: %s", lfn, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// LockFileName get the lock file name from the lock name.
|
||||
func LockFileName(name string) string {
|
||||
return path.Join(os.TempDir(), fmt.Sprintf(".kubelogin.%s.lock", name))
|
||||
}
|
||||
|
||||
// Acquire acquire a lock for the specified name. The context could be used to set a timeout.
|
||||
func (m *Mutex) Acquire(ctx context.Context, name string) (*Lock, error) {
|
||||
lfn := LockFileName(name)
|
||||
fm, err := filemutex.New(lfn)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("error creating mutex file %s: %w", lfn, err)
|
||||
}
|
||||
|
||||
lockChan := internalAcquire(fm)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
_ = internalRelease(fm, lfn, m.Logger)
|
||||
return nil, ctx.Err()
|
||||
case err := <-lockChan:
|
||||
if err != nil {
|
||||
_ = internalRelease(fm, lfn, m.Logger)
|
||||
return nil, xerrors.Errorf("error acquiring lock on file %s: %w", lfn, err)
|
||||
}
|
||||
return &Lock{Data: fm, Name: name}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Release release the specified lock
|
||||
func (m *Mutex) Release(lock *Lock) error {
|
||||
fm := lock.Data.(*filemutex.FileMutex)
|
||||
lfn := LockFileName(lock.Name)
|
||||
return internalRelease(fm, lfn, m.Logger)
|
||||
}
|
||||
64
pkg/adaptors/mutex/mutex_test.go
Normal file
64
pkg/adaptors/mutex/mutex_test.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package mutex
|
||||
|
||||
import (
|
||||
"github.com/int128/kubelogin/pkg/adaptors/logger"
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/xerrors"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestMutex(t *testing.T) {
|
||||
|
||||
t.Run("Test successful parallel acquisition with no reentry allowed", func(t *testing.T) {
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
nbConcurrency := 20
|
||||
wg := sync.WaitGroup{}
|
||||
events := make(chan int, nbConcurrency*2)
|
||||
errors := make(chan error, nbConcurrency)
|
||||
doLockUnlock := func() {
|
||||
defer wg.Done()
|
||||
|
||||
m := Mutex{
|
||||
Logger: logger.New(),
|
||||
}
|
||||
if mutex, err := m.Acquire(ctx, "test"); err == nil {
|
||||
events <- 1
|
||||
var dur = time.Duration(rand.Intn(5000))
|
||||
time.Sleep(dur * time.Microsecond)
|
||||
events <- -1
|
||||
if err := m.Release(mutex); err != nil {
|
||||
errors <- xerrors.Errorf("Release error: %w", err)
|
||||
}
|
||||
} else {
|
||||
errors <- xerrors.Errorf("Acquire error: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < nbConcurrency; i++ {
|
||||
wg.Add(1)
|
||||
go doLockUnlock()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
close(events)
|
||||
close(errors)
|
||||
|
||||
countConcurrent := 0
|
||||
for delta := range events {
|
||||
countConcurrent += delta
|
||||
if countConcurrent > 1 {
|
||||
t.Errorf("The mutex did not prevented reentry: %d", countConcurrent)
|
||||
}
|
||||
}
|
||||
|
||||
for anError := range errors {
|
||||
t.Errorf("The gorouting returned an error: %s", anError)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -3,51 +3,46 @@ package oidcclient
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/coreos/go-oidc"
|
||||
gooidc "github.com/coreos/go-oidc"
|
||||
"github.com/google/wire"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/certpool"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/clock"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/logger"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/oidcclient/logging"
|
||||
"github.com/int128/kubelogin/pkg/oidc"
|
||||
"github.com/int128/kubelogin/pkg/tlsclientconfig"
|
||||
"github.com/int128/kubelogin/pkg/tlsclientconfig/loader"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
//go:generate mockgen -destination mock_oidcclient/mock_factory.go github.com/int128/kubelogin/pkg/adaptors/oidcclient FactoryInterface
|
||||
|
||||
var Set = wire.NewSet(
|
||||
wire.Struct(new(Factory), "*"),
|
||||
wire.Bind(new(FactoryInterface), new(*Factory)),
|
||||
)
|
||||
|
||||
type FactoryInterface interface {
|
||||
New(ctx context.Context, config Config) (Interface, error)
|
||||
}
|
||||
|
||||
// Config represents a configuration of OpenID Connect client.
|
||||
type Config struct {
|
||||
IssuerURL string
|
||||
ClientID string
|
||||
ClientSecret string
|
||||
ExtraScopes []string // optional
|
||||
CertPool certpool.Interface
|
||||
SkipTLSVerify bool
|
||||
New(ctx context.Context, p oidc.Provider, tlsClientConfig tlsclientconfig.Config) (Interface, error)
|
||||
}
|
||||
|
||||
type Factory struct {
|
||||
Loader loader.Loader
|
||||
Clock clock.Interface
|
||||
Logger logger.Interface
|
||||
}
|
||||
|
||||
// New returns an instance of adaptors.Interface with the given configuration.
|
||||
func (f *Factory) New(ctx context.Context, config Config) (Interface, error) {
|
||||
var tlsConfig tls.Config
|
||||
tlsConfig.InsecureSkipVerify = config.SkipTLSVerify
|
||||
config.CertPool.SetRootCAs(&tlsConfig)
|
||||
func (f *Factory) New(ctx context.Context, p oidc.Provider, tlsClientConfig tlsclientconfig.Config) (Interface, error) {
|
||||
rawTLSClientConfig, err := f.Loader.Load(tlsClientConfig)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("could not load the TLS client config: %w", err)
|
||||
}
|
||||
baseTransport := &http.Transport{
|
||||
TLSClientConfig: &tlsConfig,
|
||||
TLSClientConfig: rawTLSClientConfig,
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
}
|
||||
loggingTransport := &logging.Transport{
|
||||
@@ -59,7 +54,7 @@ func (f *Factory) New(ctx context.Context, config Config) (Interface, error) {
|
||||
}
|
||||
|
||||
ctx = context.WithValue(ctx, oauth2.HTTPClient, httpClient)
|
||||
provider, err := oidc.NewProvider(ctx, config.IssuerURL)
|
||||
provider, err := gooidc.NewProvider(ctx, p.IssuerURL)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("oidc discovery error: %w", err)
|
||||
}
|
||||
@@ -72,9 +67,9 @@ func (f *Factory) New(ctx context.Context, config Config) (Interface, error) {
|
||||
provider: provider,
|
||||
oauth2Config: oauth2.Config{
|
||||
Endpoint: provider.Endpoint(),
|
||||
ClientID: config.ClientID,
|
||||
ClientSecret: config.ClientSecret,
|
||||
Scopes: append(config.ExtraScopes, oidc.ScopeOpenID),
|
||||
ClientID: p.ClientID,
|
||||
ClientSecret: p.ClientSecret,
|
||||
Scopes: append(p.ExtraScopes, gooidc.ScopeOpenID),
|
||||
},
|
||||
clock: f.Clock,
|
||||
logger: f.Logger,
|
||||
@@ -82,7 +77,7 @@ func (f *Factory) New(ctx context.Context, config Config) (Interface, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func extractSupportedPKCEMethods(provider *oidc.Provider) ([]string, error) {
|
||||
func extractSupportedPKCEMethods(provider *gooidc.Provider) ([]string, error) {
|
||||
var d struct {
|
||||
CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported"`
|
||||
}
|
||||
|
||||
52
pkg/adaptors/oidcclient/mock_oidcclient/mock_factory.go
Normal file
52
pkg/adaptors/oidcclient/mock_oidcclient/mock_factory.go
Normal file
@@ -0,0 +1,52 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/int128/kubelogin/pkg/adaptors/oidcclient (interfaces: FactoryInterface)
|
||||
|
||||
// Package mock_oidcclient is a generated GoMock package.
|
||||
package mock_oidcclient
|
||||
|
||||
import (
|
||||
context "context"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
oidcclient "github.com/int128/kubelogin/pkg/adaptors/oidcclient"
|
||||
oidc "github.com/int128/kubelogin/pkg/oidc"
|
||||
tlsclientconfig "github.com/int128/kubelogin/pkg/tlsclientconfig"
|
||||
reflect "reflect"
|
||||
)
|
||||
|
||||
// MockFactoryInterface is a mock of FactoryInterface interface.
|
||||
type MockFactoryInterface struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockFactoryInterfaceMockRecorder
|
||||
}
|
||||
|
||||
// MockFactoryInterfaceMockRecorder is the mock recorder for MockFactoryInterface.
|
||||
type MockFactoryInterfaceMockRecorder struct {
|
||||
mock *MockFactoryInterface
|
||||
}
|
||||
|
||||
// NewMockFactoryInterface creates a new mock instance.
|
||||
func NewMockFactoryInterface(ctrl *gomock.Controller) *MockFactoryInterface {
|
||||
mock := &MockFactoryInterface{ctrl: ctrl}
|
||||
mock.recorder = &MockFactoryInterfaceMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockFactoryInterface) EXPECT() *MockFactoryInterfaceMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// New mocks base method.
|
||||
func (m *MockFactoryInterface) New(arg0 context.Context, arg1 oidc.Provider, arg2 tlsclientconfig.Config) (oidcclient.Interface, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "New", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(oidcclient.Interface)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// New indicates an expected call of New.
|
||||
func (mr *MockFactoryInterfaceMockRecorder) New(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "New", reflect.TypeOf((*MockFactoryInterface)(nil).New), arg0, arg1, arg2)
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
gooidc "github.com/coreos/go-oidc"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/clock"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/logger"
|
||||
"github.com/int128/kubelogin/pkg/jwt"
|
||||
"github.com/int128/kubelogin/pkg/oidc"
|
||||
"github.com/int128/kubelogin/pkg/pkce"
|
||||
"github.com/int128/oauth2cli"
|
||||
@@ -184,17 +183,8 @@ func (c *client) verifyToken(ctx context.Context, token *oauth2.Token, nonce str
|
||||
if nonce != "" && nonce != verifiedIDToken.Nonce {
|
||||
return nil, xerrors.Errorf("nonce did not match (wants %s but got %s)", nonce, verifiedIDToken.Nonce)
|
||||
}
|
||||
pretty, err := jwt.DecodePayloadAsPrettyJSON(idToken)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("could not decode the token: %w", err)
|
||||
}
|
||||
return &oidc.TokenSet{
|
||||
IDToken: idToken,
|
||||
IDTokenClaims: jwt.Claims{
|
||||
Subject: verifiedIDToken.Subject,
|
||||
Expiry: verifiedIDToken.Expiry,
|
||||
Pretty: pretty,
|
||||
},
|
||||
IDToken: idToken,
|
||||
RefreshToken: token.RefreshToken,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ package mock_tokencache
|
||||
import (
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
tokencache "github.com/int128/kubelogin/pkg/adaptors/tokencache"
|
||||
oidc "github.com/int128/kubelogin/pkg/oidc"
|
||||
reflect "reflect"
|
||||
)
|
||||
|
||||
@@ -34,10 +35,10 @@ func (m *MockInterface) EXPECT() *MockInterfaceMockRecorder {
|
||||
}
|
||||
|
||||
// FindByKey mocks base method.
|
||||
func (m *MockInterface) FindByKey(arg0 string, arg1 tokencache.Key) (*tokencache.Value, error) {
|
||||
func (m *MockInterface) FindByKey(arg0 string, arg1 tokencache.Key) (*oidc.TokenSet, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "FindByKey", arg0, arg1)
|
||||
ret0, _ := ret[0].(*tokencache.Value)
|
||||
ret0, _ := ret[0].(*oidc.TokenSet)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
@@ -49,7 +50,7 @@ func (mr *MockInterfaceMockRecorder) FindByKey(arg0, arg1 interface{}) *gomock.C
|
||||
}
|
||||
|
||||
// Save mocks base method.
|
||||
func (m *MockInterface) Save(arg0 string, arg1 tokencache.Key, arg2 tokencache.Value) error {
|
||||
func (m *MockInterface) Save(arg0 string, arg1 tokencache.Key, arg2 oidc.TokenSet) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Save", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(error)
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/google/wire"
|
||||
"github.com/int128/kubelogin/pkg/oidc"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
@@ -21,8 +22,8 @@ var Set = wire.NewSet(
|
||||
)
|
||||
|
||||
type Interface interface {
|
||||
FindByKey(dir string, key Key) (*Value, error)
|
||||
Save(dir string, key Key, value Value) error
|
||||
FindByKey(dir string, key Key) (*oidc.TokenSet, error)
|
||||
Save(dir string, key Key, tokenSet oidc.TokenSet) error
|
||||
}
|
||||
|
||||
// Key represents a key of a token cache.
|
||||
@@ -30,14 +31,14 @@ type Key struct {
|
||||
IssuerURL string
|
||||
ClientID string
|
||||
ClientSecret string
|
||||
Username string
|
||||
ExtraScopes []string
|
||||
CACertFilename string
|
||||
CACertData string
|
||||
SkipTLSVerify bool
|
||||
}
|
||||
|
||||
// Value represents a value of a token cache.
|
||||
type Value struct {
|
||||
type entity struct {
|
||||
IDToken string `json:"id_token,omitempty"`
|
||||
RefreshToken string `json:"refresh_token,omitempty"`
|
||||
}
|
||||
@@ -46,7 +47,7 @@ type Value struct {
|
||||
// Filename of a token cache is sha256 digest of the issuer, zero-character and client ID.
|
||||
type Repository struct{}
|
||||
|
||||
func (r *Repository) FindByKey(dir string, key Key) (*Value, error) {
|
||||
func (r *Repository) FindByKey(dir string, key Key) (*oidc.TokenSet, error) {
|
||||
filename, err := computeFilename(key)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("could not compute the key: %w", err)
|
||||
@@ -58,14 +59,17 @@ func (r *Repository) FindByKey(dir string, key Key) (*Value, error) {
|
||||
}
|
||||
defer f.Close()
|
||||
d := json.NewDecoder(f)
|
||||
var c Value
|
||||
if err := d.Decode(&c); err != nil {
|
||||
var e entity
|
||||
if err := d.Decode(&e); err != nil {
|
||||
return nil, xerrors.Errorf("invalid json file %s: %w", p, err)
|
||||
}
|
||||
return &c, nil
|
||||
return &oidc.TokenSet{
|
||||
IDToken: e.IDToken,
|
||||
RefreshToken: e.RefreshToken,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *Repository) Save(dir string, key Key, value Value) error {
|
||||
func (r *Repository) Save(dir string, key Key, tokenSet oidc.TokenSet) error {
|
||||
if err := os.MkdirAll(dir, 0700); err != nil {
|
||||
return xerrors.Errorf("could not create directory %s: %w", dir, err)
|
||||
}
|
||||
@@ -79,8 +83,11 @@ func (r *Repository) Save(dir string, key Key, value Value) error {
|
||||
return xerrors.Errorf("could not create file %s: %w", p, err)
|
||||
}
|
||||
defer f.Close()
|
||||
e := json.NewEncoder(f)
|
||||
if err := e.Encode(&value); err != nil {
|
||||
e := entity{
|
||||
IDToken: tokenSet.IDToken,
|
||||
RefreshToken: tokenSet.RefreshToken,
|
||||
}
|
||||
if err := json.NewEncoder(f).Encode(&e); err != nil {
|
||||
return xerrors.Errorf("json encode error: %w", err)
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/int128/kubelogin/pkg/oidc"
|
||||
)
|
||||
|
||||
func TestRepository_FindByKey(t *testing.T) {
|
||||
@@ -31,12 +32,12 @@ func TestRepository_FindByKey(t *testing.T) {
|
||||
t.Fatalf("could not write to the temp file: %s", err)
|
||||
}
|
||||
|
||||
value, err := r.FindByKey(dir, key)
|
||||
got, err := r.FindByKey(dir, key)
|
||||
if err != nil {
|
||||
t.Errorf("err wants nil but %+v", err)
|
||||
}
|
||||
want := &Value{IDToken: "YOUR_ID_TOKEN", RefreshToken: "YOUR_REFRESH_TOKEN"}
|
||||
if diff := cmp.Diff(want, value); diff != "" {
|
||||
want := &oidc.TokenSet{IDToken: "YOUR_ID_TOKEN", RefreshToken: "YOUR_REFRESH_TOKEN"}
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
t.Errorf("mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
@@ -55,8 +56,8 @@ func TestRepository_Save(t *testing.T) {
|
||||
CACertFilename: "/path/to/cert",
|
||||
SkipTLSVerify: false,
|
||||
}
|
||||
value := Value{IDToken: "YOUR_ID_TOKEN", RefreshToken: "YOUR_REFRESH_TOKEN"}
|
||||
if err := r.Save(dir, key, value); err != nil {
|
||||
tokenSet := oidc.TokenSet{IDToken: "YOUR_ID_TOKEN", RefreshToken: "YOUR_REFRESH_TOKEN"}
|
||||
if err := r.Save(dir, key, tokenSet); err != nil {
|
||||
t.Errorf("err wants nil but %+v", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -6,16 +6,17 @@ package di
|
||||
import (
|
||||
"github.com/google/wire"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/browser"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/certpool"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/clock"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/cmd"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/credentialpluginwriter"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/kubeconfig"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/logger"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/mutex"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/oidcclient"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/reader"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/stdio"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/tokencache"
|
||||
"github.com/int128/kubelogin/pkg/tlsclientconfig/loader"
|
||||
"github.com/int128/kubelogin/pkg/usecases/authentication"
|
||||
"github.com/int128/kubelogin/pkg/usecases/credentialplugin"
|
||||
"github.com/int128/kubelogin/pkg/usecases/setup"
|
||||
@@ -51,8 +52,9 @@ func NewCmdForHeadless(clock.Interface, stdio.Stdin, stdio.Stdout, logger.Interf
|
||||
kubeconfig.Set,
|
||||
tokencache.Set,
|
||||
oidcclient.Set,
|
||||
certpool.Set,
|
||||
loader.Set,
|
||||
credentialpluginwriter.Set,
|
||||
mutex.Set,
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -7,16 +7,17 @@ package di
|
||||
|
||||
import (
|
||||
"github.com/int128/kubelogin/pkg/adaptors/browser"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/certpool"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/clock"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/cmd"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/credentialpluginwriter"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/kubeconfig"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/logger"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/mutex"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/oidcclient"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/reader"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/stdio"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/tokencache"
|
||||
"github.com/int128/kubelogin/pkg/tlsclientconfig/loader"
|
||||
"github.com/int128/kubelogin/pkg/usecases/authentication"
|
||||
"github.com/int128/kubelogin/pkg/usecases/authentication/authcode"
|
||||
"github.com/int128/kubelogin/pkg/usecases/authentication/ropc"
|
||||
@@ -46,7 +47,9 @@ var (
|
||||
|
||||
// NewCmdForHeadless returns an instance of adaptors.Cmd for headless testing.
|
||||
func NewCmdForHeadless(clockInterface clock.Interface, stdin stdio.Stdin, stdout stdio.Stdout, loggerInterface logger.Interface, browserInterface browser.Interface) cmd.Interface {
|
||||
loaderLoader := loader.Loader{}
|
||||
factory := &oidcclient.Factory{
|
||||
Loader: loaderLoader,
|
||||
Clock: clockInterface,
|
||||
Logger: loggerInterface,
|
||||
}
|
||||
@@ -76,11 +79,9 @@ func NewCmdForHeadless(clockInterface clock.Interface, stdin stdio.Stdin, stdout
|
||||
kubeconfigKubeconfig := &kubeconfig.Kubeconfig{
|
||||
Logger: loggerInterface,
|
||||
}
|
||||
newFunc := _wireNewFuncValue
|
||||
standaloneStandalone := &standalone.Standalone{
|
||||
Authentication: authenticationAuthentication,
|
||||
Kubeconfig: kubeconfigKubeconfig,
|
||||
NewCertPool: newFunc,
|
||||
Logger: loggerInterface,
|
||||
}
|
||||
root := &cmd.Root{
|
||||
@@ -91,11 +92,14 @@ func NewCmdForHeadless(clockInterface clock.Interface, stdin stdio.Stdin, stdout
|
||||
writer := &credentialpluginwriter.Writer{
|
||||
Stdout: stdout,
|
||||
}
|
||||
mutexMutex := &mutex.Mutex{
|
||||
Logger: loggerInterface,
|
||||
}
|
||||
getToken := &credentialplugin.GetToken{
|
||||
Authentication: authenticationAuthentication,
|
||||
TokenCacheRepository: repository,
|
||||
NewCertPool: newFunc,
|
||||
Writer: writer,
|
||||
Mutex: mutexMutex,
|
||||
Logger: loggerInterface,
|
||||
}
|
||||
cmdGetToken := &cmd.GetToken{
|
||||
@@ -104,7 +108,6 @@ func NewCmdForHeadless(clockInterface clock.Interface, stdin stdio.Stdin, stdout
|
||||
}
|
||||
setupSetup := &setup.Setup{
|
||||
Authentication: authenticationAuthentication,
|
||||
NewCertPool: newFunc,
|
||||
Logger: loggerInterface,
|
||||
}
|
||||
cmdSetup := &cmd.Setup{
|
||||
@@ -118,7 +121,3 @@ func NewCmdForHeadless(clockInterface clock.Interface, stdin stdio.Stdin, stdout
|
||||
}
|
||||
return cmdCmd
|
||||
}
|
||||
|
||||
var (
|
||||
_wireNewFuncValue = certpool.NewFunc(certpool.New)
|
||||
)
|
||||
|
||||
@@ -9,12 +9,22 @@ import (
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// TokenSet represents an output DTO of
|
||||
// Interface.GetTokenByAuthCode, Interface.GetTokenByROPC and Interface.Refresh.
|
||||
// Provider represents an OIDC provider.
|
||||
type Provider struct {
|
||||
IssuerURL string
|
||||
ClientID string
|
||||
ClientSecret string // optional
|
||||
ExtraScopes []string // optional
|
||||
}
|
||||
|
||||
// TokenSet represents a set of ID token and refresh token.
|
||||
type TokenSet struct {
|
||||
IDToken string
|
||||
RefreshToken string
|
||||
IDTokenClaims jwt.Claims
|
||||
IDToken string
|
||||
RefreshToken string
|
||||
}
|
||||
|
||||
func (ts TokenSet) DecodeWithoutVerify() (*jwt.Claims, error) {
|
||||
return jwt.DecodeWithoutVerify(ts.IDToken)
|
||||
}
|
||||
|
||||
func NewState() (string, error) {
|
||||
|
||||
11
pkg/tlsclientconfig/config.go
Normal file
11
pkg/tlsclientconfig/config.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package tlsclientconfig
|
||||
|
||||
import "crypto/tls"
|
||||
|
||||
// Config represents a config for TLS client.
|
||||
type Config struct {
|
||||
CACertFilename []string
|
||||
CACertData []string
|
||||
SkipTLSVerify bool
|
||||
Renegotiation tls.RenegotiationSupport
|
||||
}
|
||||
71
pkg/tlsclientconfig/loader/load.go
Normal file
71
pkg/tlsclientconfig/loader/load.go
Normal file
@@ -0,0 +1,71 @@
|
||||
// Package loader provides loading certificates from files or base64 encoded string.
|
||||
package loader
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/google/wire"
|
||||
"github.com/int128/kubelogin/pkg/tlsclientconfig"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// Set provides an implementation and interface.
|
||||
var Set = wire.NewSet(
|
||||
wire.Struct(new(Loader), "*"),
|
||||
wire.Bind(new(Interface), new(*Loader)),
|
||||
)
|
||||
|
||||
type Interface interface {
|
||||
Load(config tlsclientconfig.Config) (*tls.Config, error)
|
||||
}
|
||||
|
||||
// Loader represents a pool of certificates.
|
||||
type Loader struct{}
|
||||
|
||||
func (l *Loader) Load(config tlsclientconfig.Config) (*tls.Config, error) {
|
||||
rootCAs := x509.NewCertPool()
|
||||
for _, f := range config.CACertFilename {
|
||||
if err := addFile(rootCAs, f); err != nil {
|
||||
return nil, xerrors.Errorf("could not load the certificate from %s: %w", f, err)
|
||||
}
|
||||
}
|
||||
for _, d := range config.CACertData {
|
||||
if err := addBase64Encoded(rootCAs, d); err != nil {
|
||||
return nil, xerrors.Errorf("could not load the certificate: %w", err)
|
||||
}
|
||||
}
|
||||
if len(rootCAs.Subjects()) == 0 {
|
||||
// use the host's root CA set
|
||||
rootCAs = nil
|
||||
}
|
||||
return &tls.Config{
|
||||
RootCAs: rootCAs,
|
||||
InsecureSkipVerify: config.SkipTLSVerify,
|
||||
Renegotiation: config.Renegotiation,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func addFile(p *x509.CertPool, filename string) error {
|
||||
b, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("could not read: %w", err)
|
||||
}
|
||||
if !p.AppendCertsFromPEM(b) {
|
||||
return xerrors.New("invalid certificate")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func addBase64Encoded(p *x509.CertPool, s string) error {
|
||||
b, err := base64.StdEncoding.DecodeString(s)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("could not decode base64: %w", err)
|
||||
}
|
||||
if !p.AppendCertsFromPEM(b) {
|
||||
return xerrors.New("invalid certificate")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
60
pkg/tlsclientconfig/loader/load_test.go
Normal file
60
pkg/tlsclientconfig/loader/load_test.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package loader
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/int128/kubelogin/pkg/tlsclientconfig"
|
||||
)
|
||||
|
||||
func TestLoader_Load(t *testing.T) {
|
||||
var loader Loader
|
||||
t.Run("Zero", func(t *testing.T) {
|
||||
cfg, err := loader.Load(tlsclientconfig.Config{})
|
||||
if err != nil {
|
||||
t.Errorf("Load error: %s", err)
|
||||
}
|
||||
if cfg.RootCAs != nil {
|
||||
t.Errorf("RootCAs wants nil but was %+v", cfg.RootCAs)
|
||||
}
|
||||
})
|
||||
t.Run("ValidFile", func(t *testing.T) {
|
||||
cfg, err := loader.Load(tlsclientconfig.Config{
|
||||
CACertFilename: []string{"testdata/ca1.crt"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("Load error: %s", err)
|
||||
}
|
||||
if n := len(cfg.RootCAs.Subjects()); n != 1 {
|
||||
t.Errorf("n wants 1 but was %d", n)
|
||||
}
|
||||
})
|
||||
t.Run("InvalidFile", func(t *testing.T) {
|
||||
_, err := loader.Load(tlsclientconfig.Config{
|
||||
CACertFilename: []string{"testdata/Makefile"},
|
||||
})
|
||||
if err == nil {
|
||||
t.Errorf("AddFile wants an error but was nil")
|
||||
}
|
||||
})
|
||||
t.Run("ValidBase64", func(t *testing.T) {
|
||||
cfg, err := loader.Load(tlsclientconfig.Config{
|
||||
CACertData: []string{readFile(t, "testdata/ca2.crt.base64")},
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("Load error: %s", err)
|
||||
}
|
||||
if n := len(cfg.RootCAs.Subjects()); n != 1 {
|
||||
t.Errorf("n wants 1 but was %d", n)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func readFile(t *testing.T, filename string) string {
|
||||
t.Helper()
|
||||
b, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
t.Fatalf("ReadFile error: %s", err)
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
@@ -59,6 +59,7 @@ func (u *Browser) Do(ctx context.Context, o *BrowserOption, client oidcclient.In
|
||||
LocalServerCertFile: o.LocalServerCertFile,
|
||||
LocalServerKeyFile: o.LocalServerKeyFile,
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(ctx, o.AuthenticationTimeout)
|
||||
defer cancel()
|
||||
readyChan := make(chan string, 1)
|
||||
|
||||
@@ -10,17 +10,11 @@ import (
|
||||
"github.com/int128/kubelogin/pkg/adaptors/browser/mock_browser"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/oidcclient"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/oidcclient/mock_oidcclient"
|
||||
"github.com/int128/kubelogin/pkg/jwt"
|
||||
"github.com/int128/kubelogin/pkg/oidc"
|
||||
"github.com/int128/kubelogin/pkg/testing/logger"
|
||||
)
|
||||
|
||||
func TestBrowser_Do(t *testing.T) {
|
||||
dummyTokenClaims := jwt.Claims{
|
||||
Subject: "YOUR_SUBJECT",
|
||||
Expiry: time.Date(2019, 1, 2, 3, 4, 5, 0, time.UTC),
|
||||
Pretty: "PRETTY_JSON",
|
||||
}
|
||||
timeout := 5 * time.Second
|
||||
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
@@ -64,9 +58,8 @@ func TestBrowser_Do(t *testing.T) {
|
||||
readyChan <- "LOCAL_SERVER_URL"
|
||||
}).
|
||||
Return(&oidc.TokenSet{
|
||||
IDToken: "YOUR_ID_TOKEN",
|
||||
RefreshToken: "YOUR_REFRESH_TOKEN",
|
||||
IDTokenClaims: dummyTokenClaims,
|
||||
IDToken: "YOUR_ID_TOKEN",
|
||||
RefreshToken: "YOUR_REFRESH_TOKEN",
|
||||
}, nil)
|
||||
u := Browser{
|
||||
Logger: logger.New(t),
|
||||
@@ -76,9 +69,8 @@ func TestBrowser_Do(t *testing.T) {
|
||||
t.Errorf("Do returned error: %+v", err)
|
||||
}
|
||||
want := &oidc.TokenSet{
|
||||
IDToken: "YOUR_ID_TOKEN",
|
||||
RefreshToken: "YOUR_REFRESH_TOKEN",
|
||||
IDTokenClaims: dummyTokenClaims,
|
||||
IDToken: "YOUR_ID_TOKEN",
|
||||
RefreshToken: "YOUR_REFRESH_TOKEN",
|
||||
}
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
t.Errorf("mismatch (-want +got):\n%s", diff)
|
||||
@@ -102,9 +94,8 @@ func TestBrowser_Do(t *testing.T) {
|
||||
readyChan <- "LOCAL_SERVER_URL"
|
||||
}).
|
||||
Return(&oidc.TokenSet{
|
||||
IDToken: "YOUR_ID_TOKEN",
|
||||
RefreshToken: "YOUR_REFRESH_TOKEN",
|
||||
IDTokenClaims: dummyTokenClaims,
|
||||
IDToken: "YOUR_ID_TOKEN",
|
||||
RefreshToken: "YOUR_REFRESH_TOKEN",
|
||||
}, nil)
|
||||
mockBrowser := mock_browser.NewMockInterface(ctrl)
|
||||
mockBrowser.EXPECT().
|
||||
@@ -118,9 +109,8 @@ func TestBrowser_Do(t *testing.T) {
|
||||
t.Errorf("Do returned error: %+v", err)
|
||||
}
|
||||
want := &oidc.TokenSet{
|
||||
IDToken: "YOUR_ID_TOKEN",
|
||||
RefreshToken: "YOUR_REFRESH_TOKEN",
|
||||
IDTokenClaims: dummyTokenClaims,
|
||||
IDToken: "YOUR_ID_TOKEN",
|
||||
RefreshToken: "YOUR_REFRESH_TOKEN",
|
||||
}
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
t.Errorf("mismatch (-want +got):\n%s", diff)
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"github.com/int128/kubelogin/pkg/adaptors/oidcclient"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/oidcclient/mock_oidcclient"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/reader/mock_reader"
|
||||
"github.com/int128/kubelogin/pkg/jwt"
|
||||
"github.com/int128/kubelogin/pkg/oidc"
|
||||
"github.com/int128/kubelogin/pkg/testing/logger"
|
||||
)
|
||||
@@ -18,11 +17,6 @@ import (
|
||||
var nonNil = gomock.Not(gomock.Nil())
|
||||
|
||||
func TestKeyboard_Do(t *testing.T) {
|
||||
dummyTokenClaims := jwt.Claims{
|
||||
Subject: "YOUR_SUBJECT",
|
||||
Expiry: time.Date(2019, 1, 2, 3, 4, 5, 0, time.UTC),
|
||||
Pretty: "PRETTY_JSON",
|
||||
}
|
||||
timeout := 5 * time.Second
|
||||
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
@@ -51,9 +45,8 @@ func TestKeyboard_Do(t *testing.T) {
|
||||
}
|
||||
}).
|
||||
Return(&oidc.TokenSet{
|
||||
IDToken: "YOUR_ID_TOKEN",
|
||||
IDTokenClaims: dummyTokenClaims,
|
||||
RefreshToken: "YOUR_REFRESH_TOKEN",
|
||||
IDToken: "YOUR_ID_TOKEN",
|
||||
RefreshToken: "YOUR_REFRESH_TOKEN",
|
||||
}, nil)
|
||||
mockReader := mock_reader.NewMockInterface(ctrl)
|
||||
mockReader.EXPECT().
|
||||
@@ -68,9 +61,8 @@ func TestKeyboard_Do(t *testing.T) {
|
||||
t.Errorf("Do returned error: %+v", err)
|
||||
}
|
||||
want := &oidc.TokenSet{
|
||||
IDToken: "YOUR_ID_TOKEN",
|
||||
IDTokenClaims: dummyTokenClaims,
|
||||
RefreshToken: "YOUR_REFRESH_TOKEN",
|
||||
IDToken: "YOUR_ID_TOKEN",
|
||||
RefreshToken: "YOUR_REFRESH_TOKEN",
|
||||
}
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
t.Errorf("mismatch (-want +got):\n%s", diff)
|
||||
|
||||
@@ -4,12 +4,11 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/google/wire"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/certpool"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/clock"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/logger"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/oidcclient"
|
||||
"github.com/int128/kubelogin/pkg/jwt"
|
||||
"github.com/int128/kubelogin/pkg/oidc"
|
||||
"github.com/int128/kubelogin/pkg/tlsclientconfig"
|
||||
"github.com/int128/kubelogin/pkg/usecases/authentication/authcode"
|
||||
"github.com/int128/kubelogin/pkg/usecases/authentication/ropc"
|
||||
"golang.org/x/xerrors"
|
||||
@@ -32,15 +31,10 @@ type Interface interface {
|
||||
|
||||
// Input represents an input DTO of the Authentication use-case.
|
||||
type Input struct {
|
||||
IssuerURL string
|
||||
ClientID string
|
||||
ClientSecret string
|
||||
ExtraScopes []string // optional
|
||||
CertPool certpool.Interface
|
||||
SkipTLSVerify bool
|
||||
IDToken string // optional, from the token cache
|
||||
RefreshToken string // optional, from the token cache
|
||||
GrantOptionSet GrantOptionSet
|
||||
Provider oidc.Provider
|
||||
GrantOptionSet GrantOptionSet
|
||||
CachedTokenSet *oidc.TokenSet // optional
|
||||
TLSClientConfig tlsclientconfig.Config
|
||||
}
|
||||
|
||||
type GrantOptionSet struct {
|
||||
@@ -78,12 +72,12 @@ type Authentication struct {
|
||||
}
|
||||
|
||||
func (u *Authentication) Do(ctx context.Context, in Input) (*Output, error) {
|
||||
if in.IDToken != "" {
|
||||
if in.CachedTokenSet != nil {
|
||||
u.Logger.V(1).Infof("checking expiration of the existing token")
|
||||
// Skip verification of the token to reduce time of a discovery request.
|
||||
// Here it trusts the signature and claims and checks only expiration,
|
||||
// because the token has been verified before caching.
|
||||
claims, err := jwt.DecodeWithoutVerify(in.IDToken)
|
||||
claims, err := in.CachedTokenSet.DecodeWithoutVerify()
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("invalid token cache (you may need to remove): %w", err)
|
||||
}
|
||||
@@ -91,40 +85,23 @@ func (u *Authentication) Do(ctx context.Context, in Input) (*Output, error) {
|
||||
u.Logger.V(1).Infof("you already have a valid token until %s", claims.Expiry)
|
||||
return &Output{
|
||||
AlreadyHasValidIDToken: true,
|
||||
TokenSet: oidc.TokenSet{
|
||||
IDToken: in.IDToken,
|
||||
RefreshToken: in.RefreshToken,
|
||||
IDTokenClaims: *claims,
|
||||
},
|
||||
TokenSet: *in.CachedTokenSet,
|
||||
}, nil
|
||||
}
|
||||
u.Logger.V(1).Infof("you have an expired token at %s", claims.Expiry)
|
||||
}
|
||||
|
||||
u.Logger.V(1).Infof("initializing an OpenID Connect client")
|
||||
client, err := u.OIDCClient.New(ctx, oidcclient.Config{
|
||||
IssuerURL: in.IssuerURL,
|
||||
ClientID: in.ClientID,
|
||||
ClientSecret: in.ClientSecret,
|
||||
ExtraScopes: in.ExtraScopes,
|
||||
CertPool: in.CertPool,
|
||||
SkipTLSVerify: in.SkipTLSVerify,
|
||||
})
|
||||
client, err := u.OIDCClient.New(ctx, in.Provider, in.TLSClientConfig)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("oidc error: %w", err)
|
||||
}
|
||||
|
||||
if in.RefreshToken != "" {
|
||||
if in.CachedTokenSet != nil && in.CachedTokenSet.RefreshToken != "" {
|
||||
u.Logger.V(1).Infof("refreshing the token")
|
||||
out, err := client.Refresh(ctx, in.RefreshToken)
|
||||
tokenSet, err := client.Refresh(ctx, in.CachedTokenSet.RefreshToken)
|
||||
if err == nil {
|
||||
return &Output{
|
||||
TokenSet: oidc.TokenSet{
|
||||
IDToken: out.IDToken,
|
||||
IDTokenClaims: out.IDTokenClaims,
|
||||
RefreshToken: out.RefreshToken,
|
||||
},
|
||||
}, nil
|
||||
return &Output{TokenSet: *tokenSet}, nil
|
||||
}
|
||||
u.Logger.V(1).Infof("could not refresh the token: %s", err)
|
||||
}
|
||||
|
||||
@@ -9,11 +9,11 @@ import (
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/oidcclient"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/oidcclient/mock_oidcclient"
|
||||
"github.com/int128/kubelogin/pkg/jwt"
|
||||
"github.com/int128/kubelogin/pkg/oidc"
|
||||
"github.com/int128/kubelogin/pkg/testing/clock"
|
||||
testingJWT "github.com/int128/kubelogin/pkg/testing/jwt"
|
||||
testingLogger "github.com/int128/kubelogin/pkg/testing/logger"
|
||||
"github.com/int128/kubelogin/pkg/tlsclientconfig"
|
||||
"github.com/int128/kubelogin/pkg/usecases/authentication/authcode"
|
||||
"github.com/int128/kubelogin/pkg/usecases/authentication/ropc"
|
||||
"golang.org/x/xerrors"
|
||||
@@ -22,16 +22,19 @@ import (
|
||||
func TestAuthentication_Do(t *testing.T) {
|
||||
timeout := 5 * time.Second
|
||||
expiryTime := time.Date(2020, 1, 2, 3, 4, 5, 0, time.UTC)
|
||||
cachedIDToken := testingJWT.EncodeF(t, func(claims *testingJWT.Claims) {
|
||||
claims.Issuer = "https://issuer.example.com"
|
||||
claims.Subject = "SUBJECT"
|
||||
dummyProvider := oidc.Provider{
|
||||
IssuerURL: "https://issuer.example.com",
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
}
|
||||
dummyTLSClientConfig := tlsclientconfig.Config{
|
||||
CACertFilename: []string{"/path/to/cert"},
|
||||
}
|
||||
issuedIDToken := testingJWT.EncodeF(t, func(claims *testingJWT.Claims) {
|
||||
claims.Issuer = "https://accounts.google.com"
|
||||
claims.Subject = "YOUR_SUBJECT"
|
||||
claims.ExpiresAt = expiryTime.Unix()
|
||||
})
|
||||
dummyClaims := jwt.Claims{
|
||||
Subject: "SUBJECT",
|
||||
Expiry: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
Pretty: "PRETTY_JSON",
|
||||
}
|
||||
|
||||
t.Run("HasValidIDToken", func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
@@ -39,10 +42,11 @@ func TestAuthentication_Do(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.TODO(), timeout)
|
||||
defer cancel()
|
||||
in := Input{
|
||||
IssuerURL: "https://issuer.example.com",
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
IDToken: cachedIDToken,
|
||||
Provider: dummyProvider,
|
||||
TLSClientConfig: dummyTLSClientConfig,
|
||||
CachedTokenSet: &oidc.TokenSet{
|
||||
IDToken: issuedIDToken,
|
||||
},
|
||||
}
|
||||
u := Authentication{
|
||||
Logger: testingLogger.New(t),
|
||||
@@ -55,16 +59,7 @@ func TestAuthentication_Do(t *testing.T) {
|
||||
want := &Output{
|
||||
AlreadyHasValidIDToken: true,
|
||||
TokenSet: oidc.TokenSet{
|
||||
IDToken: cachedIDToken,
|
||||
IDTokenClaims: jwt.Claims{
|
||||
Subject: "SUBJECT",
|
||||
Expiry: expiryTime,
|
||||
Pretty: `{
|
||||
"exp": 1577934245,
|
||||
"iss": "https://issuer.example.com",
|
||||
"sub": "SUBJECT"
|
||||
}`,
|
||||
},
|
||||
IDToken: issuedIDToken,
|
||||
},
|
||||
}
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
@@ -78,32 +73,28 @@ func TestAuthentication_Do(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.TODO(), timeout)
|
||||
defer cancel()
|
||||
in := Input{
|
||||
IssuerURL: "https://issuer.example.com",
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
IDToken: cachedIDToken,
|
||||
RefreshToken: "VALID_REFRESH_TOKEN",
|
||||
Provider: dummyProvider,
|
||||
TLSClientConfig: dummyTLSClientConfig,
|
||||
CachedTokenSet: &oidc.TokenSet{
|
||||
IDToken: issuedIDToken,
|
||||
RefreshToken: "VALID_REFRESH_TOKEN",
|
||||
},
|
||||
}
|
||||
mockOIDCClient := mock_oidcclient.NewMockInterface(ctrl)
|
||||
mockOIDCClient.EXPECT().
|
||||
Refresh(ctx, "VALID_REFRESH_TOKEN").
|
||||
Return(&oidc.TokenSet{
|
||||
IDToken: "NEW_ID_TOKEN",
|
||||
RefreshToken: "NEW_REFRESH_TOKEN",
|
||||
IDTokenClaims: dummyClaims,
|
||||
IDToken: "NEW_ID_TOKEN",
|
||||
RefreshToken: "NEW_REFRESH_TOKEN",
|
||||
}, nil)
|
||||
mockOIDCClientFactory := mock_oidcclient.NewMockFactoryInterface(ctrl)
|
||||
mockOIDCClientFactory.EXPECT().
|
||||
New(ctx, dummyProvider, dummyTLSClientConfig).
|
||||
Return(mockOIDCClient, nil)
|
||||
u := Authentication{
|
||||
OIDCClient: &oidcclientFactory{
|
||||
t: t,
|
||||
client: mockOIDCClient,
|
||||
want: oidcclient.Config{
|
||||
IssuerURL: "https://issuer.example.com",
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
},
|
||||
},
|
||||
Logger: testingLogger.New(t),
|
||||
Clock: clock.Fake(expiryTime.Add(+time.Hour)),
|
||||
OIDCClient: mockOIDCClientFactory,
|
||||
Logger: testingLogger.New(t),
|
||||
Clock: clock.Fake(expiryTime.Add(+time.Hour)),
|
||||
}
|
||||
got, err := u.Do(ctx, in)
|
||||
if err != nil {
|
||||
@@ -111,9 +102,8 @@ func TestAuthentication_Do(t *testing.T) {
|
||||
}
|
||||
want := &Output{
|
||||
TokenSet: oidc.TokenSet{
|
||||
IDToken: "NEW_ID_TOKEN",
|
||||
RefreshToken: "NEW_REFRESH_TOKEN",
|
||||
IDTokenClaims: dummyClaims,
|
||||
IDToken: "NEW_ID_TOKEN",
|
||||
RefreshToken: "NEW_REFRESH_TOKEN",
|
||||
},
|
||||
}
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
@@ -127,6 +117,8 @@ func TestAuthentication_Do(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.TODO(), timeout)
|
||||
defer cancel()
|
||||
in := Input{
|
||||
Provider: dummyProvider,
|
||||
TLSClientConfig: dummyTLSClientConfig,
|
||||
GrantOptionSet: GrantOptionSet{
|
||||
AuthCodeBrowserOption: &authcode.BrowserOption{
|
||||
BindAddress: []string{"127.0.0.1:8000"},
|
||||
@@ -134,11 +126,10 @@ func TestAuthentication_Do(t *testing.T) {
|
||||
AuthenticationTimeout: 10 * time.Second,
|
||||
},
|
||||
},
|
||||
IssuerURL: "https://issuer.example.com",
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
IDToken: cachedIDToken,
|
||||
RefreshToken: "EXPIRED_REFRESH_TOKEN",
|
||||
CachedTokenSet: &oidc.TokenSet{
|
||||
IDToken: issuedIDToken,
|
||||
RefreshToken: "EXPIRED_REFRESH_TOKEN",
|
||||
},
|
||||
}
|
||||
mockOIDCClient := mock_oidcclient.NewMockInterface(ctrl)
|
||||
mockOIDCClient.EXPECT().SupportedPKCEMethods()
|
||||
@@ -151,22 +142,17 @@ func TestAuthentication_Do(t *testing.T) {
|
||||
readyChan <- "LOCAL_SERVER_URL"
|
||||
}).
|
||||
Return(&oidc.TokenSet{
|
||||
IDToken: "NEW_ID_TOKEN",
|
||||
RefreshToken: "NEW_REFRESH_TOKEN",
|
||||
IDTokenClaims: dummyClaims,
|
||||
IDToken: "NEW_ID_TOKEN",
|
||||
RefreshToken: "NEW_REFRESH_TOKEN",
|
||||
}, nil)
|
||||
mockOIDCClientFactory := mock_oidcclient.NewMockFactoryInterface(ctrl)
|
||||
mockOIDCClientFactory.EXPECT().
|
||||
New(ctx, dummyProvider, dummyTLSClientConfig).
|
||||
Return(mockOIDCClient, nil)
|
||||
u := Authentication{
|
||||
OIDCClient: &oidcclientFactory{
|
||||
t: t,
|
||||
client: mockOIDCClient,
|
||||
want: oidcclient.Config{
|
||||
IssuerURL: "https://issuer.example.com",
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
},
|
||||
},
|
||||
Logger: testingLogger.New(t),
|
||||
Clock: clock.Fake(expiryTime.Add(+time.Hour)),
|
||||
OIDCClient: mockOIDCClientFactory,
|
||||
Logger: testingLogger.New(t),
|
||||
Clock: clock.Fake(expiryTime.Add(+time.Hour)),
|
||||
AuthCodeBrowser: &authcode.Browser{
|
||||
Logger: testingLogger.New(t),
|
||||
},
|
||||
@@ -177,9 +163,8 @@ func TestAuthentication_Do(t *testing.T) {
|
||||
}
|
||||
want := &Output{
|
||||
TokenSet: oidc.TokenSet{
|
||||
IDToken: "NEW_ID_TOKEN",
|
||||
RefreshToken: "NEW_REFRESH_TOKEN",
|
||||
IDTokenClaims: dummyClaims,
|
||||
IDToken: "NEW_ID_TOKEN",
|
||||
RefreshToken: "NEW_REFRESH_TOKEN",
|
||||
},
|
||||
}
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
@@ -193,35 +178,29 @@ func TestAuthentication_Do(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.TODO(), timeout)
|
||||
defer cancel()
|
||||
in := Input{
|
||||
Provider: dummyProvider,
|
||||
TLSClientConfig: dummyTLSClientConfig,
|
||||
GrantOptionSet: GrantOptionSet{
|
||||
ROPCOption: &ropc.Option{
|
||||
Username: "USER",
|
||||
Password: "PASS",
|
||||
},
|
||||
},
|
||||
IssuerURL: "https://issuer.example.com",
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
}
|
||||
mockOIDCClient := mock_oidcclient.NewMockInterface(ctrl)
|
||||
mockOIDCClient.EXPECT().
|
||||
GetTokenByROPC(gomock.Any(), "USER", "PASS").
|
||||
Return(&oidc.TokenSet{
|
||||
IDToken: "YOUR_ID_TOKEN",
|
||||
RefreshToken: "YOUR_REFRESH_TOKEN",
|
||||
IDTokenClaims: dummyClaims,
|
||||
IDToken: "YOUR_ID_TOKEN",
|
||||
RefreshToken: "YOUR_REFRESH_TOKEN",
|
||||
}, nil)
|
||||
mockOIDCClientFactory := mock_oidcclient.NewMockFactoryInterface(ctrl)
|
||||
mockOIDCClientFactory.EXPECT().
|
||||
New(ctx, dummyProvider, dummyTLSClientConfig).
|
||||
Return(mockOIDCClient, nil)
|
||||
u := Authentication{
|
||||
OIDCClient: &oidcclientFactory{
|
||||
t: t,
|
||||
client: mockOIDCClient,
|
||||
want: oidcclient.Config{
|
||||
IssuerURL: "https://issuer.example.com",
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
},
|
||||
},
|
||||
Logger: testingLogger.New(t),
|
||||
OIDCClient: mockOIDCClientFactory,
|
||||
Logger: testingLogger.New(t),
|
||||
ROPC: &ropc.ROPC{
|
||||
Logger: testingLogger.New(t),
|
||||
},
|
||||
@@ -232,9 +211,8 @@ func TestAuthentication_Do(t *testing.T) {
|
||||
}
|
||||
want := &Output{
|
||||
TokenSet: oidc.TokenSet{
|
||||
IDToken: "YOUR_ID_TOKEN",
|
||||
RefreshToken: "YOUR_REFRESH_TOKEN",
|
||||
IDTokenClaims: dummyClaims,
|
||||
IDToken: "YOUR_ID_TOKEN",
|
||||
RefreshToken: "YOUR_REFRESH_TOKEN",
|
||||
},
|
||||
}
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
@@ -242,16 +220,3 @@ func TestAuthentication_Do(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type oidcclientFactory struct {
|
||||
t *testing.T
|
||||
client oidcclient.Interface
|
||||
want oidcclient.Config
|
||||
}
|
||||
|
||||
func (f *oidcclientFactory) New(_ context.Context, got oidcclient.Config) (oidcclient.Interface, error) {
|
||||
if diff := cmp.Diff(f.want, got); diff != "" {
|
||||
f.t.Errorf("mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
return f.client, nil
|
||||
}
|
||||
|
||||
@@ -9,18 +9,12 @@ import (
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/oidcclient/mock_oidcclient"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/reader/mock_reader"
|
||||
"github.com/int128/kubelogin/pkg/jwt"
|
||||
"github.com/int128/kubelogin/pkg/oidc"
|
||||
"github.com/int128/kubelogin/pkg/testing/logger"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
func TestROPC_Do(t *testing.T) {
|
||||
dummyTokenClaims := jwt.Claims{
|
||||
Subject: "YOUR_SUBJECT",
|
||||
Expiry: time.Date(2019, 1, 2, 3, 4, 5, 0, time.UTC),
|
||||
Pretty: "PRETTY_JSON",
|
||||
}
|
||||
timeout := 5 * time.Second
|
||||
|
||||
t.Run("AskUsernameAndPassword", func(t *testing.T) {
|
||||
@@ -33,9 +27,8 @@ func TestROPC_Do(t *testing.T) {
|
||||
mockOIDCClient.EXPECT().
|
||||
GetTokenByROPC(gomock.Any(), "USER", "PASS").
|
||||
Return(&oidc.TokenSet{
|
||||
IDToken: "YOUR_ID_TOKEN",
|
||||
IDTokenClaims: dummyTokenClaims,
|
||||
RefreshToken: "YOUR_REFRESH_TOKEN",
|
||||
IDToken: "YOUR_ID_TOKEN",
|
||||
RefreshToken: "YOUR_REFRESH_TOKEN",
|
||||
}, nil)
|
||||
mockReader := mock_reader.NewMockInterface(ctrl)
|
||||
mockReader.EXPECT().ReadString(usernamePrompt).Return("USER", nil)
|
||||
@@ -49,9 +42,8 @@ func TestROPC_Do(t *testing.T) {
|
||||
t.Errorf("Do returned error: %+v", err)
|
||||
}
|
||||
want := &oidc.TokenSet{
|
||||
IDToken: "YOUR_ID_TOKEN",
|
||||
RefreshToken: "YOUR_REFRESH_TOKEN",
|
||||
IDTokenClaims: dummyTokenClaims,
|
||||
IDToken: "YOUR_ID_TOKEN",
|
||||
RefreshToken: "YOUR_REFRESH_TOKEN",
|
||||
}
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
t.Errorf("mismatch (-want +got):\n%s", diff)
|
||||
@@ -71,9 +63,8 @@ func TestROPC_Do(t *testing.T) {
|
||||
mockOIDCClient.EXPECT().
|
||||
GetTokenByROPC(gomock.Any(), "USER", "PASS").
|
||||
Return(&oidc.TokenSet{
|
||||
IDToken: "YOUR_ID_TOKEN",
|
||||
RefreshToken: "YOUR_REFRESH_TOKEN",
|
||||
IDTokenClaims: dummyTokenClaims,
|
||||
IDToken: "YOUR_ID_TOKEN",
|
||||
RefreshToken: "YOUR_REFRESH_TOKEN",
|
||||
}, nil)
|
||||
u := ROPC{
|
||||
Logger: logger.New(t),
|
||||
@@ -83,9 +74,8 @@ func TestROPC_Do(t *testing.T) {
|
||||
t.Errorf("Do returned error: %+v", err)
|
||||
}
|
||||
want := &oidc.TokenSet{
|
||||
IDToken: "YOUR_ID_TOKEN",
|
||||
RefreshToken: "YOUR_REFRESH_TOKEN",
|
||||
IDTokenClaims: dummyTokenClaims,
|
||||
IDToken: "YOUR_ID_TOKEN",
|
||||
RefreshToken: "YOUR_REFRESH_TOKEN",
|
||||
}
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
t.Errorf("mismatch (-want +got):\n%s", diff)
|
||||
@@ -104,9 +94,8 @@ func TestROPC_Do(t *testing.T) {
|
||||
mockOIDCClient.EXPECT().
|
||||
GetTokenByROPC(gomock.Any(), "USER", "PASS").
|
||||
Return(&oidc.TokenSet{
|
||||
IDToken: "YOUR_ID_TOKEN",
|
||||
RefreshToken: "YOUR_REFRESH_TOKEN",
|
||||
IDTokenClaims: dummyTokenClaims,
|
||||
IDToken: "YOUR_ID_TOKEN",
|
||||
RefreshToken: "YOUR_REFRESH_TOKEN",
|
||||
}, nil)
|
||||
mockEnv := mock_reader.NewMockInterface(ctrl)
|
||||
mockEnv.EXPECT().ReadPassword(passwordPrompt).Return("PASS", nil)
|
||||
@@ -119,9 +108,8 @@ func TestROPC_Do(t *testing.T) {
|
||||
t.Errorf("Do returned error: %+v", err)
|
||||
}
|
||||
want := &oidc.TokenSet{
|
||||
IDToken: "YOUR_ID_TOKEN",
|
||||
RefreshToken: "YOUR_REFRESH_TOKEN",
|
||||
IDTokenClaims: dummyTokenClaims,
|
||||
IDToken: "YOUR_ID_TOKEN",
|
||||
RefreshToken: "YOUR_REFRESH_TOKEN",
|
||||
}
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
t.Errorf("mismatch (-want +got):\n%s", diff)
|
||||
|
||||
@@ -5,12 +5,16 @@ package credentialplugin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/int128/kubelogin/pkg/adaptors/mutex"
|
||||
|
||||
"github.com/google/wire"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/certpool"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/credentialpluginwriter"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/logger"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/tokencache"
|
||||
"github.com/int128/kubelogin/pkg/oidc"
|
||||
"github.com/int128/kubelogin/pkg/tlsclientconfig"
|
||||
"github.com/int128/kubelogin/pkg/usecases/authentication"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
@@ -28,92 +32,89 @@ type Interface interface {
|
||||
|
||||
// Input represents an input DTO of the GetToken use-case.
|
||||
type Input struct {
|
||||
IssuerURL string
|
||||
ClientID string
|
||||
ClientSecret string
|
||||
ExtraScopes []string // optional
|
||||
CACertFilename string // optional
|
||||
CACertData string // optional
|
||||
SkipTLSVerify bool
|
||||
TokenCacheDir string
|
||||
GrantOptionSet authentication.GrantOptionSet
|
||||
IssuerURL string
|
||||
ClientID string
|
||||
ClientSecret string
|
||||
ExtraScopes []string // optional
|
||||
TokenCacheDir string
|
||||
GrantOptionSet authentication.GrantOptionSet
|
||||
TLSClientConfig tlsclientconfig.Config
|
||||
}
|
||||
|
||||
type GetToken struct {
|
||||
Authentication authentication.Interface
|
||||
TokenCacheRepository tokencache.Interface
|
||||
NewCertPool certpool.NewFunc
|
||||
Writer credentialpluginwriter.Interface
|
||||
Mutex mutex.Interface
|
||||
Logger logger.Interface
|
||||
}
|
||||
|
||||
func (u *GetToken) Do(ctx context.Context, in Input) error {
|
||||
u.Logger.V(1).Infof("WARNING: log may contain your secrets such as token or password")
|
||||
out, err := u.getTokenFromCacheOrProvider(ctx, in)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("could not get a token: %w", err)
|
||||
}
|
||||
u.Logger.V(1).Infof("writing the token to client-go")
|
||||
if err := u.Writer.Write(credentialpluginwriter.Output{Token: out.TokenSet.IDToken, Expiry: out.TokenSet.IDTokenClaims.Expiry}); err != nil {
|
||||
return xerrors.Errorf("could not write the token to client-go: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *GetToken) getTokenFromCacheOrProvider(ctx context.Context, in Input) (*authentication.Output, error) {
|
||||
// Prevent multiple concurrent token query using a file mutex. See https://github.com/int128/kubelogin/issues/389
|
||||
lock, err := u.Mutex.Acquire(ctx, "get-token")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = u.Mutex.Release(lock)
|
||||
}()
|
||||
|
||||
u.Logger.V(1).Infof("finding a token from cache directory %s", in.TokenCacheDir)
|
||||
tokenCacheKey := tokencache.Key{
|
||||
IssuerURL: in.IssuerURL,
|
||||
ClientID: in.ClientID,
|
||||
ClientSecret: in.ClientSecret,
|
||||
ExtraScopes: in.ExtraScopes,
|
||||
CACertFilename: in.CACertFilename,
|
||||
CACertData: in.CACertData,
|
||||
SkipTLSVerify: in.SkipTLSVerify,
|
||||
CACertFilename: strings.Join(in.TLSClientConfig.CACertFilename, ","),
|
||||
CACertData: strings.Join(in.TLSClientConfig.CACertData, ","),
|
||||
SkipTLSVerify: in.TLSClientConfig.SkipTLSVerify,
|
||||
}
|
||||
tokenCacheValue, err := u.TokenCacheRepository.FindByKey(in.TokenCacheDir, tokenCacheKey)
|
||||
if in.GrantOptionSet.ROPCOption != nil {
|
||||
tokenCacheKey.Username = in.GrantOptionSet.ROPCOption.Username
|
||||
}
|
||||
cachedTokenSet, err := u.TokenCacheRepository.FindByKey(in.TokenCacheDir, tokenCacheKey)
|
||||
if err != nil {
|
||||
u.Logger.V(1).Infof("could not find a token cache: %s", err)
|
||||
tokenCacheValue = &tokencache.Value{}
|
||||
}
|
||||
certPool := u.NewCertPool()
|
||||
if in.CACertFilename != "" {
|
||||
if err := certPool.AddFile(in.CACertFilename); err != nil {
|
||||
return nil, xerrors.Errorf("could not load the certificate file: %w", err)
|
||||
}
|
||||
}
|
||||
if in.CACertData != "" {
|
||||
if err := certPool.AddBase64Encoded(in.CACertData); err != nil {
|
||||
return nil, xerrors.Errorf("could not load the certificate data: %w", err)
|
||||
}
|
||||
}
|
||||
out, err := u.Authentication.Do(ctx, authentication.Input{
|
||||
IssuerURL: in.IssuerURL,
|
||||
ClientID: in.ClientID,
|
||||
ClientSecret: in.ClientSecret,
|
||||
ExtraScopes: in.ExtraScopes,
|
||||
CertPool: certPool,
|
||||
SkipTLSVerify: in.SkipTLSVerify,
|
||||
IDToken: tokenCacheValue.IDToken,
|
||||
RefreshToken: tokenCacheValue.RefreshToken,
|
||||
GrantOptionSet: in.GrantOptionSet,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("authentication error: %w", err)
|
||||
}
|
||||
u.Logger.V(1).Infof("you got a token: %s", out.TokenSet.IDTokenClaims.Pretty)
|
||||
if out.AlreadyHasValidIDToken {
|
||||
u.Logger.V(1).Infof("you already have a valid token until %s", out.TokenSet.IDTokenClaims.Expiry)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
u.Logger.V(1).Infof("you got a valid token until %s", out.TokenSet.IDTokenClaims.Expiry)
|
||||
newTokenCacheValue := tokencache.Value{
|
||||
IDToken: out.TokenSet.IDToken,
|
||||
RefreshToken: out.TokenSet.RefreshToken,
|
||||
authenticationInput := authentication.Input{
|
||||
Provider: oidc.Provider{
|
||||
IssuerURL: in.IssuerURL,
|
||||
ClientID: in.ClientID,
|
||||
ClientSecret: in.ClientSecret,
|
||||
ExtraScopes: in.ExtraScopes,
|
||||
},
|
||||
GrantOptionSet: in.GrantOptionSet,
|
||||
CachedTokenSet: cachedTokenSet,
|
||||
TLSClientConfig: in.TLSClientConfig,
|
||||
}
|
||||
if err := u.TokenCacheRepository.Save(in.TokenCacheDir, tokenCacheKey, newTokenCacheValue); err != nil {
|
||||
return nil, xerrors.Errorf("could not write the token cache: %w", err)
|
||||
authenticationOutput, err := u.Authentication.Do(ctx, authenticationInput)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("authentication error: %w", err)
|
||||
}
|
||||
return out, nil
|
||||
idTokenClaims, err := authenticationOutput.TokenSet.DecodeWithoutVerify()
|
||||
if err != nil {
|
||||
return xerrors.Errorf("you got an invalid token: %w", err)
|
||||
}
|
||||
u.Logger.V(1).Infof("you got a token: %s", idTokenClaims.Pretty)
|
||||
|
||||
if authenticationOutput.AlreadyHasValidIDToken {
|
||||
u.Logger.V(1).Infof("you already have a valid token until %s", idTokenClaims.Expiry)
|
||||
} else {
|
||||
u.Logger.V(1).Infof("you got a valid token until %s", idTokenClaims.Expiry)
|
||||
if err := u.TokenCacheRepository.Save(in.TokenCacheDir, tokenCacheKey, authenticationOutput.TokenSet); err != nil {
|
||||
return xerrors.Errorf("could not write the token cache: %w", err)
|
||||
}
|
||||
}
|
||||
u.Logger.V(1).Infof("writing the token to client-go")
|
||||
out := credentialpluginwriter.Output{
|
||||
Token: authenticationOutput.TokenSet.IDToken,
|
||||
Expiry: idTokenClaims.Expiry,
|
||||
}
|
||||
if err := u.Writer.Write(out); err != nil {
|
||||
return xerrors.Errorf("could not write the token to client-go: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -5,102 +5,149 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/int128/kubelogin/pkg/adaptors/mutex"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/mutex/mock_mutex"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/certpool"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/certpool/mock_certpool"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/credentialpluginwriter"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/credentialpluginwriter/mock_credentialpluginwriter"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/tokencache"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/tokencache/mock_tokencache"
|
||||
"github.com/int128/kubelogin/pkg/jwt"
|
||||
"github.com/int128/kubelogin/pkg/oidc"
|
||||
testingJWT "github.com/int128/kubelogin/pkg/testing/jwt"
|
||||
"github.com/int128/kubelogin/pkg/testing/logger"
|
||||
"github.com/int128/kubelogin/pkg/tlsclientconfig"
|
||||
"github.com/int128/kubelogin/pkg/usecases/authentication"
|
||||
"github.com/int128/kubelogin/pkg/usecases/authentication/mock_authentication"
|
||||
"github.com/int128/kubelogin/pkg/usecases/authentication/ropc"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
func TestGetToken_Do(t *testing.T) {
|
||||
dummyTokenClaims := jwt.Claims{
|
||||
Subject: "YOUR_SUBJECT",
|
||||
Expiry: time.Date(2019, 1, 2, 3, 4, 5, 0, time.UTC),
|
||||
Pretty: "PRETTY_JSON",
|
||||
}
|
||||
issuedIDTokenExpiration := time.Now().Add(1 * time.Hour).Round(time.Second)
|
||||
issuedIDToken := testingJWT.EncodeF(t, func(claims *testingJWT.Claims) {
|
||||
claims.Issuer = "https://accounts.google.com"
|
||||
claims.Subject = "YOUR_SUBJECT"
|
||||
claims.ExpiresAt = issuedIDTokenExpiration.Unix()
|
||||
})
|
||||
|
||||
t.Run("FullOptions", func(t *testing.T) {
|
||||
t.Run("LeastOptions", func(t *testing.T) {
|
||||
var grantOptionSet authentication.GrantOptionSet
|
||||
tokenSet := oidc.TokenSet{
|
||||
IDToken: issuedIDToken,
|
||||
RefreshToken: "YOUR_REFRESH_TOKEN",
|
||||
}
|
||||
tokenCacheKey := tokencache.Key{
|
||||
IssuerURL: "https://accounts.google.com",
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
}
|
||||
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
ctx := context.TODO()
|
||||
in := Input{
|
||||
IssuerURL: "https://accounts.google.com",
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
TokenCacheDir: "/path/to/token-cache",
|
||||
CACertFilename: "/path/to/cert",
|
||||
CACertData: "BASE64ENCODED",
|
||||
SkipTLSVerify: true,
|
||||
GrantOptionSet: grantOptionSet,
|
||||
}
|
||||
mockCertPool := mock_certpool.NewMockInterface(ctrl)
|
||||
mockCertPool.EXPECT().
|
||||
AddFile("/path/to/cert")
|
||||
mockCertPool.EXPECT().
|
||||
AddBase64Encoded("BASE64ENCODED")
|
||||
mockAuthentication := mock_authentication.NewMockInterface(ctrl)
|
||||
mockAuthentication.EXPECT().
|
||||
Do(ctx, authentication.Input{
|
||||
IssuerURL: "https://accounts.google.com",
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
CertPool: mockCertPool,
|
||||
SkipTLSVerify: true,
|
||||
Provider: oidc.Provider{
|
||||
IssuerURL: "https://accounts.google.com",
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
},
|
||||
GrantOptionSet: grantOptionSet,
|
||||
}).
|
||||
Return(&authentication.Output{
|
||||
TokenSet: oidc.TokenSet{
|
||||
IDToken: "YOUR_ID_TOKEN",
|
||||
RefreshToken: "YOUR_REFRESH_TOKEN",
|
||||
IDTokenClaims: dummyTokenClaims,
|
||||
},
|
||||
}, nil)
|
||||
Return(&authentication.Output{TokenSet: tokenSet}, nil)
|
||||
tokenCacheRepository := mock_tokencache.NewMockInterface(ctrl)
|
||||
tokenCacheRepository.EXPECT().
|
||||
FindByKey("/path/to/token-cache",
|
||||
tokencache.Key{
|
||||
IssuerURL: "https://accounts.google.com",
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
CACertFilename: "/path/to/cert",
|
||||
CACertData: "BASE64ENCODED",
|
||||
SkipTLSVerify: true,
|
||||
}).
|
||||
FindByKey("/path/to/token-cache", tokenCacheKey).
|
||||
Return(nil, xerrors.New("file not found"))
|
||||
tokenCacheRepository.EXPECT().
|
||||
Save("/path/to/token-cache",
|
||||
tokencache.Key{
|
||||
IssuerURL: "https://accounts.google.com",
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
CACertFilename: "/path/to/cert",
|
||||
CACertData: "BASE64ENCODED",
|
||||
SkipTLSVerify: true,
|
||||
},
|
||||
tokencache.Value{
|
||||
IDToken: "YOUR_ID_TOKEN",
|
||||
RefreshToken: "YOUR_REFRESH_TOKEN",
|
||||
})
|
||||
Save("/path/to/token-cache", tokenCacheKey, tokenSet)
|
||||
credentialPluginWriter := mock_credentialpluginwriter.NewMockInterface(ctrl)
|
||||
credentialPluginWriter.EXPECT().
|
||||
Write(credentialpluginwriter.Output{
|
||||
Token: "YOUR_ID_TOKEN",
|
||||
Expiry: dummyTokenClaims.Expiry,
|
||||
Token: issuedIDToken,
|
||||
Expiry: issuedIDTokenExpiration,
|
||||
})
|
||||
u := GetToken{
|
||||
Authentication: mockAuthentication,
|
||||
TokenCacheRepository: tokenCacheRepository,
|
||||
NewCertPool: func() certpool.Interface { return mockCertPool },
|
||||
Writer: credentialPluginWriter,
|
||||
Mutex: setupMutexMock(ctrl),
|
||||
Logger: logger.New(t),
|
||||
}
|
||||
if err := u.Do(ctx, in); err != nil {
|
||||
t.Errorf("Do returned error: %+v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("FullOptions", func(t *testing.T) {
|
||||
grantOptionSet := authentication.GrantOptionSet{
|
||||
ROPCOption: &ropc.Option{Username: "YOUR_USERNAME"},
|
||||
}
|
||||
tokenSet := oidc.TokenSet{
|
||||
IDToken: issuedIDToken,
|
||||
RefreshToken: "YOUR_REFRESH_TOKEN",
|
||||
}
|
||||
tokenCacheKey := tokencache.Key{
|
||||
IssuerURL: "https://accounts.google.com",
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
Username: "YOUR_USERNAME",
|
||||
CACertFilename: "/path/to/cert",
|
||||
CACertData: "BASE64ENCODED",
|
||||
SkipTLSVerify: true,
|
||||
}
|
||||
tlsClientConfig := tlsclientconfig.Config{
|
||||
CACertFilename: []string{"/path/to/cert"},
|
||||
CACertData: []string{"BASE64ENCODED"},
|
||||
SkipTLSVerify: true,
|
||||
}
|
||||
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
ctx := context.TODO()
|
||||
in := Input{
|
||||
IssuerURL: "https://accounts.google.com",
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
TokenCacheDir: "/path/to/token-cache",
|
||||
GrantOptionSet: grantOptionSet,
|
||||
TLSClientConfig: tlsClientConfig,
|
||||
}
|
||||
mockAuthentication := mock_authentication.NewMockInterface(ctrl)
|
||||
mockAuthentication.EXPECT().
|
||||
Do(ctx, authentication.Input{
|
||||
Provider: oidc.Provider{
|
||||
IssuerURL: "https://accounts.google.com",
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
},
|
||||
GrantOptionSet: grantOptionSet,
|
||||
TLSClientConfig: tlsClientConfig,
|
||||
}).
|
||||
Return(&authentication.Output{TokenSet: tokenSet}, nil)
|
||||
tokenCacheRepository := mock_tokencache.NewMockInterface(ctrl)
|
||||
tokenCacheRepository.EXPECT().
|
||||
FindByKey("/path/to/token-cache", tokenCacheKey).
|
||||
Return(nil, xerrors.New("file not found"))
|
||||
tokenCacheRepository.EXPECT().
|
||||
Save("/path/to/token-cache", tokenCacheKey, tokenSet)
|
||||
credentialPluginWriter := mock_credentialpluginwriter.NewMockInterface(ctrl)
|
||||
credentialPluginWriter.EXPECT().
|
||||
Write(credentialpluginwriter.Output{
|
||||
Token: issuedIDToken,
|
||||
Expiry: issuedIDTokenExpiration,
|
||||
})
|
||||
u := GetToken{
|
||||
Authentication: mockAuthentication,
|
||||
TokenCacheRepository: tokenCacheRepository,
|
||||
Writer: credentialPluginWriter,
|
||||
Mutex: setupMutexMock(ctrl),
|
||||
Logger: logger.New(t),
|
||||
}
|
||||
if err := u.Do(ctx, in); err != nil {
|
||||
@@ -118,21 +165,22 @@ func TestGetToken_Do(t *testing.T) {
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
TokenCacheDir: "/path/to/token-cache",
|
||||
}
|
||||
mockCertPool := mock_certpool.NewMockInterface(ctrl)
|
||||
mockAuthentication := mock_authentication.NewMockInterface(ctrl)
|
||||
mockAuthentication.EXPECT().
|
||||
Do(ctx, authentication.Input{
|
||||
IssuerURL: "https://accounts.google.com",
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
IDToken: "VALID_ID_TOKEN",
|
||||
CertPool: mockCertPool,
|
||||
Provider: oidc.Provider{
|
||||
IssuerURL: "https://accounts.google.com",
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
},
|
||||
CachedTokenSet: &oidc.TokenSet{
|
||||
IDToken: issuedIDToken,
|
||||
},
|
||||
}).
|
||||
Return(&authentication.Output{
|
||||
AlreadyHasValidIDToken: true,
|
||||
TokenSet: oidc.TokenSet{
|
||||
IDToken: "VALID_ID_TOKEN",
|
||||
IDTokenClaims: dummyTokenClaims,
|
||||
IDToken: issuedIDToken,
|
||||
},
|
||||
}, nil)
|
||||
tokenCacheRepository := mock_tokencache.NewMockInterface(ctrl)
|
||||
@@ -142,20 +190,20 @@ func TestGetToken_Do(t *testing.T) {
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
}).
|
||||
Return(&tokencache.Value{
|
||||
IDToken: "VALID_ID_TOKEN",
|
||||
Return(&oidc.TokenSet{
|
||||
IDToken: issuedIDToken,
|
||||
}, nil)
|
||||
credentialPluginWriter := mock_credentialpluginwriter.NewMockInterface(ctrl)
|
||||
credentialPluginWriter.EXPECT().
|
||||
Write(credentialpluginwriter.Output{
|
||||
Token: "VALID_ID_TOKEN",
|
||||
Expiry: dummyTokenClaims.Expiry,
|
||||
Token: issuedIDToken,
|
||||
Expiry: issuedIDTokenExpiration,
|
||||
})
|
||||
u := GetToken{
|
||||
Authentication: mockAuthentication,
|
||||
TokenCacheRepository: tokenCacheRepository,
|
||||
NewCertPool: func() certpool.Interface { return mockCertPool },
|
||||
Writer: credentialPluginWriter,
|
||||
Mutex: setupMutexMock(ctrl),
|
||||
Logger: logger.New(t),
|
||||
}
|
||||
if err := u.Do(ctx, in); err != nil {
|
||||
@@ -173,14 +221,14 @@ func TestGetToken_Do(t *testing.T) {
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
TokenCacheDir: "/path/to/token-cache",
|
||||
}
|
||||
mockCertPool := mock_certpool.NewMockInterface(ctrl)
|
||||
mockAuthentication := mock_authentication.NewMockInterface(ctrl)
|
||||
mockAuthentication.EXPECT().
|
||||
Do(ctx, authentication.Input{
|
||||
IssuerURL: "https://accounts.google.com",
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
CertPool: mockCertPool,
|
||||
Provider: oidc.Provider{
|
||||
IssuerURL: "https://accounts.google.com",
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
},
|
||||
}).
|
||||
Return(nil, xerrors.New("authentication error"))
|
||||
tokenCacheRepository := mock_tokencache.NewMockInterface(ctrl)
|
||||
@@ -194,8 +242,8 @@ func TestGetToken_Do(t *testing.T) {
|
||||
u := GetToken{
|
||||
Authentication: mockAuthentication,
|
||||
TokenCacheRepository: tokenCacheRepository,
|
||||
NewCertPool: func() certpool.Interface { return mockCertPool },
|
||||
Writer: mock_credentialpluginwriter.NewMockInterface(ctrl),
|
||||
Mutex: setupMutexMock(ctrl),
|
||||
Logger: logger.New(t),
|
||||
}
|
||||
if err := u.Do(ctx, in); err == nil {
|
||||
@@ -203,3 +251,12 @@ func TestGetToken_Do(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Setup a mock that expect the mutex to be lock and unlock
|
||||
func setupMutexMock(ctrl *gomock.Controller) *mock_mutex.MockInterface {
|
||||
mockMutex := mock_mutex.NewMockInterface(ctrl)
|
||||
lockValue := &mutex.Lock{Data: "testData"}
|
||||
acquireCall := mockMutex.EXPECT().Acquire(gomock.Not(gomock.Nil()), "get-token").Return(lockValue, nil)
|
||||
mockMutex.EXPECT().Release(lockValue).Return(nil).After(acquireCall)
|
||||
return mockMutex
|
||||
}
|
||||
|
||||
@@ -11,30 +11,30 @@ import (
|
||||
reflect "reflect"
|
||||
)
|
||||
|
||||
// MockInterface is a mock of Interface interface.
|
||||
// MockInterface is a mock of Interface interface
|
||||
type MockInterface struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockInterfaceMockRecorder
|
||||
}
|
||||
|
||||
// MockInterfaceMockRecorder is the mock recorder for MockInterface.
|
||||
// MockInterfaceMockRecorder is the mock recorder for MockInterface
|
||||
type MockInterfaceMockRecorder struct {
|
||||
mock *MockInterface
|
||||
}
|
||||
|
||||
// NewMockInterface creates a new mock instance.
|
||||
// NewMockInterface creates a new mock instance
|
||||
func NewMockInterface(ctrl *gomock.Controller) *MockInterface {
|
||||
mock := &MockInterface{ctrl: ctrl}
|
||||
mock.recorder = &MockInterfaceMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockInterface) EXPECT() *MockInterfaceMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Do mocks base method.
|
||||
// Do mocks base method
|
||||
func (m *MockInterface) Do(arg0 context.Context, arg1 credentialplugin.Input) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Do", arg0, arg1)
|
||||
@@ -42,7 +42,7 @@ func (m *MockInterface) Do(arg0 context.Context, arg1 credentialplugin.Input) er
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Do indicates an expected call of Do.
|
||||
// Do indicates an expected call of Do
|
||||
func (mr *MockInterfaceMockRecorder) Do(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Do", reflect.TypeOf((*MockInterface)(nil).Do), arg0, arg1)
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/google/wire"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/certpool"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/logger"
|
||||
"github.com/int128/kubelogin/pkg/usecases/authentication"
|
||||
)
|
||||
@@ -22,6 +21,5 @@ type Interface interface {
|
||||
|
||||
type Setup struct {
|
||||
Authentication authentication.Interface
|
||||
NewCertPool certpool.NewFunc
|
||||
Logger logger.Interface
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/int128/kubelogin/pkg/oidc"
|
||||
"github.com/int128/kubelogin/pkg/tlsclientconfig"
|
||||
"github.com/int128/kubelogin/pkg/usecases/authentication"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
@@ -71,45 +73,37 @@ type Stage2Input struct {
|
||||
ClientID string
|
||||
ClientSecret string
|
||||
ExtraScopes []string // optional
|
||||
CACertFilename string // optional
|
||||
CACertData string // optional
|
||||
SkipTLSVerify bool
|
||||
ListenAddressArgs []string // non-nil if set by the command arg
|
||||
GrantOptionSet authentication.GrantOptionSet
|
||||
TLSClientConfig tlsclientconfig.Config
|
||||
}
|
||||
|
||||
func (u *Setup) DoStage2(ctx context.Context, in Stage2Input) error {
|
||||
u.Logger.Printf("authentication in progress...")
|
||||
certPool := u.NewCertPool()
|
||||
if in.CACertFilename != "" {
|
||||
if err := certPool.AddFile(in.CACertFilename); err != nil {
|
||||
return xerrors.Errorf("could not load the certificate file: %w", err)
|
||||
}
|
||||
}
|
||||
if in.CACertData != "" {
|
||||
if err := certPool.AddBase64Encoded(in.CACertData); err != nil {
|
||||
return xerrors.Errorf("could not load the certificate data: %w", err)
|
||||
}
|
||||
}
|
||||
out, err := u.Authentication.Do(ctx, authentication.Input{
|
||||
IssuerURL: in.IssuerURL,
|
||||
ClientID: in.ClientID,
|
||||
ClientSecret: in.ClientSecret,
|
||||
ExtraScopes: in.ExtraScopes,
|
||||
CertPool: certPool,
|
||||
SkipTLSVerify: in.SkipTLSVerify,
|
||||
GrantOptionSet: in.GrantOptionSet,
|
||||
Provider: oidc.Provider{
|
||||
IssuerURL: in.IssuerURL,
|
||||
ClientID: in.ClientID,
|
||||
ClientSecret: in.ClientSecret,
|
||||
ExtraScopes: in.ExtraScopes,
|
||||
},
|
||||
GrantOptionSet: in.GrantOptionSet,
|
||||
TLSClientConfig: in.TLSClientConfig,
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("authentication error: %w", err)
|
||||
}
|
||||
idTokenClaims, err := out.TokenSet.DecodeWithoutVerify()
|
||||
if err != nil {
|
||||
return xerrors.Errorf("you got an invalid token: %w", err)
|
||||
}
|
||||
|
||||
v := stage2Vars{
|
||||
IDTokenPrettyJSON: out.TokenSet.IDTokenClaims.Pretty,
|
||||
IDTokenPrettyJSON: idTokenClaims.Pretty,
|
||||
IssuerURL: in.IssuerURL,
|
||||
ClientID: in.ClientID,
|
||||
Args: makeCredentialPluginArgs(in),
|
||||
Subject: out.TokenSet.IDTokenClaims.Subject,
|
||||
Subject: idTokenClaims.Subject,
|
||||
}
|
||||
var b strings.Builder
|
||||
if err := stage2Tpl.Execute(&b, &v); err != nil {
|
||||
@@ -129,13 +123,13 @@ func makeCredentialPluginArgs(in Stage2Input) []string {
|
||||
for _, extraScope := range in.ExtraScopes {
|
||||
args = append(args, "--oidc-extra-scope="+extraScope)
|
||||
}
|
||||
if in.CACertFilename != "" {
|
||||
args = append(args, "--certificate-authority="+in.CACertFilename)
|
||||
for _, f := range in.TLSClientConfig.CACertFilename {
|
||||
args = append(args, "--certificate-authority="+f)
|
||||
}
|
||||
if in.CACertData != "" {
|
||||
args = append(args, "--certificate-authority-data="+in.CACertData)
|
||||
for _, d := range in.TLSClientConfig.CACertData {
|
||||
args = append(args, "--certificate-authority-data="+d)
|
||||
}
|
||||
if in.SkipTLSVerify {
|
||||
if in.TLSClientConfig.SkipTLSVerify {
|
||||
args = append(args, "--insecure-skip-tls-verify")
|
||||
}
|
||||
|
||||
|
||||
@@ -6,59 +6,56 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/certpool"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/certpool/mock_certpool"
|
||||
"github.com/int128/kubelogin/pkg/jwt"
|
||||
"github.com/int128/kubelogin/pkg/oidc"
|
||||
testingJWT "github.com/int128/kubelogin/pkg/testing/jwt"
|
||||
"github.com/int128/kubelogin/pkg/testing/logger"
|
||||
"github.com/int128/kubelogin/pkg/tlsclientconfig"
|
||||
"github.com/int128/kubelogin/pkg/usecases/authentication"
|
||||
"github.com/int128/kubelogin/pkg/usecases/authentication/mock_authentication"
|
||||
)
|
||||
|
||||
func TestSetup_DoStage2(t *testing.T) {
|
||||
issuedIDToken := testingJWT.EncodeF(t, func(claims *testingJWT.Claims) {
|
||||
claims.Issuer = "https://issuer.example.com"
|
||||
claims.Subject = "YOUR_SUBJECT"
|
||||
claims.ExpiresAt = time.Now().Add(1 * time.Hour).Unix()
|
||||
})
|
||||
dummyTLSClientConfig := tlsclientconfig.Config{
|
||||
CACertFilename: []string{"/path/to/cert"},
|
||||
}
|
||||
var grantOptionSet authentication.GrantOptionSet
|
||||
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
ctx := context.Background()
|
||||
|
||||
in := Stage2Input{
|
||||
IssuerURL: "https://accounts.google.com",
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
ExtraScopes: []string{"email"},
|
||||
CACertFilename: "/path/to/cert",
|
||||
SkipTLSVerify: true,
|
||||
GrantOptionSet: grantOptionSet,
|
||||
IssuerURL: "https://accounts.google.com",
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
ExtraScopes: []string{"email"},
|
||||
GrantOptionSet: grantOptionSet,
|
||||
TLSClientConfig: dummyTLSClientConfig,
|
||||
}
|
||||
|
||||
mockCertPool := mock_certpool.NewMockInterface(ctrl)
|
||||
mockCertPool.EXPECT().
|
||||
AddFile("/path/to/cert")
|
||||
mockAuthentication := mock_authentication.NewMockInterface(ctrl)
|
||||
mockAuthentication.EXPECT().
|
||||
Do(ctx, authentication.Input{
|
||||
IssuerURL: "https://accounts.google.com",
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
ExtraScopes: []string{"email"},
|
||||
CertPool: mockCertPool,
|
||||
SkipTLSVerify: true,
|
||||
GrantOptionSet: grantOptionSet,
|
||||
Provider: oidc.Provider{
|
||||
IssuerURL: "https://accounts.google.com",
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
ExtraScopes: []string{"email"},
|
||||
},
|
||||
GrantOptionSet: grantOptionSet,
|
||||
TLSClientConfig: dummyTLSClientConfig,
|
||||
}).
|
||||
Return(&authentication.Output{
|
||||
TokenSet: oidc.TokenSet{
|
||||
IDToken: "YOUR_ID_TOKEN",
|
||||
IDToken: issuedIDToken,
|
||||
RefreshToken: "YOUR_REFRESH_TOKEN",
|
||||
IDTokenClaims: jwt.Claims{
|
||||
Subject: "YOUR_SUBJECT",
|
||||
Expiry: time.Date(2019, 1, 2, 3, 4, 5, 0, time.UTC),
|
||||
Pretty: "PRETTY_JSON",
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
u := Setup{
|
||||
Authentication: mockAuthentication,
|
||||
NewCertPool: func() certpool.Interface { return mockCertPool },
|
||||
Logger: logger.New(t),
|
||||
}
|
||||
if err := u.DoStage2(ctx, in); err != nil {
|
||||
|
||||
@@ -4,9 +4,10 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/google/wire"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/certpool"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/kubeconfig"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/logger"
|
||||
"github.com/int128/kubelogin/pkg/oidc"
|
||||
"github.com/int128/kubelogin/pkg/tlsclientconfig"
|
||||
"github.com/int128/kubelogin/pkg/usecases/authentication"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
@@ -28,10 +29,8 @@ type Input struct {
|
||||
KubeconfigFilename string // Default to the environment variable or global config as kubectl
|
||||
KubeconfigContext kubeconfig.ContextName // Default to the current context but ignored if KubeconfigUser is set
|
||||
KubeconfigUser kubeconfig.UserName // Default to the user of the context
|
||||
CACertFilename string // optional
|
||||
CACertData string // optional
|
||||
SkipTLSVerify bool
|
||||
GrantOptionSet authentication.GrantOptionSet
|
||||
TLSClientConfig tlsclientconfig.Config
|
||||
}
|
||||
|
||||
const oidcConfigErrorMessage = `No configuration found.
|
||||
@@ -57,7 +56,6 @@ See https://github.com/int128/kubelogin for more.
|
||||
type Standalone struct {
|
||||
Authentication authentication.Interface
|
||||
Kubeconfig kubeconfig.Interface
|
||||
NewCertPool certpool.NewFunc
|
||||
Logger logger.Interface
|
||||
}
|
||||
|
||||
@@ -72,50 +70,51 @@ func (u *Standalone) Do(ctx context.Context, in Input) error {
|
||||
u.Logger.Printf(deprecationMessage)
|
||||
u.Logger.V(1).Infof("using the authentication provider of the user %s", authProvider.UserName)
|
||||
u.Logger.V(1).Infof("a token will be written to %s", authProvider.LocationOfOrigin)
|
||||
certPool := u.NewCertPool()
|
||||
if authProvider.IDPCertificateAuthority != "" {
|
||||
if err := certPool.AddFile(authProvider.IDPCertificateAuthority); err != nil {
|
||||
return xerrors.Errorf("could not load the certificate of idp-certificate-authority: %w", err)
|
||||
}
|
||||
u.Logger.V(1).Infof("using the certificate %s", authProvider.IDPCertificateAuthority)
|
||||
in.TLSClientConfig.CACertFilename = append(in.TLSClientConfig.CACertFilename, authProvider.IDPCertificateAuthority)
|
||||
}
|
||||
if authProvider.IDPCertificateAuthorityData != "" {
|
||||
if err := certPool.AddBase64Encoded(authProvider.IDPCertificateAuthorityData); err != nil {
|
||||
return xerrors.Errorf("could not load the certificate of idp-certificate-authority-data: %w", err)
|
||||
u.Logger.V(1).Infof("using the certificate in %s", authProvider.LocationOfOrigin)
|
||||
in.TLSClientConfig.CACertData = append(in.TLSClientConfig.CACertData, authProvider.IDPCertificateAuthorityData)
|
||||
}
|
||||
var cachedTokenSet *oidc.TokenSet
|
||||
if authProvider.IDToken != "" {
|
||||
cachedTokenSet = &oidc.TokenSet{
|
||||
IDToken: authProvider.IDToken,
|
||||
RefreshToken: authProvider.RefreshToken,
|
||||
}
|
||||
}
|
||||
if in.CACertFilename != "" {
|
||||
if err := certPool.AddFile(in.CACertFilename); err != nil {
|
||||
return xerrors.Errorf("could not load the certificate file: %w", err)
|
||||
}
|
||||
|
||||
authenticationInput := authentication.Input{
|
||||
Provider: oidc.Provider{
|
||||
IssuerURL: authProvider.IDPIssuerURL,
|
||||
ClientID: authProvider.ClientID,
|
||||
ClientSecret: authProvider.ClientSecret,
|
||||
ExtraScopes: authProvider.ExtraScopes,
|
||||
},
|
||||
GrantOptionSet: in.GrantOptionSet,
|
||||
CachedTokenSet: cachedTokenSet,
|
||||
TLSClientConfig: in.TLSClientConfig,
|
||||
}
|
||||
if in.CACertData != "" {
|
||||
if err := certPool.AddBase64Encoded(in.CACertData); err != nil {
|
||||
return xerrors.Errorf("could not load the certificate data: %w", err)
|
||||
}
|
||||
}
|
||||
out, err := u.Authentication.Do(ctx, authentication.Input{
|
||||
IssuerURL: authProvider.IDPIssuerURL,
|
||||
ClientID: authProvider.ClientID,
|
||||
ClientSecret: authProvider.ClientSecret,
|
||||
ExtraScopes: authProvider.ExtraScopes,
|
||||
CertPool: certPool,
|
||||
SkipTLSVerify: in.SkipTLSVerify,
|
||||
IDToken: authProvider.IDToken,
|
||||
RefreshToken: authProvider.RefreshToken,
|
||||
GrantOptionSet: in.GrantOptionSet,
|
||||
})
|
||||
authenticationOutput, err := u.Authentication.Do(ctx, authenticationInput)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("authentication error: %w", err)
|
||||
}
|
||||
u.Logger.V(1).Infof("you got a token: %s", out.TokenSet.IDTokenClaims.Pretty)
|
||||
if out.AlreadyHasValidIDToken {
|
||||
u.Logger.Printf("You already have a valid token until %s", out.TokenSet.IDTokenClaims.Expiry)
|
||||
|
||||
idTokenClaims, err := authenticationOutput.TokenSet.DecodeWithoutVerify()
|
||||
if err != nil {
|
||||
return xerrors.Errorf("you got an invalid token: %w", err)
|
||||
}
|
||||
u.Logger.V(1).Infof("you got a token: %s", idTokenClaims.Pretty)
|
||||
if authenticationOutput.AlreadyHasValidIDToken {
|
||||
u.Logger.Printf("You already have a valid token until %s", idTokenClaims.Expiry)
|
||||
return nil
|
||||
}
|
||||
|
||||
u.Logger.Printf("You got a valid token until %s", out.TokenSet.IDTokenClaims.Expiry)
|
||||
authProvider.IDToken = out.TokenSet.IDToken
|
||||
authProvider.RefreshToken = out.TokenSet.RefreshToken
|
||||
u.Logger.Printf("You got a valid token until %s", idTokenClaims.Expiry)
|
||||
authProvider.IDToken = authenticationOutput.TokenSet.IDToken
|
||||
authProvider.RefreshToken = authenticationOutput.TokenSet.RefreshToken
|
||||
u.Logger.V(1).Infof("writing the ID token and refresh token to %s", authProvider.LocationOfOrigin)
|
||||
if err := u.Kubeconfig.UpdateAuthProvider(authProvider); err != nil {
|
||||
return xerrors.Errorf("could not update the kubeconfig: %w", err)
|
||||
|
||||
@@ -6,24 +6,24 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/certpool"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/certpool/mock_certpool"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/kubeconfig"
|
||||
"github.com/int128/kubelogin/pkg/adaptors/kubeconfig/mock_kubeconfig"
|
||||
"github.com/int128/kubelogin/pkg/jwt"
|
||||
"github.com/int128/kubelogin/pkg/oidc"
|
||||
testingJWT "github.com/int128/kubelogin/pkg/testing/jwt"
|
||||
"github.com/int128/kubelogin/pkg/testing/logger"
|
||||
"github.com/int128/kubelogin/pkg/tlsclientconfig"
|
||||
"github.com/int128/kubelogin/pkg/usecases/authentication"
|
||||
"github.com/int128/kubelogin/pkg/usecases/authentication/mock_authentication"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
func TestStandalone_Do(t *testing.T) {
|
||||
dummyTokenClaims := jwt.Claims{
|
||||
Subject: "YOUR_SUBJECT",
|
||||
Expiry: time.Date(2019, 1, 2, 3, 4, 5, 0, time.UTC),
|
||||
Pretty: "PRETTY_JSON",
|
||||
}
|
||||
issuedIDTokenExpiration := time.Now().Add(1 * time.Hour).Round(time.Second)
|
||||
issuedIDToken := testingJWT.EncodeF(t, func(claims *testingJWT.Claims) {
|
||||
claims.Issuer = "https://accounts.google.com"
|
||||
claims.Subject = "YOUR_SUBJECT"
|
||||
claims.ExpiresAt = issuedIDTokenExpiration.Unix()
|
||||
})
|
||||
|
||||
t.Run("FullOptions", func(t *testing.T) {
|
||||
var grantOptionSet authentication.GrantOptionSet
|
||||
@@ -34,9 +34,6 @@ func TestStandalone_Do(t *testing.T) {
|
||||
KubeconfigFilename: "/path/to/kubeconfig",
|
||||
KubeconfigContext: "theContext",
|
||||
KubeconfigUser: "theUser",
|
||||
CACertFilename: "/path/to/cert1",
|
||||
CACertData: "BASE64ENCODED1",
|
||||
SkipTLSVerify: true,
|
||||
GrantOptionSet: grantOptionSet,
|
||||
}
|
||||
currentAuthProvider := &kubeconfig.AuthProvider{
|
||||
@@ -48,15 +45,6 @@ func TestStandalone_Do(t *testing.T) {
|
||||
IDPCertificateAuthority: "/path/to/cert2",
|
||||
IDPCertificateAuthorityData: "BASE64ENCODED2",
|
||||
}
|
||||
mockCertPool := mock_certpool.NewMockInterface(ctrl)
|
||||
mockCertPool.EXPECT().
|
||||
AddFile("/path/to/cert1")
|
||||
mockCertPool.EXPECT().
|
||||
AddFile("/path/to/cert2")
|
||||
mockCertPool.EXPECT().
|
||||
AddBase64Encoded("BASE64ENCODED1")
|
||||
mockCertPool.EXPECT().
|
||||
AddBase64Encoded("BASE64ENCODED2")
|
||||
mockKubeconfig := mock_kubeconfig.NewMockInterface(ctrl)
|
||||
mockKubeconfig.EXPECT().
|
||||
GetCurrentAuthProvider("/path/to/kubeconfig", kubeconfig.ContextName("theContext"), kubeconfig.UserName("theUser")).
|
||||
@@ -70,30 +58,32 @@ func TestStandalone_Do(t *testing.T) {
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
IDPCertificateAuthority: "/path/to/cert2",
|
||||
IDPCertificateAuthorityData: "BASE64ENCODED2",
|
||||
IDToken: "YOUR_ID_TOKEN",
|
||||
IDToken: issuedIDToken,
|
||||
RefreshToken: "YOUR_REFRESH_TOKEN",
|
||||
})
|
||||
mockAuthentication := mock_authentication.NewMockInterface(ctrl)
|
||||
mockAuthentication.EXPECT().
|
||||
Do(ctx, authentication.Input{
|
||||
IssuerURL: "https://accounts.google.com",
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
CertPool: mockCertPool,
|
||||
SkipTLSVerify: true,
|
||||
Provider: oidc.Provider{
|
||||
IssuerURL: "https://accounts.google.com",
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
},
|
||||
GrantOptionSet: grantOptionSet,
|
||||
TLSClientConfig: tlsclientconfig.Config{
|
||||
CACertFilename: []string{"/path/to/cert2"},
|
||||
CACertData: []string{"BASE64ENCODED2"},
|
||||
},
|
||||
}).
|
||||
Return(&authentication.Output{
|
||||
TokenSet: oidc.TokenSet{
|
||||
IDToken: "YOUR_ID_TOKEN",
|
||||
RefreshToken: "YOUR_REFRESH_TOKEN",
|
||||
IDTokenClaims: dummyTokenClaims,
|
||||
IDToken: issuedIDToken,
|
||||
RefreshToken: "YOUR_REFRESH_TOKEN",
|
||||
},
|
||||
}, nil)
|
||||
u := Standalone{
|
||||
Authentication: mockAuthentication,
|
||||
Kubeconfig: mockKubeconfig,
|
||||
NewCertPool: func() certpool.Interface { return mockCertPool },
|
||||
Logger: logger.New(t),
|
||||
}
|
||||
if err := u.Do(ctx, in); err != nil {
|
||||
@@ -112,9 +102,8 @@ func TestStandalone_Do(t *testing.T) {
|
||||
IDPIssuerURL: "https://accounts.google.com",
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
IDToken: "VALID_ID_TOKEN",
|
||||
IDToken: issuedIDToken,
|
||||
}
|
||||
mockCertPool := mock_certpool.NewMockInterface(ctrl)
|
||||
mockKubeconfig := mock_kubeconfig.NewMockInterface(ctrl)
|
||||
mockKubeconfig.EXPECT().
|
||||
GetCurrentAuthProvider("", kubeconfig.ContextName(""), kubeconfig.UserName("")).
|
||||
@@ -122,23 +111,24 @@ func TestStandalone_Do(t *testing.T) {
|
||||
mockAuthentication := mock_authentication.NewMockInterface(ctrl)
|
||||
mockAuthentication.EXPECT().
|
||||
Do(ctx, authentication.Input{
|
||||
IssuerURL: "https://accounts.google.com",
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
IDToken: "VALID_ID_TOKEN",
|
||||
CertPool: mockCertPool,
|
||||
Provider: oidc.Provider{
|
||||
IssuerURL: "https://accounts.google.com",
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
},
|
||||
CachedTokenSet: &oidc.TokenSet{
|
||||
IDToken: issuedIDToken,
|
||||
},
|
||||
}).
|
||||
Return(&authentication.Output{
|
||||
AlreadyHasValidIDToken: true,
|
||||
TokenSet: oidc.TokenSet{
|
||||
IDToken: "VALID_ID_TOKEN",
|
||||
IDTokenClaims: dummyTokenClaims,
|
||||
IDToken: issuedIDToken,
|
||||
},
|
||||
}, nil)
|
||||
u := Standalone{
|
||||
Authentication: mockAuthentication,
|
||||
Kubeconfig: mockKubeconfig,
|
||||
NewCertPool: func() certpool.Interface { return mockCertPool },
|
||||
Logger: logger.New(t),
|
||||
}
|
||||
if err := u.Do(ctx, in); err != nil {
|
||||
@@ -178,7 +168,6 @@ func TestStandalone_Do(t *testing.T) {
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
}
|
||||
mockCertPool := mock_certpool.NewMockInterface(ctrl)
|
||||
mockKubeconfig := mock_kubeconfig.NewMockInterface(ctrl)
|
||||
mockKubeconfig.EXPECT().
|
||||
GetCurrentAuthProvider("", kubeconfig.ContextName(""), kubeconfig.UserName("")).
|
||||
@@ -186,16 +175,16 @@ func TestStandalone_Do(t *testing.T) {
|
||||
mockAuthentication := mock_authentication.NewMockInterface(ctrl)
|
||||
mockAuthentication.EXPECT().
|
||||
Do(ctx, authentication.Input{
|
||||
IssuerURL: "https://accounts.google.com",
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
CertPool: mockCertPool,
|
||||
Provider: oidc.Provider{
|
||||
IssuerURL: "https://accounts.google.com",
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
},
|
||||
}).
|
||||
Return(nil, xerrors.New("authentication error"))
|
||||
u := Standalone{
|
||||
Authentication: mockAuthentication,
|
||||
Kubeconfig: mockKubeconfig,
|
||||
NewCertPool: func() certpool.Interface { return mockCertPool },
|
||||
Logger: logger.New(t),
|
||||
}
|
||||
if err := u.Do(ctx, in); err == nil {
|
||||
@@ -215,7 +204,6 @@ func TestStandalone_Do(t *testing.T) {
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
}
|
||||
mockCertPool := mock_certpool.NewMockInterface(ctrl)
|
||||
mockKubeconfig := mock_kubeconfig.NewMockInterface(ctrl)
|
||||
mockKubeconfig.EXPECT().
|
||||
GetCurrentAuthProvider("", kubeconfig.ContextName(""), kubeconfig.UserName("")).
|
||||
@@ -227,29 +215,28 @@ func TestStandalone_Do(t *testing.T) {
|
||||
IDPIssuerURL: "https://accounts.google.com",
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
IDToken: "YOUR_ID_TOKEN",
|
||||
IDToken: issuedIDToken,
|
||||
RefreshToken: "YOUR_REFRESH_TOKEN",
|
||||
}).
|
||||
Return(xerrors.New("I/O error"))
|
||||
mockAuthentication := mock_authentication.NewMockInterface(ctrl)
|
||||
mockAuthentication.EXPECT().
|
||||
Do(ctx, authentication.Input{
|
||||
IssuerURL: "https://accounts.google.com",
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
CertPool: mockCertPool,
|
||||
Provider: oidc.Provider{
|
||||
IssuerURL: "https://accounts.google.com",
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
},
|
||||
}).
|
||||
Return(&authentication.Output{
|
||||
TokenSet: oidc.TokenSet{
|
||||
IDToken: "YOUR_ID_TOKEN",
|
||||
RefreshToken: "YOUR_REFRESH_TOKEN",
|
||||
IDTokenClaims: dummyTokenClaims,
|
||||
IDToken: issuedIDToken,
|
||||
RefreshToken: "YOUR_REFRESH_TOKEN",
|
||||
},
|
||||
}, nil)
|
||||
u := Standalone{
|
||||
Authentication: mockAuthentication,
|
||||
Kubeconfig: mockKubeconfig,
|
||||
NewCertPool: func() certpool.Interface { return mockCertPool },
|
||||
Logger: logger.New(t),
|
||||
}
|
||||
if err := u.Do(ctx, in); err == nil {
|
||||
|
||||
Reference in New Issue
Block a user