mirror of
https://github.com/int128/kubelogin.git
synced 2026-02-19 19:09:50 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e392645593 | ||
|
|
1c2ca4f248 | ||
|
|
21c50a5a0e | ||
|
|
b3f2e2d9ec |
34
.github/workflows/acceptance-test.yaml
vendored
34
.github/workflows/acceptance-test.yaml
vendored
@@ -1,34 +0,0 @@
|
||||
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@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.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
|
||||
2
.github/workflows/docker.yaml
vendored
2
.github/workflows/docker.yaml
vendored
@@ -46,7 +46,7 @@ jobs:
|
||||
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@ca877d9245402d1537745e0e356eab47c3520991 # v6.13.0
|
||||
- uses: docker/build-push-action@67a2d409c0a876cbe6b11854e3e25193efe4e62d # v6.12.0
|
||||
id: build
|
||||
with:
|
||||
push: ${{ github.event_name == 'push' }}
|
||||
|
||||
8
.github/workflows/go.yaml
vendored
8
.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@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
|
||||
- uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.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@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
|
||||
- uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache-dependency-path: go.sum
|
||||
@@ -59,7 +59,7 @@ jobs:
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
|
||||
- uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
|
||||
with:
|
||||
go-version-file: tools/go.mod
|
||||
cache-dependency-path: tools/go.sum
|
||||
@@ -70,7 +70,7 @@ jobs:
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
|
||||
- uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
|
||||
with:
|
||||
go-version-file: tools/go.mod
|
||||
cache-dependency-path: tools/go.sum
|
||||
|
||||
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@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
|
||||
- uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.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@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
|
||||
- uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache-dependency-path: go.sum
|
||||
|
||||
@@ -21,7 +21,7 @@ Install the latest release from [Homebrew](https://brew.sh/), [Krew](https://git
|
||||
|
||||
```sh
|
||||
# Homebrew (macOS and Linux)
|
||||
brew install kubelogin
|
||||
brew install int128/kubelogin/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
|
||||
|
||||
Kubelogin stores the token cache to the file system by default.
|
||||
For enhanced security, it is recommended to store it to the keyring.
|
||||
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.
|
||||
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 from the keyring
|
||||
Deleted the token cache in the keyring
|
||||
```
|
||||
|
||||
Kubelogin will ask you to log in via the browser again.
|
||||
|
||||
@@ -4,38 +4,33 @@ 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 the access control.
|
||||
# set up 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/v1 \
|
||||
--exec-interactive-mode=Never \
|
||||
--exec-api-version=client.authentication.k8s.io/v1beta1 \
|
||||
--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
|
||||
|
||||
# Show the kubeconfig.
|
||||
kubectl config view
|
||||
|
||||
# clean up the resources
|
||||
.PHONY: clean
|
||||
clean:
|
||||
-rm -r $(OUTPUT_DIR)
|
||||
|
||||
.PHONY: delete-cluster
|
||||
delete-cluster:
|
||||
kind delete cluster --name $(CLUSTER_NAME)
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
# kubelogin/acceptance_test
|
||||
|
||||
This is a manual test to verify if the Kubernetes OIDC authentication works with your OIDC provider.
|
||||
This is a manual test for verifying Kubernetes OIDC authentication with your OIDC provider.
|
||||
|
||||
|
||||
## Purpose
|
||||
|
||||
This test checks the following points:
|
||||
|
||||
1. You can set up your OIDC provider using the [setup guide](../docs/setup.md).
|
||||
1. You can set up your OIDC provider using [setup guide](../docs/setup.md).
|
||||
1. The plugin works with your OIDC provider.
|
||||
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Prerequisite
|
||||
@@ -20,7 +22,7 @@ make -C ..
|
||||
```
|
||||
|
||||
You need to set up your provider.
|
||||
See the [setup guide](../docs/setup.md) for more.
|
||||
See [setup guide](../docs/setup.md) for more.
|
||||
|
||||
You need to install the following tools:
|
||||
|
||||
@@ -42,6 +44,7 @@ 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,18 +24,17 @@ See the [usage](usage.md) for the details.
|
||||
|
||||
You can log in with a Google account.
|
||||
|
||||
Open [Google APIs Console](https://console.cloud.google.com/apis/credentials) and create an OAuth client with the following setting:
|
||||
Open [Google APIs Console](https://console.developers.google.com/apis/credentials) and create an OAuth client with the following setting:
|
||||
|
||||
- Application Type: Desktop app
|
||||
- Application Type: Other
|
||||
|
||||
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` |
|
||||
| `YOUR_CLIENT_SECRET` | `XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX` |
|
||||
| Variable | Value |
|
||||
| ---------------- | -------------------------------- |
|
||||
| `ISSUER_URL` | `https://accounts.google.com` |
|
||||
| `YOUR_CLIENT_ID` | `xxx.apps.googleusercontent.com` |
|
||||
|
||||
### Keycloak
|
||||
|
||||
@@ -68,8 +67,6 @@ 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.
|
||||
@@ -132,7 +129,7 @@ Replace the following variables in the later sections.
|
||||
| `ISSUER_URL` | `https://YOUR_ORGANIZATION.okta.com` |
|
||||
| `YOUR_CLIENT_ID` | random string |
|
||||
|
||||
`YOUR_CLIENT_SECRET` is not required for this configuration.
|
||||
You do not need to set `YOUR_CLIENT_SECRET`.
|
||||
|
||||
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).
|
||||
@@ -157,7 +154,7 @@ Leverage the following variables in the next steps.
|
||||
|
||||
`YOUR_CLIENT_SECRET` is not required for this configuration.
|
||||
|
||||
## 2. Authenticate with the OpenID Connect Provider
|
||||
## 2. Verify authentication
|
||||
|
||||
Run the following command:
|
||||
|
||||
@@ -167,12 +164,11 @@ 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.
|
||||
|
||||
For the full options,
|
||||
You can set extra options, for example, extra scope or CA certificate.
|
||||
See also the full options.
|
||||
|
||||
```sh
|
||||
kubectl oidc-login setup --help
|
||||
@@ -180,12 +176,14 @@ kubectl oidc-login setup --help
|
||||
|
||||
## 3. Bind a cluster role
|
||||
|
||||
You can run the following command to bind `cluster-admin` role to you:
|
||||
Here 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:
|
||||
@@ -203,7 +201,6 @@ 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 \
|
||||
@@ -212,11 +209,6 @@ 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 (disk|keyring) (default "disk")
|
||||
--token-cache-storage string Storage for the token cache. One of (auto|keyring|disk) (default "auto")
|
||||
--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,6 +52,24 @@ 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
|
||||
@@ -105,21 +123,16 @@ See also [net/http#ProxyFromEnvironment](https://golang.org/pkg/net/http/#ProxyF
|
||||
|
||||
### Token cache
|
||||
|
||||
Kubelogin stores the token cache to the file system by default.
|
||||
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.
|
||||
|
||||
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).
|
||||
If you encounter a problem, try `--token-cache-storage` to set the storage.
|
||||
|
||||
```yaml
|
||||
# Force to use the OS keyring
|
||||
- --token-cache-storage=keyring
|
||||
```
|
||||
|
||||
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
|
||||
# Force to use the file system
|
||||
- --token-cache-storage=disk
|
||||
```
|
||||
|
||||
### Home directory expansion
|
||||
|
||||
8
go.mod
8
go.mod
@@ -3,7 +3,7 @@ module github.com/int128/kubelogin
|
||||
go 1.23.5
|
||||
|
||||
require (
|
||||
github.com/chromedp/chromedp v0.12.1
|
||||
github.com/chromedp/chromedp v0.11.2
|
||||
github.com/coreos/go-oidc/v3 v3.12.0
|
||||
github.com/gofrs/flock v0.12.1
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1
|
||||
@@ -13,7 +13,7 @@ require (
|
||||
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.6
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/zalando/go-keyring v0.2.6
|
||||
golang.org/x/oauth2 v0.25.0
|
||||
@@ -27,7 +27,7 @@ require (
|
||||
|
||||
require (
|
||||
al.essio.dev/pkg/shellescape v1.5.1 // indirect
|
||||
github.com/chromedp/cdproto v0.0.0-20250120090109-d38428e4d9c8 // indirect
|
||||
github.com/chromedp/cdproto v0.0.0-20241022234722-4d5d5faf59fb // indirect
|
||||
github.com/chromedp/sysutil v1.1.0 // indirect
|
||||
github.com/danieljoos/wincred v1.2.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
@@ -45,7 +45,7 @@ require (
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/mailru/easyjson v0.9.0 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
|
||||
15
go.sum
15
go.sum
@@ -1,10 +1,10 @@
|
||||
al.essio.dev/pkg/shellescape v1.5.1 h1:86HrALUujYS/h+GtqoB26SBEdkWfmMI6FubjXlsXyho=
|
||||
al.essio.dev/pkg/shellescape v1.5.1/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890=
|
||||
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
|
||||
github.com/chromedp/cdproto v0.0.0-20250120090109-d38428e4d9c8 h1:Q2byC+xLgH/Z7hExJ8G/jVqsvCfGhMmNgM1ysZARA3o=
|
||||
github.com/chromedp/cdproto v0.0.0-20250120090109-d38428e4d9c8/go.mod h1:RTGuBeCeabAJGi3OZf71a6cGa7oYBfBP75VJZFLv6SU=
|
||||
github.com/chromedp/chromedp v0.12.1 h1:kBMblXk7xH5/6j3K9uk8d7/c+fzXWiUsCsPte0VMwOA=
|
||||
github.com/chromedp/chromedp v0.12.1/go.mod h1:F6+wdq9LKFDMoyxhq46ZLz4VLXrsrCAR3sFqJz4Nqc0=
|
||||
github.com/chromedp/cdproto v0.0.0-20241022234722-4d5d5faf59fb h1:noKVm2SsG4v0Yd0lHNtFYc9EUxIVvrr4kJ6hM8wvIYU=
|
||||
github.com/chromedp/cdproto v0.0.0-20241022234722-4d5d5faf59fb/go.mod h1:4XqMl3iIW08jtieURWL6Tt5924w21pxirC6th662XUM=
|
||||
github.com/chromedp/chromedp v0.11.2 h1:ZRHTh7DjbNTlfIv3NFTbB7eVeu5XCNkgrpcGSpn2oX0=
|
||||
github.com/chromedp/chromedp v0.11.2/go.mod h1:lr8dFRLKsdTTWb75C/Ttol2vnBKOSnt0BW8R9Xaupi8=
|
||||
github.com/chromedp/sysutil v1.1.0 h1:PUFNv5EcprjqXZD9nJb9b/c9ibAbxiYo4exNWZyipwM=
|
||||
github.com/chromedp/sysutil v1.1.0/go.mod h1:WiThHUdltqCNKGc4gaU50XgYjwjYIhKWoHGPTUfWTJ8=
|
||||
github.com/coreos/go-oidc/v3 v3.12.0 h1:sJk+8G2qq94rDI6ehZ71Bol3oUHy63qNYmkiSjrc/Jo=
|
||||
@@ -83,8 +83,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo=
|
||||
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
|
||||
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
|
||||
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
@@ -106,9 +106,8 @@ github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWN
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
||||
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
|
||||
@@ -20,6 +20,7 @@ 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"
|
||||
clientauthenticationv1 "k8s.io/client-go/pkg/apis/clientauthentication/v1"
|
||||
clientauthenticationv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1"
|
||||
)
|
||||
|
||||
// Run the integration tests of the credential plugin use-case.
|
||||
@@ -50,6 +50,7 @@ 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{
|
||||
@@ -76,6 +77,7 @@ 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{
|
||||
@@ -106,6 +108,7 @@ 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{})
|
||||
@@ -200,6 +203,7 @@ 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{
|
||||
@@ -225,6 +229,7 @@ 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{
|
||||
@@ -252,6 +257,7 @@ 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{
|
||||
@@ -278,6 +284,7 @@ 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{
|
||||
@@ -307,6 +314,7 @@ 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{
|
||||
@@ -333,6 +341,7 @@ 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{
|
||||
@@ -359,6 +368,7 @@ 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{
|
||||
@@ -391,6 +401,7 @@ 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{
|
||||
@@ -435,14 +446,11 @@ 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",
|
||||
@@ -453,22 +461,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 clientauthenticationv1.ExecCredential
|
||||
var got clientauthenticationv1beta1.ExecCredential
|
||||
if err := json.NewDecoder(stdout).Decode(&got); err != nil {
|
||||
t.Errorf("could not decode json of the credential plugin: %s", err)
|
||||
return
|
||||
}
|
||||
want := clientauthenticationv1.ExecCredential{
|
||||
want := clientauthenticationv1beta1.ExecCredential{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "client.authentication.k8s.io/v1",
|
||||
APIVersion: "client.authentication.k8s.io/v1beta1",
|
||||
Kind: "ExecCredential",
|
||||
},
|
||||
Status: &clientauthenticationv1.ExecCredentialStatus{
|
||||
Status: &clientauthenticationv1beta1.ExecCredentialStatus{
|
||||
Token: token,
|
||||
ExpirationTimestamp: &metav1.Time{Time: expiry},
|
||||
},
|
||||
}
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
t.Errorf("stdout mismatch (-want +got):\n%s", diff)
|
||||
t.Errorf("kubeconfig mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.51.1. DO NOT EDIT.
|
||||
// Code generated by mockery v2.51.0. DO NOT EDIT.
|
||||
|
||||
package service_mock
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.51.1. DO NOT EDIT.
|
||||
// Code generated by mockery v2.51.0. DO NOT EDIT.
|
||||
|
||||
package service_mock
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.51.1. DO NOT EDIT.
|
||||
// Code generated by mockery v2.51.0. DO NOT EDIT.
|
||||
|
||||
package cmd_mock
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.51.1. DO NOT EDIT.
|
||||
// Code generated by mockery v2.51.0. DO NOT EDIT.
|
||||
|
||||
package reader_mock
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.51.1. DO NOT EDIT.
|
||||
// Code generated by mockery v2.51.0. DO NOT EDIT.
|
||||
|
||||
package writer_mock
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.51.1. DO NOT EDIT.
|
||||
// Code generated by mockery v2.51.0. DO NOT EDIT.
|
||||
|
||||
package browser_mock
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.51.1. DO NOT EDIT.
|
||||
// Code generated by mockery v2.51.0. DO NOT EDIT.
|
||||
|
||||
package clock_mock
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.51.1. DO NOT EDIT.
|
||||
// Code generated by mockery v2.51.0. DO NOT EDIT.
|
||||
|
||||
package logger_mock
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.51.1. DO NOT EDIT.
|
||||
// Code generated by mockery v2.51.0. DO NOT EDIT.
|
||||
|
||||
package logger_mock
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.51.1. DO NOT EDIT.
|
||||
// Code generated by mockery v2.51.0. DO NOT EDIT.
|
||||
|
||||
package logger_mock
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.51.1. DO NOT EDIT.
|
||||
// Code generated by mockery v2.51.0. DO NOT EDIT.
|
||||
|
||||
package reader_mock
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.51.1. DO NOT EDIT.
|
||||
// Code generated by mockery v2.51.0. DO NOT EDIT.
|
||||
|
||||
package stdio_mock
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.51.1. DO NOT EDIT.
|
||||
// Code generated by mockery v2.51.0. DO NOT EDIT.
|
||||
|
||||
package stdio_mock
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.51.1. DO NOT EDIT.
|
||||
// Code generated by mockery v2.51.0. DO NOT EDIT.
|
||||
|
||||
package jwt_mock
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.51.1. DO NOT EDIT.
|
||||
// Code generated by mockery v2.51.0. DO NOT EDIT.
|
||||
|
||||
package loader_mock
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.51.1. DO NOT EDIT.
|
||||
// Code generated by mockery v2.51.0. DO NOT EDIT.
|
||||
|
||||
package writer_mock
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.51.1. DO NOT EDIT.
|
||||
// Code generated by mockery v2.51.0. DO NOT EDIT.
|
||||
|
||||
package client_mock
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.51.1. DO NOT EDIT.
|
||||
// Code generated by mockery v2.51.0. DO NOT EDIT.
|
||||
|
||||
package client_mock
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.51.1. DO NOT EDIT.
|
||||
// Code generated by mockery v2.51.0. DO NOT EDIT.
|
||||
|
||||
package logger_mock
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.51.1. DO NOT EDIT.
|
||||
// Code generated by mockery v2.51.0. DO NOT EDIT.
|
||||
|
||||
package loader_mock
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.51.1. DO NOT EDIT.
|
||||
// Code generated by mockery v2.51.0. DO NOT EDIT.
|
||||
|
||||
package repository_mock
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.51.1. DO NOT EDIT.
|
||||
// Code generated by mockery v2.51.0. DO NOT EDIT.
|
||||
|
||||
package authentication_mock
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.51.1. DO NOT EDIT.
|
||||
// Code generated by mockery v2.51.0. DO NOT EDIT.
|
||||
|
||||
package clean_mock
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.51.1. DO NOT EDIT.
|
||||
// Code generated by mockery v2.51.0. DO NOT EDIT.
|
||||
|
||||
package credentialplugin_mock
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.51.1. DO NOT EDIT.
|
||||
// Code generated by mockery v2.51.0. DO NOT EDIT.
|
||||
|
||||
package setup_mock
|
||||
|
||||
@@ -22,16 +22,48 @@ func (_m *MockInterface) EXPECT() *MockInterface_Expecter {
|
||||
return &MockInterface_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// Do provides a mock function with given fields: ctx, in
|
||||
func (_m *MockInterface) Do(ctx context.Context, in setup.Input) error {
|
||||
// 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 {
|
||||
ret := _m.Called(ctx, in)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Do")
|
||||
panic("no return value specified for DoStage2")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, setup.Input) error); ok {
|
||||
if rf, ok := ret.Get(0).(func(context.Context, setup.Stage2Input) error); ok {
|
||||
r0 = rf(ctx, in)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
@@ -40,31 +72,31 @@ func (_m *MockInterface) Do(ctx context.Context, in setup.Input) error {
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockInterface_Do_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Do'
|
||||
type MockInterface_Do_Call struct {
|
||||
// MockInterface_DoStage2_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DoStage2'
|
||||
type MockInterface_DoStage2_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Do is a helper method to define mock.On call
|
||||
// DoStage2 is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - 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)}
|
||||
// - 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)}
|
||||
}
|
||||
|
||||
func (_c *MockInterface_Do_Call) Run(run func(ctx context.Context, in setup.Input)) *MockInterface_Do_Call {
|
||||
func (_c *MockInterface_DoStage2_Call) Run(run func(ctx context.Context, in setup.Stage2Input)) *MockInterface_DoStage2_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(setup.Input))
|
||||
run(args[0].(context.Context), args[1].(setup.Stage2Input))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockInterface_Do_Call) Return(_a0 error) *MockInterface_Do_Call {
|
||||
func (_c *MockInterface_DoStage2_Call) Return(_a0 error) *MockInterface_DoStage2_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockInterface_Do_Call) RunAndReturn(run func(context.Context, setup.Input) error) *MockInterface_Do_Call {
|
||||
func (_c *MockInterface_DoStage2_Call) RunAndReturn(run func(context.Context, setup.Stage2Input) error) *MockInterface_DoStage2_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.51.1. DO NOT EDIT.
|
||||
// Code generated by mockery v2.51.0. DO NOT EDIT.
|
||||
|
||||
package standalone_mock
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.51.1. DO NOT EDIT.
|
||||
// Code generated by mockery v2.51.0. DO NOT EDIT.
|
||||
|
||||
package io_mock
|
||||
|
||||
|
||||
@@ -9,11 +9,15 @@ import (
|
||||
)
|
||||
|
||||
type cleanOptions struct {
|
||||
TokenCacheDir string
|
||||
tokenCacheOptions tokenCacheOptions
|
||||
}
|
||||
|
||||
func (o *cleanOptions) addFlags(f *pflag.FlagSet) {
|
||||
f.StringVar(&o.TokenCacheDir, "token-cache-dir", getDefaultTokenCacheDir(), "Path to a directory of the token cache")
|
||||
o.tokenCacheOptions.addFlags(f)
|
||||
}
|
||||
|
||||
func (o *cleanOptions) expandHomedir() {
|
||||
o.tokenCacheOptions.expandHomedir()
|
||||
}
|
||||
|
||||
type Clean struct {
|
||||
@@ -27,13 +31,18 @@ func (cmd *Clean) New() *cobra.Command {
|
||||
Short: "Delete the token cache",
|
||||
Long: `Delete the token cache.
|
||||
|
||||
This deletes the token cache directory from both the file system and the keyring.
|
||||
This deletes both the OS keyring and the directory by default.
|
||||
If you encounter an error of keyring, try --token-cache-storage=disk.
|
||||
`,
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(c *cobra.Command, _ []string) error {
|
||||
o.TokenCacheDir = expandHomedir(o.TokenCacheDir)
|
||||
o.expandHomedir()
|
||||
tokenCacheConfig, err := o.tokenCacheOptions.tokenCacheConfig()
|
||||
if err != nil {
|
||||
return fmt.Errorf("clean: %w", err)
|
||||
}
|
||||
in := clean.Input{
|
||||
TokenCacheDir: o.TokenCacheDir,
|
||||
TokenCacheConfig: tokenCacheConfig,
|
||||
}
|
||||
if err := cmd.Clean.Do(c.Context(), in); err != nil {
|
||||
return fmt.Errorf("clean: %w", err)
|
||||
|
||||
@@ -8,7 +8,6 @@ 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"
|
||||
@@ -17,7 +16,6 @@ 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"
|
||||
)
|
||||
|
||||
@@ -25,14 +23,6 @@ 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
|
||||
@@ -41,7 +31,13 @@ func TestCmd_Run(t *testing.T) {
|
||||
"Defaults": {
|
||||
args: []string{executable},
|
||||
in: standalone.Input{
|
||||
GrantOptionSet: defaultGrantOptionSet,
|
||||
GrantOptionSet: authentication.GrantOptionSet{
|
||||
AuthCodeBrowserOption: &authcode.BrowserOption{
|
||||
BindAddress: defaultListenAddress,
|
||||
AuthenticationTimeout: defaultAuthenticationTimeoutSec * time.Second,
|
||||
RedirectURLHostname: "localhost",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"FullOptions": {
|
||||
@@ -55,7 +51,13 @@ func TestCmd_Run(t *testing.T) {
|
||||
KubeconfigFilename: "/path/to/kubeconfig",
|
||||
KubeconfigContext: "hello.k8s.local",
|
||||
KubeconfigUser: "google",
|
||||
GrantOptionSet: defaultGrantOptionSet,
|
||||
GrantOptionSet: authentication.GrantOptionSet{
|
||||
AuthCodeBrowserOption: &authcode.BrowserOption{
|
||||
BindAddress: defaultListenAddress,
|
||||
AuthenticationTimeout: defaultAuthenticationTimeoutSec * time.Second,
|
||||
RedirectURLHostname: "localhost",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -118,8 +120,15 @@ 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": {
|
||||
@@ -130,7 +139,7 @@ func TestCmd_Run(t *testing.T) {
|
||||
"--oidc-client-secret", "YOUR_CLIENT_SECRET",
|
||||
"--oidc-extra-scope", "email",
|
||||
"--oidc-extra-scope", "profile",
|
||||
"--token-cache-storage", "keyring",
|
||||
"--token-cache-storage", "disk",
|
||||
"-v1",
|
||||
},
|
||||
in: credentialplugin.Input{
|
||||
@@ -142,9 +151,15 @@ func TestCmd_Run(t *testing.T) {
|
||||
},
|
||||
TokenCacheConfig: tokencache.Config{
|
||||
Directory: filepath.Join(userHomeDir, ".kube/cache/oidc-login"),
|
||||
Storage: tokencache.StorageKeyring,
|
||||
Storage: tokencache.StorageDisk,
|
||||
},
|
||||
GrantOptionSet: authentication.GrantOptionSet{
|
||||
AuthCodeBrowserOption: &authcode.BrowserOption{
|
||||
BindAddress: defaultListenAddress,
|
||||
AuthenticationTimeout: defaultAuthenticationTimeoutSec * time.Second,
|
||||
RedirectURLHostname: "localhost",
|
||||
},
|
||||
},
|
||||
GrantOptionSet: defaultGrantOptionSet,
|
||||
},
|
||||
},
|
||||
"AccessToken": {
|
||||
@@ -162,8 +177,15 @@ 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": {
|
||||
@@ -183,6 +205,7 @@ 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{
|
||||
@@ -259,54 +282,4 @@ 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,8 +3,6 @@ package cmd
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
_ "embed"
|
||||
|
||||
"github.com/int128/kubelogin/pkg/usecases/setup"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
@@ -37,31 +35,13 @@ 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)
|
||||
@@ -70,7 +50,7 @@ func (cmd *Setup) New() *cobra.Command {
|
||||
if err != nil {
|
||||
return fmt.Errorf("setup: %w", err)
|
||||
}
|
||||
in := setup.Input{
|
||||
in := setup.Stage2Input{
|
||||
IssuerURL: o.IssuerURL,
|
||||
ClientID: o.ClientID,
|
||||
ClientSecret: o.ClientSecret,
|
||||
@@ -79,12 +59,18 @@ func (cmd *Setup) New() *cobra.Command {
|
||||
PKCEMethod: pkceMethod,
|
||||
GrantOptionSet: grantOptionSet,
|
||||
TLSClientConfig: o.tlsOptions.tlsClientConfig(),
|
||||
ChangedFlags: changedFlags,
|
||||
}
|
||||
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
|
||||
}
|
||||
if in.IssuerURL == "" || in.ClientID == "" {
|
||||
return c.Help()
|
||||
cmd.Setup.DoStage1()
|
||||
return nil
|
||||
}
|
||||
if err := cmd.Setup.Do(c.Context(), in); err != nil {
|
||||
if err := cmd.Setup.DoStage2(c.Context(), in); err != nil {
|
||||
return fmt.Errorf("setup: %w", err)
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
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{"disk", "keyring"}, "|")
|
||||
var allTokenCacheStorage = strings.Join([]string{"auto", "keyring", "disk"}, "|")
|
||||
|
||||
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", "disk", fmt.Sprintf("Storage for the token cache. One of (%s)", allTokenCacheStorage))
|
||||
f.StringVar(&o.TokenCacheStorage, "token-cache-storage", "auto", fmt.Sprintf("Storage for the token cache. One of (%s)", allTokenCacheStorage))
|
||||
}
|
||||
|
||||
func (o *tokenCacheOptions) expandHomedir() {
|
||||
@@ -39,10 +39,12 @@ func (o *tokenCacheOptions) tokenCacheConfig() (tokencache.Config, error) {
|
||||
Directory: o.TokenCacheDir,
|
||||
}
|
||||
switch o.TokenCacheStorage {
|
||||
case "disk":
|
||||
config.Storage = tokencache.StorageDisk
|
||||
case "auto":
|
||||
config.Storage = tokencache.StorageAuto
|
||||
case "keyring":
|
||||
config.Storage = tokencache.StorageKeyring
|
||||
case "disk":
|
||||
config.Storage = tokencache.StorageDisk
|
||||
default:
|
||||
return tokencache.Config{}, fmt.Errorf("token-cache-storage must be one of (%s)", allTokenCacheStorage)
|
||||
}
|
||||
|
||||
@@ -97,7 +97,9 @@ func NewCmdForHeadless(clockInterface clock.Interface, stdin stdio.Stdin, stdout
|
||||
Standalone: standaloneStandalone,
|
||||
Logger: loggerInterface,
|
||||
}
|
||||
repositoryRepository := &repository.Repository{}
|
||||
repositoryRepository := &repository.Repository{
|
||||
Logger: loggerInterface,
|
||||
}
|
||||
reader3 := &reader2.Reader{}
|
||||
writer3 := &writer2.Writer{
|
||||
Stdout: stdout,
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/gob"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
@@ -12,6 +13,7 @@ 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"
|
||||
@@ -37,7 +39,9 @@ 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{}
|
||||
type Repository struct {
|
||||
Logger logger.Interface
|
||||
}
|
||||
|
||||
// keyringService is used to namespace the keyring access.
|
||||
// Some implementations may also display this string when prompting the user
|
||||
@@ -53,6 +57,16 @@ 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:
|
||||
@@ -106,6 +120,14 @@ 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:
|
||||
@@ -163,20 +185,36 @@ func (r *Repository) Lock(config tokencache.Config, key tokencache.Key) (io.Clos
|
||||
}
|
||||
|
||||
func (r *Repository) DeleteAll(config tokencache.Config) error {
|
||||
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)
|
||||
}
|
||||
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
|
||||
}
|
||||
}(),
|
||||
)
|
||||
}
|
||||
|
||||
func encodeKey(tokenSet oidc.TokenSet) ([]byte, error) {
|
||||
|
||||
@@ -25,8 +25,10 @@ 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 Storage = iota
|
||||
StorageDisk
|
||||
// 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 {
|
||||
TokenCacheDir string
|
||||
TokenCacheConfig tokencache.Config
|
||||
}
|
||||
|
||||
type Clean struct {
|
||||
@@ -31,17 +31,8 @@ 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(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")
|
||||
if err := u.TokenCacheRepository.DeleteAll(in.TokenCacheConfig); err != nil {
|
||||
return fmt.Errorf("delete the token cache: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3,17 +3,9 @@ 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"
|
||||
)
|
||||
|
||||
@@ -23,62 +15,11 @@ var Set = wire.NewSet(
|
||||
)
|
||||
|
||||
type Interface interface {
|
||||
Do(ctx context.Context, in Input) error
|
||||
DoStage1()
|
||||
DoStage2(ctx context.Context, in Stage2Input) 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
|
||||
}
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
## 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 }}
|
||||
```
|
||||
@@ -1,66 +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"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
28
pkg/usecases/setup/stage1.go
Normal file
28
pkg/usecases/setup/stage1.go
Normal file
@@ -0,0 +1,28 @@
|
||||
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)
|
||||
}
|
||||
178
pkg/usecases/setup/stage2.go
Normal file
178
pkg/usecases/setup/stage2.go
Normal file
@@ -0,0 +1,178 @@
|
||||
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
|
||||
}
|
||||
111
pkg/usecases/setup/stage2_test.go
Normal file
111
pkg/usecases/setup/stage2_test.go
Normal file
@@ -0,0 +1,111 @@
|
||||
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,11 +2,12 @@
|
||||
|
||||
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 the [setup guide](../docs/setup.md).
|
||||
1. User can set up Kubernetes OIDC authentication using [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.
|
||||
|
||||
@@ -17,6 +18,7 @@ It depends on the following components:
|
||||
- Browser (Chrome)
|
||||
- kubectl command
|
||||
|
||||
|
||||
## How it works
|
||||
|
||||
Let's take a look at the diagram.
|
||||
@@ -43,6 +45,7 @@ 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:
|
||||
@@ -77,6 +80,7 @@ make terminate
|
||||
make clean
|
||||
```
|
||||
|
||||
|
||||
## Technical consideration
|
||||
|
||||
### Network and DNS
|
||||
|
||||
@@ -8,16 +8,13 @@ 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,18 +2,15 @@ CERT_DIR := ../cert
|
||||
|
||||
.PHONY: dex
|
||||
dex: dex.yaml
|
||||
# Wait for kind network.
|
||||
until docker network inspect kind; do sleep 1; done
|
||||
|
||||
# Create a container.
|
||||
# wait for kind network
|
||||
while true; do if docker network inspect kind; then break; fi; 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,7 +29,6 @@ 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
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ 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.1
|
||||
github.com/vektra/mockery/v2 v2.51.0
|
||||
)
|
||||
|
||||
require (
|
||||
|
||||
@@ -580,8 +580,8 @@ github.com/uudashr/gocognit v1.2.0 h1:3BU9aMr1xbhPlvJLSydKwdLN3tEUUrzPSSM8S4hDYR
|
||||
github.com/uudashr/gocognit v1.2.0/go.mod h1:k/DdKPI6XBZO1q7HgoV2juESI2/Ofj9AcHPZhBBdrTU=
|
||||
github.com/uudashr/iface v1.3.0 h1:zwPch0fs9tdh9BmL5kcgSpvnObV+yHjO4JjVBl8IA10=
|
||||
github.com/uudashr/iface v1.3.0/go.mod h1:4QvspiRd3JLPAEXBQ9AiZpLbJlrWWgRChOKDJEuQTdg=
|
||||
github.com/vektra/mockery/v2 v2.51.1 h1:BiiUSotsS7B56xvTjlIY2VDZxiEY9rzQ+ev69jE/mtw=
|
||||
github.com/vektra/mockery/v2 v2.51.1/go.mod h1:xO2DeYemEPC2tCzIZ+a1tifZ/7Laf/Chxg3vlc+oDsI=
|
||||
github.com/vektra/mockery/v2 v2.51.0 h1:pAPiE1xyZ/2y6VTr8dRAJGrbNBKeMtN0aqY12rXzuiQ=
|
||||
github.com/vektra/mockery/v2 v2.51.0/go.mod h1:xO2DeYemEPC2tCzIZ+a1tifZ/7Laf/Chxg3vlc+oDsI=
|
||||
github.com/xen0n/gosmopolitan v1.2.2 h1:/p2KTnMzwRexIW8GlKawsTWOxn7UHA+jCMF/V8HHtvU=
|
||||
github.com/xen0n/gosmopolitan v1.2.2/go.mod h1:7XX7Mj61uLYrj0qmeN0zi7XDon9JRAEhYQqAPLVNTeg=
|
||||
github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM=
|
||||
|
||||
Reference in New Issue
Block a user