mirror of
https://github.com/int128/kubelogin.git
synced 2026-02-17 18:09:51 +00:00
Compare commits
64 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
342ace8aec | ||
|
|
259303299e | ||
|
|
64b42f8f8c | ||
|
|
31f182cc37 | ||
|
|
57873e9338 | ||
|
|
531792ba02 | ||
|
|
bd66f19bd5 | ||
|
|
951e1c4713 | ||
|
|
aed6620066 | ||
|
|
140b612357 | ||
|
|
efc5ce1571 | ||
|
|
c20af93fd4 | ||
|
|
6c7e7f7dad | ||
|
|
1af1bb2910 | ||
|
|
e1863154df | ||
|
|
c7736355b6 | ||
|
|
93d83b9365 | ||
|
|
d5f9e3c88e | ||
|
|
4d10746b42 | ||
|
|
b26cd49178 | ||
|
|
19aeb78113 | ||
|
|
b359f0de12 | ||
|
|
c91e9bee42 | ||
|
|
88fb3c2ea8 | ||
|
|
751469d3b9 | ||
|
|
c29ab9c8ce | ||
|
|
19e4da8f4d | ||
|
|
fcb4a27cde | ||
|
|
021bc77094 | ||
|
|
f9367d6fd1 | ||
|
|
516d8bae41 | ||
|
|
22153cc1f4 | ||
|
|
45dd932876 | ||
|
|
6b108ffb9d | ||
|
|
cf3063c3bf | ||
|
|
c7060d2ca5 | ||
|
|
8002eeb191 | ||
|
|
b95872b24f | ||
|
|
2d5775315f | ||
|
|
ba2e5e5fea | ||
|
|
525cdae92d | ||
|
|
30b7f47e70 | ||
|
|
284cd851ea | ||
|
|
4cf5b302fe | ||
|
|
63dcbeb6f5 | ||
|
|
ea6b3815bf | ||
|
|
a5f746ad6e | ||
|
|
434c69407e | ||
|
|
8ec95ed141 | ||
|
|
dee9032023 | ||
|
|
cb7a9742ac | ||
|
|
a3013a12b9 | ||
|
|
bc7e71f586 | ||
|
|
19d61e70a9 | ||
|
|
3a38753ee7 | ||
|
|
56e09ad65e | ||
|
|
58a4b1399f | ||
|
|
6726d851cb | ||
|
|
21e03dc294 | ||
|
|
5f1ed82a85 | ||
|
|
abb1a564f4 | ||
|
|
6d4eee5d1d | ||
|
|
4c10146639 | ||
|
|
3121e55498 |
34
.github/workflows/acceptance-test.yaml
vendored
Normal file
34
.github/workflows/acceptance-test.yaml
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
name: acceptance-test
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- .github/workflows/acceptance-test.yaml
|
||||
- acceptance_test/**
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- .github/workflows/acceptance-test.yaml
|
||||
- acceptance_test/**
|
||||
|
||||
jobs:
|
||||
test-makefile:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache-dependency-path: go.sum
|
||||
- run: make -C acceptance_test check
|
||||
- run: make -C acceptance_test
|
||||
env:
|
||||
OIDC_ISSUER_URL: https://accounts.google.com
|
||||
OIDC_CLIENT_ID: REDACTED.apps.googleusercontent.com
|
||||
YOUR_EMAIL: REDACTED@gmail.com
|
||||
- run: make -C acceptance_test delete-cluster
|
||||
- run: make -C acceptance_test clean
|
||||
18
.github/workflows/docker.yaml
vendored
18
.github/workflows/docker.yaml
vendored
@@ -28,15 +28,15 @@ jobs:
|
||||
contents: read
|
||||
packages: write
|
||||
outputs:
|
||||
image-uri: ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}
|
||||
image-uri: ${{ steps.build-metadata.outputs.image-uri }}
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
|
||||
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- uses: docker/metadata-action@369eb591f429131d6889c46b94e711f089e6ca96 # v5.6.1
|
||||
- uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
|
||||
id: metadata
|
||||
with:
|
||||
images: ghcr.io/${{ github.repository }}
|
||||
@@ -44,9 +44,9 @@ jobs:
|
||||
id: cache
|
||||
with:
|
||||
image: ghcr.io/${{ github.repository }}/cache
|
||||
- uses: docker/setup-qemu-action@53851d14592bedcffcf25ea515637cff71ef929a # v3.3.0
|
||||
- uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0
|
||||
- uses: docker/build-push-action@67a2d409c0a876cbe6b11854e3e25193efe4e62d # v6.12.0
|
||||
- uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
|
||||
- uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
|
||||
- uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6.16.0
|
||||
id: build
|
||||
with:
|
||||
push: ${{ github.event_name == 'push' }}
|
||||
@@ -58,9 +58,13 @@ jobs:
|
||||
linux/amd64
|
||||
linux/arm64
|
||||
linux/ppc64le
|
||||
- uses: int128/docker-build-metadata-action@fac3c879c58b212e339c5e959cabb865cbee0c6e # v1.0.0
|
||||
id: build-metadata
|
||||
with:
|
||||
metadata: ${{ steps.build.outputs.metadata }}
|
||||
|
||||
test:
|
||||
if: github.event_name == 'push'
|
||||
if: needs.build.outputs.image-uri != ''
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
|
||||
16
.github/workflows/go.yaml
vendored
16
.github/workflows/go.yaml
vendored
@@ -30,7 +30,7 @@ jobs:
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
|
||||
- uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache-dependency-path: go.sum
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
|
||||
- uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache-dependency-path: go.sum
|
||||
@@ -59,10 +59,10 @@ jobs:
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
|
||||
- uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0
|
||||
with:
|
||||
go-version-file: tools/go.mod
|
||||
cache-dependency-path: tools/go.sum
|
||||
go-version-file: go.mod
|
||||
cache-dependency-path: go.sum
|
||||
- run: make lint
|
||||
|
||||
generate:
|
||||
@@ -70,10 +70,10 @@ jobs:
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
|
||||
- uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0
|
||||
with:
|
||||
go-version-file: tools/go.mod
|
||||
cache-dependency-path: tools/go.sum
|
||||
go-version-file: go.mod
|
||||
cache-dependency-path: go.sum
|
||||
- run: go mod tidy
|
||||
- run: make generate
|
||||
- uses: int128/update-generated-files-action@65b9a7ae3ededc5679d78343f58fbebcf1ebd785 # v2.57.0
|
||||
|
||||
2
.github/workflows/release.yaml
vendored
2
.github/workflows/release.yaml
vendored
@@ -58,7 +58,7 @@ jobs:
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
|
||||
- uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache-dependency-path: go.sum
|
||||
|
||||
2
.github/workflows/system-test.yaml
vendored
2
.github/workflows/system-test.yaml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
|
||||
- uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache-dependency-path: go.sum
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM --platform=$BUILDPLATFORM golang:1.23 AS builder
|
||||
FROM --platform=$BUILDPLATFORM golang:1.24 AS builder
|
||||
|
||||
WORKDIR /builder
|
||||
|
||||
|
||||
8
Makefile
8
Makefile
@@ -11,12 +11,10 @@ integration-test:
|
||||
|
||||
.PHONY: generate
|
||||
generate:
|
||||
$(MAKE) -C tools
|
||||
./tools/bin/wire ./pkg/di
|
||||
go tool github.com/google/wire/cmd/wire ./pkg/di
|
||||
rm -fr mocks/
|
||||
./tools/bin/mockery
|
||||
go tool github.com/vektra/mockery/v2
|
||||
|
||||
.PHONY: lint
|
||||
lint:
|
||||
$(MAKE) -C tools
|
||||
./tools/bin/golangci-lint run
|
||||
go tool github.com/golangci/golangci-lint/cmd/golangci-lint run
|
||||
|
||||
@@ -21,7 +21,7 @@ Install the latest release from [Homebrew](https://brew.sh/), [Krew](https://git
|
||||
|
||||
```sh
|
||||
# Homebrew (macOS and Linux)
|
||||
brew install int128/kubelogin/kubelogin
|
||||
brew install kubelogin
|
||||
|
||||
# Krew (macOS, Linux, Windows and ARM)
|
||||
kubectl krew install oidc-login
|
||||
@@ -83,8 +83,8 @@ If the refresh token has expired, it will perform re-authentication.
|
||||
|
||||
### Token cache
|
||||
|
||||
If the OS keyring is available, kubelogin stores the token cache to the OS keyring.
|
||||
Otherwise, kubelogin stores the token cache to the file system.
|
||||
Kubelogin stores the token cache to the file system by default.
|
||||
For enhanced security, it is recommended to store it to the keyring.
|
||||
See the [token cache](docs/usage.md#token-cache) for details.
|
||||
|
||||
You can log out by deleting the token cache.
|
||||
@@ -92,7 +92,7 @@ You can log out by deleting the token cache.
|
||||
```console
|
||||
% kubectl oidc-login clean
|
||||
Deleted the token cache at /home/user/.kube/cache/oidc-login
|
||||
Deleted the token cache in the keyring
|
||||
Deleted the token cache from the keyring
|
||||
```
|
||||
|
||||
Kubelogin will ask you to log in via the browser again.
|
||||
|
||||
@@ -4,33 +4,38 @@ OUTPUT_DIR := $(CURDIR)/output
|
||||
KUBECONFIG := $(OUTPUT_DIR)/kubeconfig.yaml
|
||||
export KUBECONFIG
|
||||
|
||||
# create a Kubernetes cluster
|
||||
.PHONY: cluster
|
||||
cluster:
|
||||
# create a cluster
|
||||
# Create a cluster.
|
||||
mkdir -p $(OUTPUT_DIR)
|
||||
sed -e "s|OIDC_ISSUER_URL|$(OIDC_ISSUER_URL)|" -e "s|OIDC_CLIENT_ID|$(OIDC_CLIENT_ID)|" cluster.yaml > $(OUTPUT_DIR)/cluster.yaml
|
||||
kind create cluster --name $(CLUSTER_NAME) --config $(OUTPUT_DIR)/cluster.yaml
|
||||
# set up access control
|
||||
|
||||
# Set up the access control.
|
||||
kubectl create clusterrole cluster-readonly --verb=get,watch,list --resource='*.*'
|
||||
kubectl create clusterrolebinding cluster-readonly --clusterrole=cluster-readonly --user=$(YOUR_EMAIL)
|
||||
# set up kubectl
|
||||
|
||||
# Set up kubectl.
|
||||
kubectl config set-credentials oidc \
|
||||
--exec-api-version=client.authentication.k8s.io/v1beta1 \
|
||||
--exec-api-version=client.authentication.k8s.io/v1 \
|
||||
--exec-interactive-mode=Never \
|
||||
--exec-command=$(CURDIR)/../kubelogin \
|
||||
--exec-arg=get-token \
|
||||
--exec-arg=--token-cache-dir=$(OUTPUT_DIR)/token-cache \
|
||||
--exec-arg=--oidc-issuer-url=$(OIDC_ISSUER_URL) \
|
||||
--exec-arg=--oidc-client-id=$(OIDC_CLIENT_ID) \
|
||||
--exec-arg=--oidc-client-secret=$(OIDC_CLIENT_SECRET) \
|
||||
--exec-arg=--oidc-extra-scope=email
|
||||
# switch the default user
|
||||
|
||||
# Switch the default user.
|
||||
kubectl config set-context --current --user=oidc
|
||||
|
||||
# clean up the resources
|
||||
# Show the kubeconfig.
|
||||
kubectl config view
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
-rm -r $(OUTPUT_DIR)
|
||||
|
||||
.PHONY: delete-cluster
|
||||
delete-cluster:
|
||||
kind delete cluster --name $(CLUSTER_NAME)
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
# kubelogin/acceptance_test
|
||||
|
||||
This is a manual test for verifying Kubernetes OIDC authentication with your OIDC provider.
|
||||
|
||||
This is a manual test to verify if the Kubernetes OIDC authentication works with your OIDC provider.
|
||||
|
||||
## Purpose
|
||||
|
||||
This test checks the following points:
|
||||
|
||||
1. You can set up your OIDC provider using [setup guide](../docs/setup.md).
|
||||
1. You can set up your OIDC provider using the [setup guide](../docs/setup.md).
|
||||
1. The plugin works with your OIDC provider.
|
||||
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Prerequisite
|
||||
@@ -22,7 +20,7 @@ make -C ..
|
||||
```
|
||||
|
||||
You need to set up your provider.
|
||||
See [setup guide](../docs/setup.md) for more.
|
||||
See the [setup guide](../docs/setup.md) for more.
|
||||
|
||||
You need to install the following tools:
|
||||
|
||||
@@ -44,7 +42,6 @@ For example, you can create a cluster with Google account authentication.
|
||||
```sh
|
||||
make OIDC_ISSUER_URL=https://accounts.google.com \
|
||||
OIDC_CLIENT_ID=REDACTED.apps.googleusercontent.com \
|
||||
OIDC_CLIENT_SECRET=REDACTED \
|
||||
YOUR_EMAIL=REDACTED@gmail.com
|
||||
```
|
||||
|
||||
|
||||
@@ -24,17 +24,18 @@ See the [usage](usage.md) for the details.
|
||||
|
||||
You can log in with a Google account.
|
||||
|
||||
Open [Google APIs Console](https://console.developers.google.com/apis/credentials) and create an OAuth client with the following setting:
|
||||
Open [Google APIs Console](https://console.cloud.google.com/apis/credentials) and create an OAuth client with the following setting:
|
||||
|
||||
- Application Type: Other
|
||||
- Application Type: Desktop app
|
||||
|
||||
Check the client ID and secret.
|
||||
Replace the following variables in the later sections.
|
||||
|
||||
| Variable | Value |
|
||||
| ---------------- | -------------------------------- |
|
||||
| `ISSUER_URL` | `https://accounts.google.com` |
|
||||
| `YOUR_CLIENT_ID` | `xxx.apps.googleusercontent.com` |
|
||||
| Variable | Value |
|
||||
| -------------------- | -------------------------------- |
|
||||
| `ISSUER_URL` | `https://accounts.google.com` |
|
||||
| `YOUR_CLIENT_ID` | `xxx.apps.googleusercontent.com` |
|
||||
| `YOUR_CLIENT_SECRET` | `XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX` |
|
||||
|
||||
### Keycloak
|
||||
|
||||
@@ -67,6 +68,8 @@ Replace the following variables in the later sections.
|
||||
| `ISSUER_URL` | `https://keycloak.example.com/auth/realms/YOUR_REALM` |
|
||||
| `YOUR_CLIENT_ID` | `YOUR_CLIENT_ID` |
|
||||
|
||||
`YOUR_CLIENT_SECRET` is not required for this configuration.
|
||||
|
||||
### Dex with GitHub
|
||||
|
||||
You can log in with a GitHub account.
|
||||
@@ -129,7 +132,7 @@ Replace the following variables in the later sections.
|
||||
| `ISSUER_URL` | `https://YOUR_ORGANIZATION.okta.com` |
|
||||
| `YOUR_CLIENT_ID` | random string |
|
||||
|
||||
You do not need to set `YOUR_CLIENT_SECRET`.
|
||||
`YOUR_CLIENT_SECRET` is not required for this configuration.
|
||||
|
||||
If you need `groups` claim for access control,
|
||||
see [jetstack/okta-kubectl-auth](https://github.com/jetstack/okta-kubectl-auth/blob/master/docs/okta-setup.md) and [#250](https://github.com/int128/kubelogin/issues/250).
|
||||
@@ -154,7 +157,7 @@ Leverage the following variables in the next steps.
|
||||
|
||||
`YOUR_CLIENT_SECRET` is not required for this configuration.
|
||||
|
||||
## 2. Verify authentication
|
||||
## 2. Authenticate with the OpenID Connect Provider
|
||||
|
||||
Run the following command:
|
||||
|
||||
@@ -164,11 +167,12 @@ kubectl oidc-login setup \
|
||||
--oidc-client-id=YOUR_CLIENT_ID
|
||||
```
|
||||
|
||||
If your provider requires a client secret, add `--oidc-client-secret=YOUR_CLIENT_SECRET`.
|
||||
|
||||
It launches the browser and navigates to `http://localhost:8000`.
|
||||
Please log in to the provider.
|
||||
|
||||
You can set extra options, for example, extra scope or CA certificate.
|
||||
See also the full options.
|
||||
For the full options,
|
||||
|
||||
```sh
|
||||
kubectl oidc-login setup --help
|
||||
@@ -176,14 +180,12 @@ kubectl oidc-login setup --help
|
||||
|
||||
## 3. Bind a cluster role
|
||||
|
||||
Here bind `cluster-admin` role to you.
|
||||
You can run the following command to bind `cluster-admin` role to you:
|
||||
|
||||
```sh
|
||||
kubectl create clusterrolebinding oidc-cluster-admin --clusterrole=cluster-admin --user='ISSUER_URL#YOUR_SUBJECT'
|
||||
```
|
||||
|
||||
As well as you can create a custom cluster role and bind it.
|
||||
|
||||
## 4. Set up the Kubernetes API server
|
||||
|
||||
Add the following flags to kube-apiserver:
|
||||
@@ -201,6 +203,7 @@ Add `oidc` user to the kubeconfig.
|
||||
|
||||
```sh
|
||||
kubectl config set-credentials oidc \
|
||||
--exec-interactive-mode=Never \
|
||||
--exec-api-version=client.authentication.k8s.io/v1 \
|
||||
--exec-command=kubectl \
|
||||
--exec-arg=oidc-login \
|
||||
@@ -209,6 +212,11 @@ kubectl config set-credentials oidc \
|
||||
--exec-arg=--oidc-client-id=YOUR_CLIENT_ID
|
||||
```
|
||||
|
||||
If your provider requires a client secret, add `--oidc-client-secret=YOUR_CLIENT_SECRET`.
|
||||
|
||||
For security, it is recommended to add `--token-cache-storage=keyring` to store the token cache to the keyring instead of the file system.
|
||||
If you encounter an error, see the [token cache](usage.md#token-cache) for details.
|
||||
|
||||
## 6. Verify cluster access
|
||||
|
||||
Make sure you can access the Kubernetes cluster.
|
||||
|
||||
@@ -14,7 +14,7 @@ Flags:
|
||||
--oidc-use-access-token Instead of using the id_token, use the access_token to authenticate to Kubernetes
|
||||
--force-refresh If set, refresh the ID token regardless of its expiration time
|
||||
--token-cache-dir string Path to a directory of the token cache (default "~/.kube/cache/oidc-login")
|
||||
--token-cache-storage string Storage for the token cache. One of (auto|keyring|disk) (default "auto")
|
||||
--token-cache-storage string Storage for the token cache. One of (disk|keyring) (default "disk")
|
||||
--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 [SECURITY RISK] If set, the server's certificate will not be checked for validity
|
||||
@@ -52,24 +52,6 @@ Global Flags:
|
||||
--vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging
|
||||
```
|
||||
|
||||
Here is the sequence diagram of the credential plugin.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
actor User
|
||||
User ->>+ kubectl: Run
|
||||
kubectl ->>+ kubelogin: Run the plugin
|
||||
kubelogin ->>+ Provider: Authentication request
|
||||
Note over User, Provider: Browser interaction
|
||||
Provider -->>- kubelogin: Authentication response
|
||||
kubelogin ->>+ Provider: Token request
|
||||
Provider -->>- kubelogin: Token response
|
||||
kubelogin -->>- kubectl: Credential
|
||||
kubectl ->>+ kube-apiserver: Request with the credential
|
||||
kube-apiserver -->>- kubectl: Response
|
||||
kubectl -->>- User: Response
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
### Authentication timeout
|
||||
@@ -123,16 +105,21 @@ See also [net/http#ProxyFromEnvironment](https://golang.org/pkg/net/http/#ProxyF
|
||||
|
||||
### Token cache
|
||||
|
||||
Kubelogin stores the token cache to the OS keyring if available.
|
||||
It depends on [zalando/go-keyring](https://github.com/zalando/go-keyring) for the keyring storage.
|
||||
Kubelogin stores the token cache to the file system by default.
|
||||
|
||||
If you encounter a problem, try `--token-cache-storage` to set the storage.
|
||||
You can store the token cache to the OS keyring for enhanced security.
|
||||
It depends on [zalando/go-keyring](https://github.com/zalando/go-keyring).
|
||||
|
||||
```yaml
|
||||
# Force to use the OS keyring
|
||||
- --token-cache-storage=keyring
|
||||
# Force to use the file system
|
||||
- --token-cache-storage=disk
|
||||
```
|
||||
|
||||
You can delete the token cache by the clean command.
|
||||
|
||||
```console
|
||||
% kubectl oidc-login clean
|
||||
Deleted the token cache at /home/user/.kube/cache/oidc-login
|
||||
Deleted the token cache from the keyring
|
||||
```
|
||||
|
||||
### Home directory expansion
|
||||
|
||||
235
go.mod
235
go.mod
@@ -1,66 +1,247 @@
|
||||
module github.com/int128/kubelogin
|
||||
|
||||
go 1.23.5
|
||||
go 1.24.2
|
||||
|
||||
require (
|
||||
github.com/chromedp/chromedp v0.11.2
|
||||
github.com/coreos/go-oidc/v3 v3.12.0
|
||||
github.com/chromedp/chromedp v0.13.6
|
||||
github.com/coreos/go-oidc/v3 v3.14.1
|
||||
github.com/gofrs/flock v0.12.1
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1
|
||||
github.com/google/go-cmp v0.6.0
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2
|
||||
github.com/google/go-cmp v0.7.0
|
||||
github.com/google/wire v0.6.0
|
||||
github.com/int128/oauth2cli v1.14.1
|
||||
github.com/int128/oauth2cli v1.15.1
|
||||
github.com/int128/oauth2dev v1.0.1
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
|
||||
github.com/spf13/cobra v1.8.1
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/spf13/cobra v1.9.1
|
||||
github.com/spf13/pflag v1.0.6
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/zalando/go-keyring v0.2.6
|
||||
golang.org/x/oauth2 v0.25.0
|
||||
golang.org/x/sync v0.10.0
|
||||
golang.org/x/term v0.28.0
|
||||
golang.org/x/oauth2 v0.29.0
|
||||
golang.org/x/sync v0.13.0
|
||||
golang.org/x/term v0.31.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
k8s.io/apimachinery v0.32.1
|
||||
k8s.io/client-go v0.32.1
|
||||
k8s.io/apimachinery v0.33.0
|
||||
k8s.io/client-go v0.33.0
|
||||
k8s.io/klog/v2 v2.130.1
|
||||
)
|
||||
|
||||
require (
|
||||
4d63.com/gocheckcompilerdirectives v1.3.0 // indirect
|
||||
4d63.com/gochecknoglobals v0.2.2 // indirect
|
||||
al.essio.dev/pkg/shellescape v1.5.1 // indirect
|
||||
github.com/chromedp/cdproto v0.0.0-20241022234722-4d5d5faf59fb // indirect
|
||||
github.com/4meepo/tagalign v1.4.2 // indirect
|
||||
github.com/Abirdcfly/dupword v0.1.3 // indirect
|
||||
github.com/Antonboom/errname v1.0.0 // indirect
|
||||
github.com/Antonboom/nilnil v1.0.1 // indirect
|
||||
github.com/Antonboom/testifylint v1.5.2 // indirect
|
||||
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect
|
||||
github.com/Crocmagnon/fatcontext v0.7.1 // indirect
|
||||
github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect
|
||||
github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.3.0 // indirect
|
||||
github.com/OpenPeeDeeP/depguard/v2 v2.2.1 // indirect
|
||||
github.com/alecthomas/go-check-sumtype v0.3.1 // indirect
|
||||
github.com/alexkohler/nakedret/v2 v2.0.5 // indirect
|
||||
github.com/alexkohler/prealloc v1.0.0 // indirect
|
||||
github.com/alingse/asasalint v0.0.11 // indirect
|
||||
github.com/alingse/nilnesserr v0.1.2 // indirect
|
||||
github.com/ashanbrown/forbidigo v1.6.0 // indirect
|
||||
github.com/ashanbrown/makezero v1.2.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bkielbasa/cyclop v1.2.3 // indirect
|
||||
github.com/blizzy78/varnamelen v0.8.0 // indirect
|
||||
github.com/bombsimon/wsl/v4 v4.5.0 // indirect
|
||||
github.com/breml/bidichk v0.3.2 // indirect
|
||||
github.com/breml/errchkjson v0.4.0 // indirect
|
||||
github.com/butuzov/ireturn v0.3.1 // indirect
|
||||
github.com/butuzov/mirror v1.3.0 // indirect
|
||||
github.com/catenacyber/perfsprint v0.8.2 // indirect
|
||||
github.com/ccojocar/zxcvbn-go v1.0.2 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/charithe/durationcheck v0.0.10 // indirect
|
||||
github.com/chavacava/garif v0.1.0 // indirect
|
||||
github.com/chigopher/pathlib v0.19.1 // indirect
|
||||
github.com/chromedp/cdproto v0.0.0-20250403032234-65de8f5d025b // indirect
|
||||
github.com/chromedp/sysutil v1.1.0 // indirect
|
||||
github.com/ckaznocha/intrange v0.3.0 // indirect
|
||||
github.com/curioswitch/go-reassign v0.3.0 // indirect
|
||||
github.com/daixiang0/gci v0.13.5 // indirect
|
||||
github.com/danieljoos/wincred v1.2.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/denis-tingaikin/go-header v0.5.0 // indirect
|
||||
github.com/ettle/strcase v0.2.0 // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/fatih/structtag v1.2.0 // indirect
|
||||
github.com/firefart/nonamedreturns v1.0.5 // indirect
|
||||
github.com/fsnotify/fsnotify v1.8.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.0.2 // indirect
|
||||
github.com/fzipp/gocyclo v0.6.0 // indirect
|
||||
github.com/ghostiam/protogetter v0.3.9 // indirect
|
||||
github.com/go-critic/go-critic v0.12.0 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.0.5 // indirect
|
||||
github.com/go-json-experiment/json v0.0.0-20250211171154-1ae217ad3535 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-toolsmith/astcast v1.1.0 // indirect
|
||||
github.com/go-toolsmith/astcopy v1.1.0 // indirect
|
||||
github.com/go-toolsmith/astequal v1.2.0 // indirect
|
||||
github.com/go-toolsmith/astfmt v1.1.0 // indirect
|
||||
github.com/go-toolsmith/astp v1.1.0 // indirect
|
||||
github.com/go-toolsmith/strparse v1.1.0 // indirect
|
||||
github.com/go-toolsmith/typep v1.1.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
|
||||
github.com/go-xmlfmt/xmlfmt v1.1.3 // indirect
|
||||
github.com/gobwas/glob v0.2.3 // indirect
|
||||
github.com/gobwas/httphead v0.1.0 // indirect
|
||||
github.com/gobwas/pool v0.2.1 // indirect
|
||||
github.com/gobwas/ws v1.4.0 // indirect
|
||||
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 // indirect
|
||||
github.com/golangci/go-printf-func-name v0.1.0 // indirect
|
||||
github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d // indirect
|
||||
github.com/golangci/golangci-lint v1.64.8 // indirect
|
||||
github.com/golangci/misspell v0.6.0 // indirect
|
||||
github.com/golangci/plugin-module-register v0.1.1 // indirect
|
||||
github.com/golangci/revgrep v0.8.0 // indirect
|
||||
github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed // indirect
|
||||
github.com/google/subcommands v1.2.0 // indirect
|
||||
github.com/gordonklaus/ineffassign v0.1.0 // indirect
|
||||
github.com/gostaticanalysis/analysisutil v0.7.1 // indirect
|
||||
github.com/gostaticanalysis/comment v1.5.0 // indirect
|
||||
github.com/gostaticanalysis/forcetypeassert v0.2.0 // indirect
|
||||
github.com/gostaticanalysis/nilerr v0.1.1 // indirect
|
||||
github.com/hashicorp/go-immutable-radix/v2 v2.1.0 // indirect
|
||||
github.com/hashicorp/go-version v1.7.0 // indirect
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||
github.com/hexops/gotextdiff v1.0.3 // indirect
|
||||
github.com/huandu/xstrings v1.4.0 // indirect
|
||||
github.com/iancoleman/strcase v0.3.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/int128/listener v1.1.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/int128/listener v1.2.0 // indirect
|
||||
github.com/jgautheron/goconst v1.7.1 // indirect
|
||||
github.com/jingyugao/rowserrcheck v1.1.1 // indirect
|
||||
github.com/jinzhu/copier v0.4.0 // indirect
|
||||
github.com/jjti/go-spancheck v0.6.4 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/julz/importas v0.2.0 // indirect
|
||||
github.com/karamaru-alpha/copyloopvar v1.2.1 // indirect
|
||||
github.com/kisielk/errcheck v1.9.0 // indirect
|
||||
github.com/kkHAIKE/contextcheck v1.1.6 // indirect
|
||||
github.com/kulti/thelper v0.6.3 // indirect
|
||||
github.com/kunwardeep/paralleltest v1.0.10 // indirect
|
||||
github.com/lasiar/canonicalheader v1.1.2 // indirect
|
||||
github.com/ldez/exptostd v0.4.2 // indirect
|
||||
github.com/ldez/gomoddirectives v0.6.1 // indirect
|
||||
github.com/ldez/grignotin v0.9.0 // indirect
|
||||
github.com/ldez/tagliatelle v0.7.1 // indirect
|
||||
github.com/ldez/usetesting v0.4.2 // indirect
|
||||
github.com/leonklingele/grouper v1.1.2 // indirect
|
||||
github.com/macabu/inamedparam v0.1.3 // indirect
|
||||
github.com/mailru/easyjson v0.9.0 // indirect
|
||||
github.com/maratori/testableexamples v1.0.0 // indirect
|
||||
github.com/maratori/testpackage v1.1.1 // indirect
|
||||
github.com/matoous/godox v1.1.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||
github.com/mgechev/revive v1.7.0 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/moricho/tparallel v0.3.2 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/nakabonne/nestif v0.3.1 // indirect
|
||||
github.com/nishanths/exhaustive v0.12.0 // indirect
|
||||
github.com/nishanths/predeclared v0.2.2 // indirect
|
||||
github.com/nunnatsa/ginkgolinter v0.19.1 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
||||
github.com/polyfloyd/go-errorlint v1.7.1 // indirect
|
||||
github.com/prometheus/client_golang v1.12.1 // indirect
|
||||
github.com/prometheus/client_model v0.2.0 // indirect
|
||||
github.com/prometheus/common v0.32.1 // indirect
|
||||
github.com/prometheus/procfs v0.7.3 // indirect
|
||||
github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1 // indirect
|
||||
github.com/quasilyte/go-ruleguard/dsl v0.3.22 // indirect
|
||||
github.com/quasilyte/gogrep v0.5.0 // indirect
|
||||
github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect
|
||||
github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect
|
||||
github.com/raeperd/recvcheck v0.2.0 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||
github.com/rs/zerolog v1.33.0 // indirect
|
||||
github.com/ryancurrah/gomodguard v1.3.5 // indirect
|
||||
github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect
|
||||
github.com/sagikazarmark/locafero v0.7.0 // indirect
|
||||
github.com/sanposhiho/wastedassign/v2 v2.1.0 // indirect
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 // indirect
|
||||
github.com/sashamelentyev/interfacebloat v1.1.0 // indirect
|
||||
github.com/sashamelentyev/usestdlibvars v1.28.0 // indirect
|
||||
github.com/securego/gosec/v2 v2.22.2 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/sivchari/containedctx v1.0.3 // indirect
|
||||
github.com/sivchari/tenv v1.12.1 // indirect
|
||||
github.com/sonatard/noctx v0.1.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/sourcegraph/go-diff v0.7.0 // indirect
|
||||
github.com/spf13/afero v1.12.0 // indirect
|
||||
github.com/spf13/cast v1.7.1 // indirect
|
||||
github.com/spf13/viper v1.20.0 // indirect
|
||||
github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect
|
||||
github.com/stbenjam/no-sprintf-host-port v0.2.0 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/tdakkota/asciicheck v0.4.1 // indirect
|
||||
github.com/tetafro/godot v1.5.0 // indirect
|
||||
github.com/timakin/bodyclose v0.0.0-20241017074812-ed6a65f985e3 // indirect
|
||||
github.com/timonwong/loggercheck v0.10.1 // indirect
|
||||
github.com/tomarrell/wrapcheck/v2 v2.10.0 // indirect
|
||||
github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect
|
||||
github.com/ultraware/funlen v0.2.0 // indirect
|
||||
github.com/ultraware/whitespace v0.2.0 // indirect
|
||||
github.com/uudashr/gocognit v1.2.0 // indirect
|
||||
github.com/uudashr/iface v1.3.1 // indirect
|
||||
github.com/vektra/mockery/v2 v2.53.3 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
golang.org/x/crypto v0.28.0 // indirect
|
||||
golang.org/x/net v0.30.0 // indirect
|
||||
golang.org/x/sys v0.29.0 // indirect
|
||||
golang.org/x/text v0.19.0 // indirect
|
||||
golang.org/x/time v0.7.0 // indirect
|
||||
github.com/xen0n/gosmopolitan v1.2.2 // indirect
|
||||
github.com/yagipy/maintidx v1.0.0 // indirect
|
||||
github.com/yeya24/promlinter v0.3.0 // indirect
|
||||
github.com/ykadowak/zerologlint v0.1.5 // indirect
|
||||
gitlab.com/bosi/decorder v0.4.2 // indirect
|
||||
go-simpler.org/musttag v0.13.0 // indirect
|
||||
go-simpler.org/sloglint v0.9.0 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.uber.org/automaxprocs v1.6.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.24.0 // indirect
|
||||
golang.org/x/crypto v0.36.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac // indirect
|
||||
golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac // indirect
|
||||
golang.org/x/mod v0.24.0 // indirect
|
||||
golang.org/x/net v0.38.0 // indirect
|
||||
golang.org/x/sys v0.32.0 // indirect
|
||||
golang.org/x/text v0.23.0 // indirect
|
||||
golang.org/x/time v0.10.0 // indirect
|
||||
golang.org/x/tools v0.31.0 // indirect
|
||||
google.golang.org/protobuf v1.36.5 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
honnef.co/go/tools v0.6.1 // indirect
|
||||
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
|
||||
mvdan.cc/gofumpt v0.7.0 // indirect
|
||||
mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f // indirect
|
||||
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect
|
||||
sigs.k8s.io/randfill v1.0.0 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect
|
||||
sigs.k8s.io/yaml v1.4.0 // indirect
|
||||
)
|
||||
|
||||
tool (
|
||||
github.com/golangci/golangci-lint/cmd/golangci-lint
|
||||
github.com/google/wire/cmd/wire
|
||||
github.com/vektra/mockery/v2
|
||||
)
|
||||
|
||||
@@ -20,7 +20,6 @@ func TestClean(t *testing.T) {
|
||||
"kubelogin",
|
||||
"clean",
|
||||
"--token-cache-dir", tokenCacheDir,
|
||||
"--token-cache-storage", "disk",
|
||||
}, "HEAD")
|
||||
if exitCode != 0 {
|
||||
t.Errorf("exit status wants 0 but %d", exitCode)
|
||||
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
"github.com/int128/kubelogin/pkg/testing/clock"
|
||||
"github.com/int128/kubelogin/pkg/testing/logger"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
clientauthenticationv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1"
|
||||
clientauthenticationv1 "k8s.io/client-go/pkg/apis/clientauthentication/v1"
|
||||
)
|
||||
|
||||
// Run the integration tests of the credential plugin use-case.
|
||||
@@ -50,7 +50,6 @@ func TestCredentialPlugin(t *testing.T) {
|
||||
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Run("AuthCode", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx, cancel := context.WithTimeout(context.TODO(), timeout)
|
||||
defer cancel()
|
||||
svc := oidcserver.New(t, tc.keyPair, testconfig.Config{
|
||||
@@ -77,7 +76,6 @@ func TestCredentialPlugin(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("ROPC", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx, cancel := context.WithTimeout(context.TODO(), timeout)
|
||||
defer cancel()
|
||||
svc := oidcserver.New(t, tc.keyPair, testconfig.Config{
|
||||
@@ -108,7 +106,6 @@ func TestCredentialPlugin(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("TokenCacheLifecycle", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx, cancel := context.WithTimeout(context.TODO(), timeout)
|
||||
defer cancel()
|
||||
svc := oidcserver.New(t, tc.keyPair, testconfig.Config{})
|
||||
@@ -203,7 +200,6 @@ func TestCredentialPlugin(t *testing.T) {
|
||||
|
||||
t.Run("PKCE", func(t *testing.T) {
|
||||
t.Run("Not supported by provider", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx, cancel := context.WithTimeout(context.TODO(), timeout)
|
||||
defer cancel()
|
||||
svc := oidcserver.New(t, keypair.None, testconfig.Config{
|
||||
@@ -229,7 +225,6 @@ func TestCredentialPlugin(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("Enforce", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx, cancel := context.WithTimeout(context.TODO(), timeout)
|
||||
defer cancel()
|
||||
svc := oidcserver.New(t, keypair.None, testconfig.Config{
|
||||
@@ -257,7 +252,6 @@ func TestCredentialPlugin(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("TLSData", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx, cancel := context.WithTimeout(context.TODO(), timeout)
|
||||
defer cancel()
|
||||
svc := oidcserver.New(t, keypair.Server, testconfig.Config{
|
||||
@@ -284,7 +278,6 @@ func TestCredentialPlugin(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("ExtraScopes", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx, cancel := context.WithTimeout(context.TODO(), timeout)
|
||||
defer cancel()
|
||||
svc := oidcserver.New(t, keypair.None, testconfig.Config{
|
||||
@@ -314,7 +307,6 @@ func TestCredentialPlugin(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("OpenURLAfterAuthentication", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx, cancel := context.WithTimeout(context.TODO(), timeout)
|
||||
defer cancel()
|
||||
svc := oidcserver.New(t, keypair.None, testconfig.Config{
|
||||
@@ -341,7 +333,6 @@ func TestCredentialPlugin(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("RedirectURLHostname", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx, cancel := context.WithTimeout(context.TODO(), timeout)
|
||||
defer cancel()
|
||||
svc := oidcserver.New(t, keypair.None, testconfig.Config{
|
||||
@@ -368,7 +359,6 @@ func TestCredentialPlugin(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("RedirectURLHTTPS", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx, cancel := context.WithTimeout(context.TODO(), timeout)
|
||||
defer cancel()
|
||||
svc := oidcserver.New(t, keypair.None, testconfig.Config{
|
||||
@@ -401,7 +391,6 @@ func TestCredentialPlugin(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("ExtraParams", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx, cancel := context.WithTimeout(context.TODO(), timeout)
|
||||
defer cancel()
|
||||
svc := oidcserver.New(t, keypair.None, testconfig.Config{
|
||||
@@ -446,11 +435,14 @@ type getTokenConfig struct {
|
||||
|
||||
func runGetToken(t *testing.T, ctx context.Context, cfg getTokenConfig) {
|
||||
cmd := di.NewCmdForHeadless(clock.Fake(cfg.now), os.Stdin, cfg.stdout, logger.New(t), cfg.httpDriver)
|
||||
t.Setenv(
|
||||
"KUBERNETES_EXEC_INFO",
|
||||
`{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1","spec":{"interactive":true}}`,
|
||||
)
|
||||
exitCode := cmd.Run(ctx, append([]string{
|
||||
"kubelogin",
|
||||
"get-token",
|
||||
"--token-cache-dir", cfg.tokenCacheDir,
|
||||
"--token-cache-storage", "disk",
|
||||
"--oidc-issuer-url", cfg.issuerURL,
|
||||
"--oidc-client-id", "kubernetes",
|
||||
"--listen-address", "127.0.0.1:0",
|
||||
@@ -461,22 +453,22 @@ func runGetToken(t *testing.T, ctx context.Context, cfg getTokenConfig) {
|
||||
}
|
||||
|
||||
func assertCredentialPluginStdout(t *testing.T, stdout io.Reader, token string, expiry time.Time) {
|
||||
var got clientauthenticationv1beta1.ExecCredential
|
||||
var got clientauthenticationv1.ExecCredential
|
||||
if err := json.NewDecoder(stdout).Decode(&got); err != nil {
|
||||
t.Errorf("could not decode json of the credential plugin: %s", err)
|
||||
return
|
||||
}
|
||||
want := clientauthenticationv1beta1.ExecCredential{
|
||||
want := clientauthenticationv1.ExecCredential{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "client.authentication.k8s.io/v1beta1",
|
||||
APIVersion: "client.authentication.k8s.io/v1",
|
||||
Kind: "ExecCredential",
|
||||
},
|
||||
Status: &clientauthenticationv1beta1.ExecCredentialStatus{
|
||||
Status: &clientauthenticationv1.ExecCredentialStatus{
|
||||
Token: token,
|
||||
ExpirationTimestamp: &metav1.Time{Time: expiry},
|
||||
},
|
||||
}
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
t.Errorf("kubeconfig mismatch (-want +got):\n%s", diff)
|
||||
t.Errorf("stdout mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.51.0. DO NOT EDIT.
|
||||
// Code generated by mockery v2.53.3. DO NOT EDIT.
|
||||
|
||||
package service_mock
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.51.0. DO NOT EDIT.
|
||||
// Code generated by mockery v2.53.3. DO NOT EDIT.
|
||||
|
||||
package service_mock
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.51.0. DO NOT EDIT.
|
||||
// Code generated by mockery v2.53.3. DO NOT EDIT.
|
||||
|
||||
package cmd_mock
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.51.0. DO NOT EDIT.
|
||||
// Code generated by mockery v2.53.3. DO NOT EDIT.
|
||||
|
||||
package reader_mock
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.51.0. DO NOT EDIT.
|
||||
// Code generated by mockery v2.53.3. DO NOT EDIT.
|
||||
|
||||
package writer_mock
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.51.0. DO NOT EDIT.
|
||||
// Code generated by mockery v2.53.3. DO NOT EDIT.
|
||||
|
||||
package browser_mock
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.51.0. DO NOT EDIT.
|
||||
// Code generated by mockery v2.53.3. DO NOT EDIT.
|
||||
|
||||
package clock_mock
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.51.0. DO NOT EDIT.
|
||||
// Code generated by mockery v2.53.3. DO NOT EDIT.
|
||||
|
||||
package logger_mock
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.51.0. DO NOT EDIT.
|
||||
// Code generated by mockery v2.53.3. DO NOT EDIT.
|
||||
|
||||
package logger_mock
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.51.0. DO NOT EDIT.
|
||||
// Code generated by mockery v2.53.3. DO NOT EDIT.
|
||||
|
||||
package logger_mock
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.51.0. DO NOT EDIT.
|
||||
// Code generated by mockery v2.53.3. DO NOT EDIT.
|
||||
|
||||
package reader_mock
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.51.0. DO NOT EDIT.
|
||||
// Code generated by mockery v2.53.3. DO NOT EDIT.
|
||||
|
||||
package stdio_mock
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.51.0. DO NOT EDIT.
|
||||
// Code generated by mockery v2.53.3. DO NOT EDIT.
|
||||
|
||||
package stdio_mock
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.51.0. DO NOT EDIT.
|
||||
// Code generated by mockery v2.53.3. DO NOT EDIT.
|
||||
|
||||
package jwt_mock
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.51.0. DO NOT EDIT.
|
||||
// Code generated by mockery v2.53.3. DO NOT EDIT.
|
||||
|
||||
package loader_mock
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.51.0. DO NOT EDIT.
|
||||
// Code generated by mockery v2.53.3. DO NOT EDIT.
|
||||
|
||||
package writer_mock
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.51.0. DO NOT EDIT.
|
||||
// Code generated by mockery v2.53.3. DO NOT EDIT.
|
||||
|
||||
package client_mock
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.51.0. DO NOT EDIT.
|
||||
// Code generated by mockery v2.53.3. DO NOT EDIT.
|
||||
|
||||
package client_mock
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.51.0. DO NOT EDIT.
|
||||
// Code generated by mockery v2.53.3. DO NOT EDIT.
|
||||
|
||||
package logger_mock
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.51.0. DO NOT EDIT.
|
||||
// Code generated by mockery v2.53.3. DO NOT EDIT.
|
||||
|
||||
package loader_mock
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.51.0. DO NOT EDIT.
|
||||
// Code generated by mockery v2.53.3. DO NOT EDIT.
|
||||
|
||||
package repository_mock
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.51.0. DO NOT EDIT.
|
||||
// Code generated by mockery v2.53.3. DO NOT EDIT.
|
||||
|
||||
package authentication_mock
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.51.0. DO NOT EDIT.
|
||||
// Code generated by mockery v2.53.3. DO NOT EDIT.
|
||||
|
||||
package clean_mock
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.51.0. DO NOT EDIT.
|
||||
// Code generated by mockery v2.53.3. DO NOT EDIT.
|
||||
|
||||
package credentialplugin_mock
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.51.0. DO NOT EDIT.
|
||||
// Code generated by mockery v2.53.3. DO NOT EDIT.
|
||||
|
||||
package setup_mock
|
||||
|
||||
@@ -22,48 +22,16 @@ func (_m *MockInterface) EXPECT() *MockInterface_Expecter {
|
||||
return &MockInterface_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// DoStage1 provides a mock function with no fields
|
||||
func (_m *MockInterface) DoStage1() {
|
||||
_m.Called()
|
||||
}
|
||||
|
||||
// MockInterface_DoStage1_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DoStage1'
|
||||
type MockInterface_DoStage1_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// DoStage1 is a helper method to define mock.On call
|
||||
func (_e *MockInterface_Expecter) DoStage1() *MockInterface_DoStage1_Call {
|
||||
return &MockInterface_DoStage1_Call{Call: _e.mock.On("DoStage1")}
|
||||
}
|
||||
|
||||
func (_c *MockInterface_DoStage1_Call) Run(run func()) *MockInterface_DoStage1_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockInterface_DoStage1_Call) Return() *MockInterface_DoStage1_Call {
|
||||
_c.Call.Return()
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockInterface_DoStage1_Call) RunAndReturn(run func()) *MockInterface_DoStage1_Call {
|
||||
_c.Run(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// DoStage2 provides a mock function with given fields: ctx, in
|
||||
func (_m *MockInterface) DoStage2(ctx context.Context, in setup.Stage2Input) error {
|
||||
// Do provides a mock function with given fields: ctx, in
|
||||
func (_m *MockInterface) Do(ctx context.Context, in setup.Input) error {
|
||||
ret := _m.Called(ctx, in)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for DoStage2")
|
||||
panic("no return value specified for Do")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, setup.Stage2Input) error); ok {
|
||||
if rf, ok := ret.Get(0).(func(context.Context, setup.Input) error); ok {
|
||||
r0 = rf(ctx, in)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
@@ -72,31 +40,31 @@ func (_m *MockInterface) DoStage2(ctx context.Context, in setup.Stage2Input) err
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockInterface_DoStage2_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DoStage2'
|
||||
type MockInterface_DoStage2_Call struct {
|
||||
// MockInterface_Do_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Do'
|
||||
type MockInterface_Do_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// DoStage2 is a helper method to define mock.On call
|
||||
// Do is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - in setup.Stage2Input
|
||||
func (_e *MockInterface_Expecter) DoStage2(ctx interface{}, in interface{}) *MockInterface_DoStage2_Call {
|
||||
return &MockInterface_DoStage2_Call{Call: _e.mock.On("DoStage2", ctx, in)}
|
||||
// - in setup.Input
|
||||
func (_e *MockInterface_Expecter) Do(ctx interface{}, in interface{}) *MockInterface_Do_Call {
|
||||
return &MockInterface_Do_Call{Call: _e.mock.On("Do", ctx, in)}
|
||||
}
|
||||
|
||||
func (_c *MockInterface_DoStage2_Call) Run(run func(ctx context.Context, in setup.Stage2Input)) *MockInterface_DoStage2_Call {
|
||||
func (_c *MockInterface_Do_Call) Run(run func(ctx context.Context, in setup.Input)) *MockInterface_Do_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(setup.Stage2Input))
|
||||
run(args[0].(context.Context), args[1].(setup.Input))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockInterface_DoStage2_Call) Return(_a0 error) *MockInterface_DoStage2_Call {
|
||||
func (_c *MockInterface_Do_Call) Return(_a0 error) *MockInterface_Do_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockInterface_DoStage2_Call) RunAndReturn(run func(context.Context, setup.Stage2Input) error) *MockInterface_DoStage2_Call {
|
||||
func (_c *MockInterface_Do_Call) RunAndReturn(run func(context.Context, setup.Input) error) *MockInterface_Do_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.51.0. DO NOT EDIT.
|
||||
// Code generated by mockery v2.53.3. DO NOT EDIT.
|
||||
|
||||
package standalone_mock
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.51.0. DO NOT EDIT.
|
||||
// Code generated by mockery v2.53.3. DO NOT EDIT.
|
||||
|
||||
package io_mock
|
||||
|
||||
|
||||
@@ -9,15 +9,11 @@ import (
|
||||
)
|
||||
|
||||
type cleanOptions struct {
|
||||
tokenCacheOptions tokenCacheOptions
|
||||
TokenCacheDir string
|
||||
}
|
||||
|
||||
func (o *cleanOptions) addFlags(f *pflag.FlagSet) {
|
||||
o.tokenCacheOptions.addFlags(f)
|
||||
}
|
||||
|
||||
func (o *cleanOptions) expandHomedir() {
|
||||
o.tokenCacheOptions.expandHomedir()
|
||||
f.StringVar(&o.TokenCacheDir, "token-cache-dir", getDefaultTokenCacheDir(), "Path to a directory of the token cache")
|
||||
}
|
||||
|
||||
type Clean struct {
|
||||
@@ -31,18 +27,13 @@ func (cmd *Clean) New() *cobra.Command {
|
||||
Short: "Delete the token cache",
|
||||
Long: `Delete the token cache.
|
||||
|
||||
This deletes both the OS keyring and the directory by default.
|
||||
If you encounter an error of keyring, try --token-cache-storage=disk.
|
||||
This deletes the token cache directory from both the file system and the keyring.
|
||||
`,
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(c *cobra.Command, _ []string) error {
|
||||
o.expandHomedir()
|
||||
tokenCacheConfig, err := o.tokenCacheOptions.tokenCacheConfig()
|
||||
if err != nil {
|
||||
return fmt.Errorf("clean: %w", err)
|
||||
}
|
||||
o.TokenCacheDir = expandHomedir(o.TokenCacheDir)
|
||||
in := clean.Input{
|
||||
TokenCacheConfig: tokenCacheConfig,
|
||||
TokenCacheDir: o.TokenCacheDir,
|
||||
}
|
||||
if err := cmd.Clean.Do(c.Context(), in); err != nil {
|
||||
return fmt.Errorf("clean: %w", err)
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/int128/kubelogin/mocks/github.com/int128/kubelogin/pkg/usecases/credentialplugin_mock"
|
||||
"github.com/int128/kubelogin/mocks/github.com/int128/kubelogin/pkg/usecases/setup_mock"
|
||||
"github.com/int128/kubelogin/mocks/github.com/int128/kubelogin/pkg/usecases/standalone_mock"
|
||||
"github.com/int128/kubelogin/pkg/oidc"
|
||||
"github.com/int128/kubelogin/pkg/testing/logger"
|
||||
@@ -16,6 +17,7 @@ import (
|
||||
"github.com/int128/kubelogin/pkg/usecases/authentication"
|
||||
"github.com/int128/kubelogin/pkg/usecases/authentication/authcode"
|
||||
"github.com/int128/kubelogin/pkg/usecases/credentialplugin"
|
||||
"github.com/int128/kubelogin/pkg/usecases/setup"
|
||||
"github.com/int128/kubelogin/pkg/usecases/standalone"
|
||||
)
|
||||
|
||||
@@ -23,6 +25,14 @@ func TestCmd_Run(t *testing.T) {
|
||||
const executable = "kubelogin"
|
||||
const version = "HEAD"
|
||||
|
||||
defaultGrantOptionSet := authentication.GrantOptionSet{
|
||||
AuthCodeBrowserOption: &authcode.BrowserOption{
|
||||
BindAddress: defaultListenAddress,
|
||||
AuthenticationTimeout: defaultAuthenticationTimeoutSec * time.Second,
|
||||
RedirectURLHostname: "localhost",
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("root", func(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
args []string
|
||||
@@ -31,13 +41,7 @@ func TestCmd_Run(t *testing.T) {
|
||||
"Defaults": {
|
||||
args: []string{executable},
|
||||
in: standalone.Input{
|
||||
GrantOptionSet: authentication.GrantOptionSet{
|
||||
AuthCodeBrowserOption: &authcode.BrowserOption{
|
||||
BindAddress: defaultListenAddress,
|
||||
AuthenticationTimeout: defaultAuthenticationTimeoutSec * time.Second,
|
||||
RedirectURLHostname: "localhost",
|
||||
},
|
||||
},
|
||||
GrantOptionSet: defaultGrantOptionSet,
|
||||
},
|
||||
},
|
||||
"FullOptions": {
|
||||
@@ -51,13 +55,7 @@ func TestCmd_Run(t *testing.T) {
|
||||
KubeconfigFilename: "/path/to/kubeconfig",
|
||||
KubeconfigContext: "hello.k8s.local",
|
||||
KubeconfigUser: "google",
|
||||
GrantOptionSet: authentication.GrantOptionSet{
|
||||
AuthCodeBrowserOption: &authcode.BrowserOption{
|
||||
BindAddress: defaultListenAddress,
|
||||
AuthenticationTimeout: defaultAuthenticationTimeoutSec * time.Second,
|
||||
RedirectURLHostname: "localhost",
|
||||
},
|
||||
},
|
||||
GrantOptionSet: defaultGrantOptionSet,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -120,15 +118,8 @@ func TestCmd_Run(t *testing.T) {
|
||||
},
|
||||
TokenCacheConfig: tokencache.Config{
|
||||
Directory: filepath.Join(userHomeDir, ".kube/cache/oidc-login"),
|
||||
Storage: tokencache.StorageAuto,
|
||||
},
|
||||
GrantOptionSet: authentication.GrantOptionSet{
|
||||
AuthCodeBrowserOption: &authcode.BrowserOption{
|
||||
BindAddress: defaultListenAddress,
|
||||
AuthenticationTimeout: defaultAuthenticationTimeoutSec * time.Second,
|
||||
RedirectURLHostname: "localhost",
|
||||
},
|
||||
},
|
||||
GrantOptionSet: defaultGrantOptionSet,
|
||||
},
|
||||
},
|
||||
"FullOptions": {
|
||||
@@ -139,7 +130,7 @@ func TestCmd_Run(t *testing.T) {
|
||||
"--oidc-client-secret", "YOUR_CLIENT_SECRET",
|
||||
"--oidc-extra-scope", "email",
|
||||
"--oidc-extra-scope", "profile",
|
||||
"--token-cache-storage", "disk",
|
||||
"--token-cache-storage", "keyring",
|
||||
"-v1",
|
||||
},
|
||||
in: credentialplugin.Input{
|
||||
@@ -151,15 +142,9 @@ func TestCmd_Run(t *testing.T) {
|
||||
},
|
||||
TokenCacheConfig: tokencache.Config{
|
||||
Directory: filepath.Join(userHomeDir, ".kube/cache/oidc-login"),
|
||||
Storage: tokencache.StorageDisk,
|
||||
},
|
||||
GrantOptionSet: authentication.GrantOptionSet{
|
||||
AuthCodeBrowserOption: &authcode.BrowserOption{
|
||||
BindAddress: defaultListenAddress,
|
||||
AuthenticationTimeout: defaultAuthenticationTimeoutSec * time.Second,
|
||||
RedirectURLHostname: "localhost",
|
||||
},
|
||||
Storage: tokencache.StorageKeyring,
|
||||
},
|
||||
GrantOptionSet: defaultGrantOptionSet,
|
||||
},
|
||||
},
|
||||
"AccessToken": {
|
||||
@@ -177,15 +162,8 @@ func TestCmd_Run(t *testing.T) {
|
||||
},
|
||||
TokenCacheConfig: tokencache.Config{
|
||||
Directory: filepath.Join(userHomeDir, ".kube/cache/oidc-login"),
|
||||
Storage: tokencache.StorageAuto,
|
||||
},
|
||||
GrantOptionSet: authentication.GrantOptionSet{
|
||||
AuthCodeBrowserOption: &authcode.BrowserOption{
|
||||
BindAddress: defaultListenAddress,
|
||||
AuthenticationTimeout: defaultAuthenticationTimeoutSec * time.Second,
|
||||
RedirectURLHostname: "localhost",
|
||||
},
|
||||
},
|
||||
GrantOptionSet: defaultGrantOptionSet,
|
||||
},
|
||||
},
|
||||
"HomedirExpansion": {
|
||||
@@ -205,7 +183,6 @@ func TestCmd_Run(t *testing.T) {
|
||||
},
|
||||
TokenCacheConfig: tokencache.Config{
|
||||
Directory: filepath.Join(userHomeDir, ".kube/oidc-cache"),
|
||||
Storage: tokencache.StorageAuto,
|
||||
},
|
||||
GrantOptionSet: authentication.GrantOptionSet{
|
||||
AuthCodeBrowserOption: &authcode.BrowserOption{
|
||||
@@ -282,4 +259,54 @@ func TestCmd_Run(t *testing.T) {
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("setup", func(t *testing.T) {
|
||||
t.Run("NoOption", func(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
cmd := Cmd{
|
||||
Logger: logger.New(t),
|
||||
Root: &Root{
|
||||
Logger: logger.New(t),
|
||||
},
|
||||
}
|
||||
exitCode := cmd.Run(ctx, []string{executable, "setup"}, version)
|
||||
if exitCode != 0 {
|
||||
t.Errorf("exitCode wants 0 but %d", exitCode)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("WithOptions", func(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
setupMock := setup_mock.NewMockInterface(t)
|
||||
setupMock.EXPECT().Do(ctx, setup.Input{
|
||||
IssuerURL: "https://issuer.example.com",
|
||||
ClientID: "YOUR_CLIENT",
|
||||
ExtraScopes: []string{"email", "profile"},
|
||||
GrantOptionSet: defaultGrantOptionSet,
|
||||
ChangedFlags: []string{
|
||||
"--oidc-issuer-url=https://issuer.example.com",
|
||||
"--oidc-client-id=YOUR_CLIENT",
|
||||
"--oidc-extra-scope=email",
|
||||
"--oidc-extra-scope=profile",
|
||||
},
|
||||
}).Return(nil)
|
||||
cmd := Cmd{
|
||||
Logger: logger.New(t),
|
||||
Root: &Root{
|
||||
Logger: logger.New(t),
|
||||
},
|
||||
Setup: &Setup{
|
||||
Setup: setupMock,
|
||||
},
|
||||
}
|
||||
exitCode := cmd.Run(ctx, []string{executable, "setup",
|
||||
"--oidc-issuer-url", "https://issuer.example.com",
|
||||
"--oidc-client-id", "YOUR_CLIENT",
|
||||
"--oidc-extra-scope", "email,profile",
|
||||
}, version)
|
||||
if exitCode != 0 {
|
||||
t.Errorf("exitCode wants 0 but %d", exitCode)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package cmd
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
_ "embed"
|
||||
|
||||
"github.com/int128/kubelogin/pkg/usecases/setup"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
@@ -35,13 +37,31 @@ type Setup struct {
|
||||
Setup setup.Interface
|
||||
}
|
||||
|
||||
//go:embed setup.md
|
||||
var setupLongDescription string
|
||||
|
||||
func (cmd *Setup) New() *cobra.Command {
|
||||
var o setupOptions
|
||||
c := &cobra.Command{
|
||||
Use: "setup",
|
||||
Short: "Show the setup instruction",
|
||||
Long: setupLongDescription,
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(c *cobra.Command, _ []string) error {
|
||||
var changedFlags []string
|
||||
c.Flags().VisitAll(func(f *pflag.Flag) {
|
||||
if !f.Changed {
|
||||
return
|
||||
}
|
||||
if sliceValue, ok := f.Value.(pflag.SliceValue); ok {
|
||||
for _, v := range sliceValue.GetSlice() {
|
||||
changedFlags = append(changedFlags, fmt.Sprintf("--%s=%s", f.Name, v))
|
||||
}
|
||||
return
|
||||
}
|
||||
changedFlags = append(changedFlags, fmt.Sprintf("--%s=%s", f.Name, f.Value))
|
||||
})
|
||||
|
||||
grantOptionSet, err := o.authenticationOptions.grantOptionSet()
|
||||
if err != nil {
|
||||
return fmt.Errorf("setup: %w", err)
|
||||
@@ -50,7 +70,7 @@ func (cmd *Setup) New() *cobra.Command {
|
||||
if err != nil {
|
||||
return fmt.Errorf("setup: %w", err)
|
||||
}
|
||||
in := setup.Stage2Input{
|
||||
in := setup.Input{
|
||||
IssuerURL: o.IssuerURL,
|
||||
ClientID: o.ClientID,
|
||||
ClientSecret: o.ClientSecret,
|
||||
@@ -59,18 +79,12 @@ func (cmd *Setup) New() *cobra.Command {
|
||||
PKCEMethod: pkceMethod,
|
||||
GrantOptionSet: grantOptionSet,
|
||||
TLSClientConfig: o.tlsOptions.tlsClientConfig(),
|
||||
}
|
||||
if c.Flags().Lookup("listen-address").Changed {
|
||||
in.ListenAddressArgs = o.authenticationOptions.ListenAddress
|
||||
}
|
||||
if c.Flags().Lookup("oidc-pkce-method").Changed {
|
||||
in.PKCEMethodArg = o.pkceOptions.PKCEMethod
|
||||
ChangedFlags: changedFlags,
|
||||
}
|
||||
if in.IssuerURL == "" || in.ClientID == "" {
|
||||
cmd.Setup.DoStage1()
|
||||
return nil
|
||||
return c.Help()
|
||||
}
|
||||
if err := cmd.Setup.DoStage2(c.Context(), in); err != nil {
|
||||
if err := cmd.Setup.Do(c.Context(), in); err != nil {
|
||||
return fmt.Errorf("setup: %w", err)
|
||||
}
|
||||
return nil
|
||||
|
||||
12
pkg/cmd/setup.md
Normal file
12
pkg/cmd/setup.md
Normal file
@@ -0,0 +1,12 @@
|
||||
This setup shows the instruction of Kubernetes OpenID Connect authentication.
|
||||
|
||||
You need to set up the OpenID Connect Provider.
|
||||
Run the following command to authenticate with the OpenID Connect Provider:
|
||||
|
||||
```
|
||||
kubectl oidc-login setup \
|
||||
--oidc-issuer-url=ISSUER_URL \
|
||||
--oidc-client-id=YOUR_CLIENT_ID
|
||||
```
|
||||
|
||||
See https://github.com/int128/kubelogin for the details.
|
||||
@@ -18,7 +18,7 @@ func getDefaultTokenCacheDir() string {
|
||||
return filepath.Join("~", ".kube", "cache", "oidc-login")
|
||||
}
|
||||
|
||||
var allTokenCacheStorage = strings.Join([]string{"auto", "keyring", "disk"}, "|")
|
||||
var allTokenCacheStorage = strings.Join([]string{"disk", "keyring"}, "|")
|
||||
|
||||
type tokenCacheOptions struct {
|
||||
TokenCacheDir string
|
||||
@@ -27,7 +27,7 @@ type tokenCacheOptions struct {
|
||||
|
||||
func (o *tokenCacheOptions) addFlags(f *pflag.FlagSet) {
|
||||
f.StringVar(&o.TokenCacheDir, "token-cache-dir", getDefaultTokenCacheDir(), "Path to a directory of the token cache")
|
||||
f.StringVar(&o.TokenCacheStorage, "token-cache-storage", "auto", fmt.Sprintf("Storage for the token cache. One of (%s)", allTokenCacheStorage))
|
||||
f.StringVar(&o.TokenCacheStorage, "token-cache-storage", "disk", fmt.Sprintf("Storage for the token cache. One of (%s)", allTokenCacheStorage))
|
||||
}
|
||||
|
||||
func (o *tokenCacheOptions) expandHomedir() {
|
||||
@@ -39,12 +39,10 @@ func (o *tokenCacheOptions) tokenCacheConfig() (tokencache.Config, error) {
|
||||
Directory: o.TokenCacheDir,
|
||||
}
|
||||
switch o.TokenCacheStorage {
|
||||
case "auto":
|
||||
config.Storage = tokencache.StorageAuto
|
||||
case "keyring":
|
||||
config.Storage = tokencache.StorageKeyring
|
||||
case "disk":
|
||||
config.Storage = tokencache.StorageDisk
|
||||
case "keyring":
|
||||
config.Storage = tokencache.StorageKeyring
|
||||
default:
|
||||
return tokencache.Config{}, fmt.Errorf("token-cache-storage must be one of (%s)", allTokenCacheStorage)
|
||||
}
|
||||
|
||||
@@ -97,9 +97,7 @@ func NewCmdForHeadless(clockInterface clock.Interface, stdin stdio.Stdin, stdout
|
||||
Standalone: standaloneStandalone,
|
||||
Logger: loggerInterface,
|
||||
}
|
||||
repositoryRepository := &repository.Repository{
|
||||
Logger: loggerInterface,
|
||||
}
|
||||
repositoryRepository := &repository.Repository{}
|
||||
reader3 := &reader2.Reader{}
|
||||
writer3 := &writer2.Writer{
|
||||
Stdout: stdout,
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"encoding/gob"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
@@ -13,7 +12,6 @@ import (
|
||||
|
||||
"github.com/gofrs/flock"
|
||||
"github.com/google/wire"
|
||||
"github.com/int128/kubelogin/pkg/infrastructure/logger"
|
||||
"github.com/int128/kubelogin/pkg/oidc"
|
||||
"github.com/int128/kubelogin/pkg/tokencache"
|
||||
"github.com/zalando/go-keyring"
|
||||
@@ -39,9 +37,7 @@ type entity struct {
|
||||
|
||||
// Repository provides access to the token cache on the local filesystem.
|
||||
// Filename of a token cache is sha256 digest of the issuer, zero-character and client ID.
|
||||
type Repository struct {
|
||||
Logger logger.Interface
|
||||
}
|
||||
type Repository struct{}
|
||||
|
||||
// keyringService is used to namespace the keyring access.
|
||||
// Some implementations may also display this string when prompting the user
|
||||
@@ -57,16 +53,6 @@ func (r *Repository) FindByKey(config tokencache.Config, key tokencache.Key) (*o
|
||||
return nil, fmt.Errorf("could not compute the key: %w", err)
|
||||
}
|
||||
switch config.Storage {
|
||||
case tokencache.StorageAuto:
|
||||
t, err := readFromKeyring(checksum)
|
||||
if errors.Is(err, keyring.ErrUnsupportedPlatform) ||
|
||||
errors.Is(err, keyring.ErrNotFound) {
|
||||
return readFromFile(config, checksum)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return t, nil
|
||||
case tokencache.StorageDisk:
|
||||
return readFromFile(config, checksum)
|
||||
case tokencache.StorageKeyring:
|
||||
@@ -120,14 +106,6 @@ func (r *Repository) Save(config tokencache.Config, key tokencache.Key, tokenSet
|
||||
return fmt.Errorf("could not compute the key: %w", err)
|
||||
}
|
||||
switch config.Storage {
|
||||
case tokencache.StorageAuto:
|
||||
if err := writeToKeyring(checksum, tokenSet); err != nil {
|
||||
if errors.Is(err, keyring.ErrUnsupportedPlatform) {
|
||||
return writeToFile(config, checksum, tokenSet)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
case tokencache.StorageDisk:
|
||||
return writeToFile(config, checksum, tokenSet)
|
||||
case tokencache.StorageKeyring:
|
||||
@@ -185,36 +163,20 @@ func (r *Repository) Lock(config tokencache.Config, key tokencache.Key) (io.Clos
|
||||
}
|
||||
|
||||
func (r *Repository) DeleteAll(config tokencache.Config) error {
|
||||
return errors.Join(
|
||||
func() error {
|
||||
if err := os.RemoveAll(config.Directory); err != nil {
|
||||
return fmt.Errorf("remove the directory %s: %w", config.Directory, err)
|
||||
}
|
||||
r.Logger.Printf("Deleted the token cache at %s", config.Directory)
|
||||
return nil
|
||||
}(),
|
||||
func() error {
|
||||
switch config.Storage {
|
||||
case tokencache.StorageAuto:
|
||||
if err := keyring.DeleteAll(keyringService); err != nil {
|
||||
if errors.Is(err, keyring.ErrUnsupportedPlatform) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("keyring delete: %w", err)
|
||||
}
|
||||
r.Logger.Printf("Deleted the token cache in the keyring")
|
||||
return nil
|
||||
case tokencache.StorageKeyring:
|
||||
if err := keyring.DeleteAll(keyringService); err != nil {
|
||||
return fmt.Errorf("keyring delete: %w", err)
|
||||
}
|
||||
r.Logger.Printf("Deleted the token cache in the keyring")
|
||||
return nil
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}(),
|
||||
)
|
||||
switch config.Storage {
|
||||
case tokencache.StorageDisk:
|
||||
if err := os.RemoveAll(config.Directory); err != nil {
|
||||
return fmt.Errorf("remove the directory %s: %w", config.Directory, err)
|
||||
}
|
||||
return nil
|
||||
case tokencache.StorageKeyring:
|
||||
if err := keyring.DeleteAll(keyringService); err != nil {
|
||||
return fmt.Errorf("keyring delete: %w", err)
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("unknown storage mode: %v", config.Storage)
|
||||
}
|
||||
}
|
||||
|
||||
func encodeKey(tokenSet oidc.TokenSet) ([]byte, error) {
|
||||
|
||||
@@ -25,10 +25,8 @@ type Config struct {
|
||||
type Storage byte
|
||||
|
||||
const (
|
||||
// StorageAuto will prefer keyring when available, and fallback to disk when not.
|
||||
StorageAuto Storage = iota
|
||||
// StorageDisk will only store cached keys on disk.
|
||||
StorageDisk
|
||||
StorageDisk Storage = iota
|
||||
// StorageDisk will only store cached keys in the OS keyring.
|
||||
StorageKeyring
|
||||
)
|
||||
|
||||
@@ -21,7 +21,7 @@ type Interface interface {
|
||||
|
||||
// Input represents an input of the Clean use-case.
|
||||
type Input struct {
|
||||
TokenCacheConfig tokencache.Config
|
||||
TokenCacheDir string
|
||||
}
|
||||
|
||||
type Clean struct {
|
||||
@@ -31,8 +31,17 @@ type Clean struct {
|
||||
|
||||
func (u *Clean) Do(ctx context.Context, in Input) error {
|
||||
u.Logger.V(1).Infof("Deleting the token cache")
|
||||
if err := u.TokenCacheRepository.DeleteAll(in.TokenCacheConfig); err != nil {
|
||||
return fmt.Errorf("delete the token cache: %w", err)
|
||||
|
||||
if err := u.TokenCacheRepository.DeleteAll(tokencache.Config{Directory: in.TokenCacheDir, Storage: tokencache.StorageDisk}); err != nil {
|
||||
return fmt.Errorf("delete the token cache from %s: %w", in.TokenCacheDir, err)
|
||||
}
|
||||
u.Logger.Printf("Deleted the token cache from %s", in.TokenCacheDir)
|
||||
|
||||
if err := u.TokenCacheRepository.DeleteAll(tokencache.Config{Directory: in.TokenCacheDir, Storage: tokencache.StorageKeyring}); err != nil {
|
||||
// Do not return an error because the keyring may not be available.
|
||||
u.Logger.Printf("Could not delete the token cache from the keyring: %s", err)
|
||||
} else {
|
||||
u.Logger.Printf("Deleted the token cache from the keyring")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3,9 +3,17 @@ package setup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
_ "embed"
|
||||
|
||||
"github.com/google/wire"
|
||||
"github.com/int128/kubelogin/pkg/infrastructure/logger"
|
||||
"github.com/int128/kubelogin/pkg/oidc"
|
||||
"github.com/int128/kubelogin/pkg/tlsclientconfig"
|
||||
"github.com/int128/kubelogin/pkg/usecases/authentication"
|
||||
)
|
||||
|
||||
@@ -15,11 +23,62 @@ var Set = wire.NewSet(
|
||||
)
|
||||
|
||||
type Interface interface {
|
||||
DoStage1()
|
||||
DoStage2(ctx context.Context, in Stage2Input) error
|
||||
Do(ctx context.Context, in Input) error
|
||||
}
|
||||
|
||||
type Setup struct {
|
||||
Authentication authentication.Interface
|
||||
Logger logger.Interface
|
||||
}
|
||||
|
||||
//go:embed setup.md
|
||||
var setupMarkdown string
|
||||
|
||||
var setupTemplate = template.Must(template.New("setup.md").Funcs(template.FuncMap{
|
||||
"quote": strconv.Quote,
|
||||
}).Parse(setupMarkdown))
|
||||
|
||||
type Input struct {
|
||||
IssuerURL string
|
||||
ClientID string
|
||||
ClientSecret string
|
||||
ExtraScopes []string
|
||||
UseAccessToken bool
|
||||
PKCEMethod oidc.PKCEMethod
|
||||
GrantOptionSet authentication.GrantOptionSet
|
||||
TLSClientConfig tlsclientconfig.Config
|
||||
ChangedFlags []string
|
||||
}
|
||||
|
||||
func (u Setup) Do(ctx context.Context, in Input) error {
|
||||
u.Logger.Printf("Authentication in progress...")
|
||||
out, err := u.Authentication.Do(ctx, authentication.Input{
|
||||
Provider: oidc.Provider{
|
||||
IssuerURL: in.IssuerURL,
|
||||
ClientID: in.ClientID,
|
||||
ClientSecret: in.ClientSecret,
|
||||
ExtraScopes: in.ExtraScopes,
|
||||
PKCEMethod: in.PKCEMethod,
|
||||
UseAccessToken: in.UseAccessToken,
|
||||
},
|
||||
GrantOptionSet: in.GrantOptionSet,
|
||||
TLSClientConfig: in.TLSClientConfig,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("authentication error: %w", err)
|
||||
}
|
||||
idTokenClaims, err := out.TokenSet.DecodeWithoutVerify()
|
||||
if err != nil {
|
||||
return fmt.Errorf("you got an invalid token: %w", err)
|
||||
}
|
||||
|
||||
var b strings.Builder
|
||||
if err := setupTemplate.Execute(&b, map[string]any{
|
||||
"IDTokenPrettyJSON": idTokenClaims.Pretty,
|
||||
"Flags": in.ChangedFlags,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("render the template: %w", err)
|
||||
}
|
||||
u.Logger.Printf(b.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
24
pkg/usecases/setup/setup.md
Normal file
24
pkg/usecases/setup/setup.md
Normal file
@@ -0,0 +1,24 @@
|
||||
## Authenticated with the OpenID Connect Provider
|
||||
|
||||
You got the token with the following claims:
|
||||
|
||||
```
|
||||
{{ .IDTokenPrettyJSON }}
|
||||
```
|
||||
|
||||
## Set up the kubeconfig
|
||||
|
||||
You can run the following command to set up the kubeconfig:
|
||||
|
||||
```
|
||||
kubectl config set-credentials oidc \
|
||||
--exec-api-version=client.authentication.k8s.io/v1 \
|
||||
--exec-interactive-mode=Never \
|
||||
--exec-command=kubectl \
|
||||
--exec-arg=oidc-login \
|
||||
--exec-arg=get-token \
|
||||
{{- range $index, $flag := .Flags }}
|
||||
{{- if $index}} \{{end}}
|
||||
--exec-arg={{ $flag | quote }}
|
||||
{{- end }}
|
||||
```
|
||||
66
pkg/usecases/setup/setup_test.go
Normal file
66
pkg/usecases/setup/setup_test.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package setup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/int128/kubelogin/mocks/github.com/int128/kubelogin/pkg/usecases/authentication_mock"
|
||||
"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"
|
||||
)
|
||||
|
||||
func TestSetup_Do(t *testing.T) {
|
||||
issuedIDToken := testingJWT.EncodeF(t, func(claims *testingJWT.Claims) {
|
||||
claims.Issuer = "https://issuer.example.com"
|
||||
claims.Subject = "YOUR_SUBJECT"
|
||||
claims.ExpiresAt = jwt.NewNumericDate(time.Now().Add(1 * time.Hour))
|
||||
})
|
||||
dummyTLSClientConfig := tlsclientconfig.Config{
|
||||
CACertFilename: []string{"/path/to/cert"},
|
||||
}
|
||||
var grantOptionSet authentication.GrantOptionSet
|
||||
|
||||
ctx := context.Background()
|
||||
in := Input{
|
||||
IssuerURL: "https://accounts.google.com",
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
ExtraScopes: []string{"email"},
|
||||
GrantOptionSet: grantOptionSet,
|
||||
TLSClientConfig: dummyTLSClientConfig,
|
||||
ChangedFlags: []string{
|
||||
"--oidc-issuer-url=https://accounts.google.com",
|
||||
"--oidc-client-id=YOUR_CLIENT_ID",
|
||||
},
|
||||
}
|
||||
mockAuthentication := authentication_mock.NewMockInterface(t)
|
||||
mockAuthentication.EXPECT().
|
||||
Do(ctx, authentication.Input{
|
||||
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: issuedIDToken,
|
||||
RefreshToken: "YOUR_REFRESH_TOKEN",
|
||||
},
|
||||
}, nil)
|
||||
u := Setup{
|
||||
Authentication: mockAuthentication,
|
||||
Logger: logger.New(t),
|
||||
}
|
||||
if err := u.Do(ctx, in); err != nil {
|
||||
t.Errorf("Do returned error: %+v", err)
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package setup
|
||||
|
||||
const stage1 = `This setup shows the instruction of Kubernetes OpenID Connect authentication.
|
||||
See also https://github.com/int128/kubelogin.
|
||||
|
||||
## 1. Set up the OpenID Connect Provider
|
||||
|
||||
Open the OpenID Connect Provider and create a client.
|
||||
|
||||
For example, Google Identity Platform:
|
||||
Open https://console.developers.google.com/apis/credentials and create an OAuth client of "Other" type.
|
||||
ISSUER is https://accounts.google.com
|
||||
|
||||
## 2. Verify authentication
|
||||
|
||||
Run the following command to proceed.
|
||||
|
||||
kubectl oidc-login setup \
|
||||
--oidc-issuer-url=ISSUER \
|
||||
--oidc-client-id=YOUR_CLIENT_ID \
|
||||
--oidc-client-secret=YOUR_CLIENT_SECRET
|
||||
|
||||
You can set your CA certificate. See also the options by --help.
|
||||
`
|
||||
|
||||
func (u *Setup) DoStage1() {
|
||||
u.Logger.Printf(stage1)
|
||||
}
|
||||
@@ -1,178 +0,0 @@
|
||||
package setup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/int128/kubelogin/pkg/oidc"
|
||||
"github.com/int128/kubelogin/pkg/tlsclientconfig"
|
||||
"github.com/int128/kubelogin/pkg/usecases/authentication"
|
||||
)
|
||||
|
||||
var stage2Tpl = template.Must(template.New("").Parse(`
|
||||
## 2. Verify authentication
|
||||
|
||||
You got a token with the following claims:
|
||||
|
||||
{{ .IDTokenPrettyJSON }}
|
||||
|
||||
## 3. Bind a cluster role
|
||||
|
||||
Run the following command:
|
||||
|
||||
kubectl create clusterrolebinding oidc-cluster-admin --clusterrole=cluster-admin --user='{{ .IssuerURL }}#{{ .Subject }}'
|
||||
|
||||
## 4. Set up the Kubernetes API server
|
||||
|
||||
Add the following options to the kube-apiserver:
|
||||
|
||||
--oidc-issuer-url={{ .IssuerURL }}
|
||||
--oidc-client-id={{ .ClientID }}
|
||||
|
||||
## 5. Set up the kubeconfig
|
||||
|
||||
Run the following command:
|
||||
|
||||
kubectl config set-credentials oidc \
|
||||
--exec-api-version=client.authentication.k8s.io/v1 \
|
||||
--exec-command=kubectl \
|
||||
--exec-arg=oidc-login \
|
||||
--exec-arg=get-token \
|
||||
{{- range $index, $arg := .Args }}
|
||||
{{- if $index}} \{{end}}
|
||||
--exec-arg={{ $arg }}
|
||||
{{- end }}
|
||||
|
||||
## 6. Verify cluster access
|
||||
|
||||
Make sure you can access the Kubernetes cluster.
|
||||
|
||||
kubectl --user=oidc get nodes
|
||||
|
||||
You can switch the default context to oidc.
|
||||
|
||||
kubectl config set-context --current --user=oidc
|
||||
|
||||
You can share the kubeconfig to your team members for on-boarding.
|
||||
`))
|
||||
|
||||
type stage2Vars struct {
|
||||
IDTokenPrettyJSON string
|
||||
IssuerURL string
|
||||
ClientID string
|
||||
Args []string
|
||||
Subject string
|
||||
}
|
||||
|
||||
// Stage2Input represents an input DTO of the stage2.
|
||||
type Stage2Input struct {
|
||||
IssuerURL string
|
||||
ClientID string
|
||||
ClientSecret string
|
||||
ExtraScopes []string // optional
|
||||
UseAccessToken bool // optional
|
||||
ListenAddressArgs []string // non-nil if set by the command arg
|
||||
PKCEMethod oidc.PKCEMethod
|
||||
PKCEMethodArg string
|
||||
GrantOptionSet authentication.GrantOptionSet
|
||||
TLSClientConfig tlsclientconfig.Config
|
||||
}
|
||||
|
||||
func (u *Setup) DoStage2(ctx context.Context, in Stage2Input) error {
|
||||
u.Logger.Printf("authentication in progress...")
|
||||
out, err := u.Authentication.Do(ctx, authentication.Input{
|
||||
Provider: oidc.Provider{
|
||||
IssuerURL: in.IssuerURL,
|
||||
ClientID: in.ClientID,
|
||||
ClientSecret: in.ClientSecret,
|
||||
ExtraScopes: in.ExtraScopes,
|
||||
PKCEMethod: in.PKCEMethod,
|
||||
UseAccessToken: in.UseAccessToken,
|
||||
},
|
||||
GrantOptionSet: in.GrantOptionSet,
|
||||
TLSClientConfig: in.TLSClientConfig,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("authentication error: %w", err)
|
||||
}
|
||||
idTokenClaims, err := out.TokenSet.DecodeWithoutVerify()
|
||||
if err != nil {
|
||||
return fmt.Errorf("you got an invalid token: %w", err)
|
||||
}
|
||||
|
||||
v := stage2Vars{
|
||||
IDTokenPrettyJSON: idTokenClaims.Pretty,
|
||||
IssuerURL: in.IssuerURL,
|
||||
ClientID: in.ClientID,
|
||||
Args: makeCredentialPluginArgs(in),
|
||||
Subject: idTokenClaims.Subject,
|
||||
}
|
||||
var b strings.Builder
|
||||
if err := stage2Tpl.Execute(&b, &v); err != nil {
|
||||
return fmt.Errorf("could not render the template: %w", err)
|
||||
}
|
||||
u.Logger.Printf(b.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeCredentialPluginArgs(in Stage2Input) []string {
|
||||
var args []string
|
||||
args = append(args, "--oidc-issuer-url="+in.IssuerURL)
|
||||
args = append(args, "--oidc-client-id="+in.ClientID)
|
||||
if in.ClientSecret != "" {
|
||||
args = append(args, "--oidc-client-secret="+in.ClientSecret)
|
||||
}
|
||||
for _, extraScope := range in.ExtraScopes {
|
||||
args = append(args, "--oidc-extra-scope="+extraScope)
|
||||
}
|
||||
if in.PKCEMethodArg != "" {
|
||||
args = append(args, "--oidc-pkce-method="+in.PKCEMethodArg)
|
||||
}
|
||||
if in.UseAccessToken {
|
||||
args = append(args, "--oidc-use-access-token")
|
||||
}
|
||||
for _, f := range in.TLSClientConfig.CACertFilename {
|
||||
args = append(args, "--certificate-authority="+f)
|
||||
}
|
||||
for _, d := range in.TLSClientConfig.CACertData {
|
||||
args = append(args, "--certificate-authority-data="+d)
|
||||
}
|
||||
if in.TLSClientConfig.SkipTLSVerify {
|
||||
args = append(args, "--insecure-skip-tls-verify")
|
||||
}
|
||||
|
||||
if in.GrantOptionSet.AuthCodeBrowserOption != nil {
|
||||
if in.GrantOptionSet.AuthCodeBrowserOption.SkipOpenBrowser {
|
||||
args = append(args, "--skip-open-browser")
|
||||
}
|
||||
if in.GrantOptionSet.AuthCodeBrowserOption.BrowserCommand != "" {
|
||||
args = append(args, "--browser-command="+in.GrantOptionSet.AuthCodeBrowserOption.BrowserCommand)
|
||||
}
|
||||
if in.GrantOptionSet.AuthCodeBrowserOption.LocalServerCertFile != "" {
|
||||
// Resolve the absolute path for the cert files so the user doesn't have to know
|
||||
// to use one when running setup.
|
||||
certpath, err := filepath.Abs(in.GrantOptionSet.AuthCodeBrowserOption.LocalServerCertFile)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
keypath, err := filepath.Abs(in.GrantOptionSet.AuthCodeBrowserOption.LocalServerKeyFile)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
args = append(args, "--local-server-cert="+certpath)
|
||||
args = append(args, "--local-server-key="+keypath)
|
||||
}
|
||||
}
|
||||
for _, l := range in.ListenAddressArgs {
|
||||
args = append(args, "--listen-address="+l)
|
||||
}
|
||||
if in.GrantOptionSet.ROPCOption != nil {
|
||||
if in.GrantOptionSet.ROPCOption.Username != "" {
|
||||
args = append(args, "--username="+in.GrantOptionSet.ROPCOption.Username)
|
||||
}
|
||||
}
|
||||
return args
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
package setup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/int128/kubelogin/mocks/github.com/int128/kubelogin/pkg/usecases/authentication_mock"
|
||||
"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/authcode"
|
||||
"github.com/int128/kubelogin/pkg/usecases/authentication/ropc"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
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 = jwt.NewNumericDate(time.Now().Add(1 * time.Hour))
|
||||
})
|
||||
dummyTLSClientConfig := tlsclientconfig.Config{
|
||||
CACertFilename: []string{"/path/to/cert"},
|
||||
}
|
||||
var grantOptionSet authentication.GrantOptionSet
|
||||
|
||||
ctx := context.Background()
|
||||
in := Stage2Input{
|
||||
IssuerURL: "https://accounts.google.com",
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
ExtraScopes: []string{"email"},
|
||||
GrantOptionSet: grantOptionSet,
|
||||
TLSClientConfig: dummyTLSClientConfig,
|
||||
}
|
||||
mockAuthentication := authentication_mock.NewMockInterface(t)
|
||||
mockAuthentication.EXPECT().
|
||||
Do(ctx, authentication.Input{
|
||||
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: issuedIDToken,
|
||||
RefreshToken: "YOUR_REFRESH_TOKEN",
|
||||
},
|
||||
}, nil)
|
||||
u := Setup{
|
||||
Authentication: mockAuthentication,
|
||||
Logger: logger.New(t),
|
||||
}
|
||||
if err := u.DoStage2(ctx, in); err != nil {
|
||||
t.Errorf("DoStage2 returned error: %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_makeCredentialPluginArgs(t *testing.T) {
|
||||
in := Stage2Input{
|
||||
IssuerURL: "https://oidc.example.com",
|
||||
ClientID: "test_kid",
|
||||
ClientSecret: "test_ksecret",
|
||||
ExtraScopes: []string{"groups"},
|
||||
PKCEMethodArg: "S256",
|
||||
ListenAddressArgs: []string{"127.0.0.1:8080", "127.0.0.1:8888"},
|
||||
GrantOptionSet: authentication.GrantOptionSet{
|
||||
AuthCodeBrowserOption: &authcode.BrowserOption{
|
||||
SkipOpenBrowser: true,
|
||||
BrowserCommand: "firefox",
|
||||
LocalServerCertFile: "/path/to/cert.crt",
|
||||
LocalServerKeyFile: "/path/to/cert.key",
|
||||
},
|
||||
ROPCOption: &ropc.Option{
|
||||
Username: "user1",
|
||||
},
|
||||
},
|
||||
TLSClientConfig: tlsclientconfig.Config{
|
||||
CACertFilename: []string{"/path/to/ca.crt"},
|
||||
CACertData: []string{"base64encoded1"},
|
||||
SkipTLSVerify: true,
|
||||
},
|
||||
}
|
||||
expet := []string{
|
||||
"--oidc-issuer-url=https://oidc.example.com",
|
||||
"--oidc-client-id=test_kid",
|
||||
"--oidc-client-secret=test_ksecret",
|
||||
"--oidc-extra-scope=groups",
|
||||
"--oidc-pkce-method=S256",
|
||||
"--certificate-authority=/path/to/ca.crt",
|
||||
"--certificate-authority-data=base64encoded1",
|
||||
"--insecure-skip-tls-verify",
|
||||
"--skip-open-browser",
|
||||
"--browser-command=firefox",
|
||||
"--local-server-cert=/path/to/cert.crt",
|
||||
"--local-server-key=/path/to/cert.key",
|
||||
"--listen-address=127.0.0.1:8080",
|
||||
"--listen-address=127.0.0.1:8888",
|
||||
"--username=user1",
|
||||
}
|
||||
got := makeCredentialPluginArgs(in)
|
||||
assert.Equal(t, expet, got)
|
||||
}
|
||||
@@ -2,12 +2,11 @@
|
||||
|
||||
This is an automated test for verifying behavior of the plugin with a real Kubernetes cluster and OIDC provider.
|
||||
|
||||
|
||||
## Purpose
|
||||
|
||||
This test checks the following points:
|
||||
|
||||
1. User can set up Kubernetes OIDC authentication using [setup guide](../docs/setup.md).
|
||||
1. User can set up Kubernetes OIDC authentication using the [setup guide](../docs/setup.md).
|
||||
1. User can log in to an OIDC provider on a browser.
|
||||
1. User can access the cluster using a token returned from the plugin.
|
||||
|
||||
@@ -18,7 +17,6 @@ It depends on the following components:
|
||||
- Browser (Chrome)
|
||||
- kubectl command
|
||||
|
||||
|
||||
## How it works
|
||||
|
||||
Let's take a look at the diagram.
|
||||
@@ -45,7 +43,6 @@ It performs the test by the following steps:
|
||||
1. kube-apiserver verifies the token by Dex.
|
||||
1. Check if kubectl exited with code 0.
|
||||
|
||||
|
||||
## Run locally
|
||||
|
||||
You need to set up the following components:
|
||||
@@ -80,7 +77,6 @@ make terminate
|
||||
make clean
|
||||
```
|
||||
|
||||
|
||||
## Technical consideration
|
||||
|
||||
### Network and DNS
|
||||
|
||||
@@ -8,13 +8,16 @@ export KUBECONFIG
|
||||
cluster:
|
||||
cp $(CERT_DIR)/ca.crt /tmp/kubelogin-system-test-dex-ca.crt
|
||||
kind create cluster --name $(CLUSTER_NAME) --config cluster.yaml
|
||||
# add the Dex container IP to /etc/hosts
|
||||
|
||||
# Add the Dex container IP to /etc/hosts.
|
||||
docker inspect -f '{{.NetworkSettings.Networks.kind.IPAddress}}' dex-server | sed -e 's,$$, dex-server,' | \
|
||||
docker exec -i $(CLUSTER_NAME)-control-plane tee -a /etc/hosts
|
||||
# wait for kube-apiserver oidc initialization
|
||||
# (oidc authenticator will retry oidc discovery every 10s)
|
||||
|
||||
# Wait for kube-apiserver oidc initialization.
|
||||
# oidc authenticator will retry oidc discovery every 10s.
|
||||
sleep 10
|
||||
# add the cluster role
|
||||
|
||||
# Add the cluster role.
|
||||
kubectl create clusterrole cluster-readonly --verb=get,watch,list --resource='*.*'
|
||||
kubectl create clusterrolebinding cluster-readonly --clusterrole=cluster-readonly --user=admin@example.com
|
||||
|
||||
|
||||
@@ -2,15 +2,18 @@ CERT_DIR := ../cert
|
||||
|
||||
.PHONY: dex
|
||||
dex: dex.yaml
|
||||
# wait for kind network
|
||||
while true; do if docker network inspect kind; then break; fi; sleep 1; done
|
||||
# create a container
|
||||
# Wait for kind network.
|
||||
until docker network inspect kind; do sleep 1; done
|
||||
|
||||
# Create a container.
|
||||
docker create -q --name dex-server -p 10443:10443 --network kind ghcr.io/dexidp/dex:v2.39.0 dex serve /dex.yaml
|
||||
# deploy the config
|
||||
|
||||
# Deploy the config.
|
||||
docker cp $(CERT_DIR)/server.crt dex-server:/
|
||||
docker cp $(CERT_DIR)/server.key dex-server:/
|
||||
docker cp dex.yaml dex-server:/
|
||||
# start the container
|
||||
|
||||
# Start the container.
|
||||
docker start dex-server
|
||||
docker logs dex-server
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ test: build
|
||||
--exec-arg=--oidc-client-id=YOUR_CLIENT_ID \
|
||||
--exec-arg=--oidc-client-secret=YOUR_CLIENT_SECRET \
|
||||
--exec-arg=--oidc-extra-scope=email \
|
||||
--exec-arg=--token-cache-storage=keyring \
|
||||
--exec-arg=--certificate-authority=$(CERT_DIR)/ca.crt \
|
||||
--exec-arg=--browser-command=$(BIN_DIR)/chromelogin
|
||||
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
GOBIN := $(CURDIR)/bin
|
||||
export GOBIN
|
||||
|
||||
all: bin/mockery bin/wire bin/golangci-lint
|
||||
|
||||
bin/mockery:
|
||||
go install github.com/vektra/mockery/v2
|
||||
|
||||
bin/wire:
|
||||
go install github.com/google/wire/cmd/wire
|
||||
|
||||
bin/golangci-lint:
|
||||
go install github.com/golangci/golangci-lint/cmd/golangci-lint
|
||||
205
tools/go.mod
205
tools/go.mod
@@ -1,205 +0,0 @@
|
||||
module github.com/int128/kubelogin/tools
|
||||
|
||||
go 1.23.5
|
||||
|
||||
require (
|
||||
github.com/golangci/golangci-lint v1.63.4
|
||||
github.com/google/wire v0.6.0
|
||||
github.com/vektra/mockery/v2 v2.51.0
|
||||
)
|
||||
|
||||
require (
|
||||
4d63.com/gocheckcompilerdirectives v1.2.1 // indirect
|
||||
4d63.com/gochecknoglobals v0.2.1 // indirect
|
||||
github.com/4meepo/tagalign v1.4.1 // indirect
|
||||
github.com/Abirdcfly/dupword v0.1.3 // indirect
|
||||
github.com/Antonboom/errname v1.0.0 // indirect
|
||||
github.com/Antonboom/nilnil v1.0.1 // indirect
|
||||
github.com/Antonboom/testifylint v1.5.2 // indirect
|
||||
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect
|
||||
github.com/Crocmagnon/fatcontext v0.5.3 // indirect
|
||||
github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect
|
||||
github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.3.0 // indirect
|
||||
github.com/OpenPeeDeeP/depguard/v2 v2.2.0 // indirect
|
||||
github.com/alecthomas/go-check-sumtype v0.3.1 // indirect
|
||||
github.com/alexkohler/nakedret/v2 v2.0.5 // indirect
|
||||
github.com/alexkohler/prealloc v1.0.0 // indirect
|
||||
github.com/alingse/asasalint v0.0.11 // indirect
|
||||
github.com/alingse/nilnesserr v0.1.1 // indirect
|
||||
github.com/ashanbrown/forbidigo v1.6.0 // indirect
|
||||
github.com/ashanbrown/makezero v1.2.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bkielbasa/cyclop v1.2.3 // indirect
|
||||
github.com/blizzy78/varnamelen v0.8.0 // indirect
|
||||
github.com/bombsimon/wsl/v4 v4.5.0 // indirect
|
||||
github.com/breml/bidichk v0.3.2 // indirect
|
||||
github.com/breml/errchkjson v0.4.0 // indirect
|
||||
github.com/butuzov/ireturn v0.3.1 // indirect
|
||||
github.com/butuzov/mirror v1.3.0 // indirect
|
||||
github.com/catenacyber/perfsprint v0.7.1 // indirect
|
||||
github.com/ccojocar/zxcvbn-go v1.0.2 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/charithe/durationcheck v0.0.10 // indirect
|
||||
github.com/chavacava/garif v0.1.0 // indirect
|
||||
github.com/chigopher/pathlib v0.19.1 // indirect
|
||||
github.com/ckaznocha/intrange v0.3.0 // indirect
|
||||
github.com/curioswitch/go-reassign v0.3.0 // indirect
|
||||
github.com/daixiang0/gci v0.13.5 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/denis-tingaikin/go-header v0.5.0 // indirect
|
||||
github.com/ettle/strcase v0.2.0 // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/fatih/structtag v1.2.0 // indirect
|
||||
github.com/firefart/nonamedreturns v1.0.5 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/fzipp/gocyclo v0.6.0 // indirect
|
||||
github.com/ghostiam/protogetter v0.3.8 // indirect
|
||||
github.com/go-critic/go-critic v0.11.5 // indirect
|
||||
github.com/go-toolsmith/astcast v1.1.0 // indirect
|
||||
github.com/go-toolsmith/astcopy v1.1.0 // indirect
|
||||
github.com/go-toolsmith/astequal v1.2.0 // indirect
|
||||
github.com/go-toolsmith/astfmt v1.1.0 // indirect
|
||||
github.com/go-toolsmith/astp v1.1.0 // indirect
|
||||
github.com/go-toolsmith/strparse v1.1.0 // indirect
|
||||
github.com/go-toolsmith/typep v1.1.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
|
||||
github.com/go-xmlfmt/xmlfmt v1.1.3 // indirect
|
||||
github.com/gobwas/glob v0.2.3 // indirect
|
||||
github.com/gofrs/flock v0.12.1 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a // indirect
|
||||
github.com/golangci/go-printf-func-name v0.1.0 // indirect
|
||||
github.com/golangci/gofmt v0.0.0-20241223200906-057b0627d9b9 // indirect
|
||||
github.com/golangci/misspell v0.6.0 // indirect
|
||||
github.com/golangci/plugin-module-register v0.1.1 // indirect
|
||||
github.com/golangci/revgrep v0.5.3 // indirect
|
||||
github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/subcommands v1.2.0 // indirect
|
||||
github.com/gordonklaus/ineffassign v0.1.0 // indirect
|
||||
github.com/gostaticanalysis/analysisutil v0.7.1 // indirect
|
||||
github.com/gostaticanalysis/comment v1.4.2 // indirect
|
||||
github.com/gostaticanalysis/forcetypeassert v0.1.0 // indirect
|
||||
github.com/gostaticanalysis/nilerr v0.1.1 // indirect
|
||||
github.com/hashicorp/go-immutable-radix/v2 v2.1.0 // indirect
|
||||
github.com/hashicorp/go-version v1.7.0 // indirect
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/hexops/gotextdiff v1.0.3 // indirect
|
||||
github.com/huandu/xstrings v1.4.0 // indirect
|
||||
github.com/iancoleman/strcase v0.2.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jgautheron/goconst v1.7.1 // indirect
|
||||
github.com/jingyugao/rowserrcheck v1.1.1 // indirect
|
||||
github.com/jinzhu/copier v0.3.5 // indirect
|
||||
github.com/jjti/go-spancheck v0.6.4 // indirect
|
||||
github.com/julz/importas v0.2.0 // indirect
|
||||
github.com/karamaru-alpha/copyloopvar v1.1.0 // indirect
|
||||
github.com/kisielk/errcheck v1.8.0 // indirect
|
||||
github.com/kkHAIKE/contextcheck v1.1.5 // indirect
|
||||
github.com/kulti/thelper v0.6.3 // indirect
|
||||
github.com/kunwardeep/paralleltest v1.0.10 // indirect
|
||||
github.com/kyoh86/exportloopref v0.1.11 // indirect
|
||||
github.com/lasiar/canonicalheader v1.1.2 // indirect
|
||||
github.com/ldez/exptostd v0.3.1 // indirect
|
||||
github.com/ldez/gomoddirectives v0.6.0 // indirect
|
||||
github.com/ldez/grignotin v0.7.0 // indirect
|
||||
github.com/ldez/tagliatelle v0.7.1 // indirect
|
||||
github.com/ldez/usetesting v0.4.2 // indirect
|
||||
github.com/leonklingele/grouper v1.1.2 // indirect
|
||||
github.com/macabu/inamedparam v0.1.3 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/maratori/testableexamples v1.0.0 // indirect
|
||||
github.com/maratori/testpackage v1.1.1 // indirect
|
||||
github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||
github.com/mgechev/revive v1.5.1 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/moricho/tparallel v0.3.2 // indirect
|
||||
github.com/nakabonne/nestif v0.3.1 // indirect
|
||||
github.com/nishanths/exhaustive v0.12.0 // indirect
|
||||
github.com/nishanths/predeclared v0.2.2 // indirect
|
||||
github.com/nunnatsa/ginkgolinter v0.18.4 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/polyfloyd/go-errorlint v1.7.0 // indirect
|
||||
github.com/prometheus/client_golang v1.12.1 // indirect
|
||||
github.com/prometheus/client_model v0.2.0 // indirect
|
||||
github.com/prometheus/common v0.32.1 // indirect
|
||||
github.com/prometheus/procfs v0.7.3 // indirect
|
||||
github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1 // indirect
|
||||
github.com/quasilyte/go-ruleguard/dsl v0.3.22 // indirect
|
||||
github.com/quasilyte/gogrep v0.5.0 // indirect
|
||||
github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect
|
||||
github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect
|
||||
github.com/raeperd/recvcheck v0.2.0 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
||||
github.com/rs/zerolog v1.29.0 // indirect
|
||||
github.com/ryancurrah/gomodguard v1.3.5 // indirect
|
||||
github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect
|
||||
github.com/sanposhiho/wastedassign/v2 v2.1.0 // indirect
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 // indirect
|
||||
github.com/sashamelentyev/interfacebloat v1.1.0 // indirect
|
||||
github.com/sashamelentyev/usestdlibvars v1.28.0 // indirect
|
||||
github.com/securego/gosec/v2 v2.21.4 // indirect
|
||||
github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/sivchari/containedctx v1.0.3 // indirect
|
||||
github.com/sivchari/tenv v1.12.1 // indirect
|
||||
github.com/sonatard/noctx v0.1.0 // indirect
|
||||
github.com/sourcegraph/go-diff v0.7.0 // indirect
|
||||
github.com/spf13/afero v1.11.0 // indirect
|
||||
github.com/spf13/cast v1.5.0 // indirect
|
||||
github.com/spf13/cobra v1.8.1 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/spf13/viper v1.15.0 // indirect
|
||||
github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect
|
||||
github.com/stbenjam/no-sprintf-host-port v0.2.0 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/stretchr/testify v1.10.0 // indirect
|
||||
github.com/subosito/gotenv v1.4.2 // indirect
|
||||
github.com/tdakkota/asciicheck v0.3.0 // indirect
|
||||
github.com/tetafro/godot v1.4.20 // indirect
|
||||
github.com/timakin/bodyclose v0.0.0-20241017074812-ed6a65f985e3 // indirect
|
||||
github.com/timonwong/loggercheck v0.10.1 // indirect
|
||||
github.com/tomarrell/wrapcheck/v2 v2.10.0 // indirect
|
||||
github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect
|
||||
github.com/ultraware/funlen v0.2.0 // indirect
|
||||
github.com/ultraware/whitespace v0.2.0 // indirect
|
||||
github.com/uudashr/gocognit v1.2.0 // indirect
|
||||
github.com/uudashr/iface v1.3.0 // indirect
|
||||
github.com/xen0n/gosmopolitan v1.2.2 // indirect
|
||||
github.com/yagipy/maintidx v1.0.0 // indirect
|
||||
github.com/yeya24/promlinter v0.3.0 // indirect
|
||||
github.com/ykadowak/zerologlint v0.1.5 // indirect
|
||||
gitlab.com/bosi/decorder v0.4.2 // indirect
|
||||
go-simpler.org/musttag v0.13.0 // indirect
|
||||
go-simpler.org/sloglint v0.7.2 // indirect
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
go.uber.org/automaxprocs v1.6.0 // indirect
|
||||
go.uber.org/multierr v1.8.0 // indirect
|
||||
go.uber.org/zap v1.24.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect
|
||||
golang.org/x/exp/typeparams v0.0.0-20241108190413-2d47ceb2692f // indirect
|
||||
golang.org/x/mod v0.22.0 // indirect
|
||||
golang.org/x/sync v0.10.0 // indirect
|
||||
golang.org/x/sys v0.28.0 // indirect
|
||||
golang.org/x/term v0.16.0 // indirect
|
||||
golang.org/x/text v0.20.0 // indirect
|
||||
golang.org/x/tools v0.28.0 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
honnef.co/go/tools v0.5.1 // indirect
|
||||
mvdan.cc/gofumpt v0.7.0 // indirect
|
||||
mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f // indirect
|
||||
)
|
||||
1017
tools/go.sum
1017
tools/go.sum
File diff suppressed because it is too large
Load Diff
@@ -1,9 +0,0 @@
|
||||
//go:build tools
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "github.com/google/wire/cmd/wire"
|
||||
_ "github.com/vektra/mockery/v2/cmd"
|
||||
_ "github.com/golangci/golangci-lint/cmd/golangci-lint"
|
||||
)
|
||||
Reference in New Issue
Block a user