Compare commits

..

13 Commits

Author SHA1 Message Date
Hidetake Iwata
fccef52a73 Bump to Go 1.13.3 (#166)
* Bump to Go 1.13.3 and golangci-lint 1.21.0

* go mod tidy
2019-10-22 11:24:46 +09:00
Hidetake Iwata
581284c626 Suppress success log to prevent screen disturbance (#165) 2019-10-19 15:36:47 +09:00
Hidetake Iwata
b5922f9419 Refactor: fix error handling and improve stability (#163)
* Fix ReadPassword() does not respect argument

* Do not ignore error when context has been cancelled

* Use longer timeout to reveal concurrency design failure

* Refactor: use context.TODO in test
2019-10-04 22:28:09 +09:00
Hidetake Iwata
7a0ca206d1 Bump Go 1.13 and dependencies (#162)
* Bump Go 1.13 and dependencies

* Fix lint error
2019-10-04 21:26:26 +09:00
dependabot-preview[bot]
0bca9ef54b Bump gopkg.in/yaml.v2 from 2.2.2 to 2.2.4 (#161)
Bumps [gopkg.in/yaml.v2](https://github.com/go-yaml/yaml) from 2.2.2 to 2.2.4.
- [Release notes](https://github.com/go-yaml/yaml/releases)
- [Commits](https://github.com/go-yaml/yaml/compare/v2.2.2...v2.2.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-04 21:08:52 +09:00
dependabot-preview[bot]
2fb551bf1b Bump github.com/int128/oauth2cli from 1.6.0 to 1.7.0 (#160)
Bumps [github.com/int128/oauth2cli](https://github.com/int128/oauth2cli) from 1.6.0 to 1.7.0.
- [Release notes](https://github.com/int128/oauth2cli/releases)
- [Commits](https://github.com/int128/oauth2cli/compare/v1.6.0...v1.7.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-04 10:50:08 +09:00
Hidetake Iwata
0bc117ddc7 Refactor (#158)
* Refactor: template rendering

* Refactor: rename DecodedIDToken fields

* Refactor: expand command options

* Refactor: improve help messages
2019-09-30 18:27:23 +09:00
Hidetake Iwata
8c640f6c73 Add setup command (#157)
* Add setup command

* Refactor: extract IDTokenSubject instead of sub claims
2019-09-29 18:34:59 +09:00
Hidetake Iwata
8a5efac337 Add deprecation message of standalone mode (#155) 2019-09-28 11:03:03 +09:00
dependabot-preview[bot]
d6e0c761ac Bump github.com/int128/oauth2cli from 1.5.0 to 1.6.0 (#153)
Bumps [github.com/int128/oauth2cli](https://github.com/int128/oauth2cli) from 1.5.0 to 1.6.0.
- [Release notes](https://github.com/int128/oauth2cli/releases)
- [Commits](https://github.com/int128/oauth2cli/compare/v1.5.0...v1.6.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-09-26 13:07:01 +09:00
dependabot-preview[bot]
8925226afe Bump github.com/go-test/deep from 1.0.3 to 1.0.4 (#150)
Bumps [github.com/go-test/deep](https://github.com/go-test/deep) from 1.0.3 to 1.0.4.
- [Release notes](https://github.com/go-test/deep/releases)
- [Changelog](https://github.com/go-test/deep/blob/master/CHANGES.md)
- [Commits](https://github.com/go-test/deep/compare/v1.0.3...v1.0.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-09-26 12:32:20 +09:00
dependabot-preview[bot]
89a0f9a79e Bump github.com/spf13/pflag from 1.0.3 to 1.0.5 (#152)
Bumps [github.com/spf13/pflag](https://github.com/spf13/pflag) from 1.0.3 to 1.0.5.
- [Release notes](https://github.com/spf13/pflag/releases)
- [Commits](https://github.com/spf13/pflag/compare/v1.0.3...v1.0.5)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-09-26 12:18:22 +09:00
Hidetake Iwata
74bb4c62c5 Fix expiration of CA certificate for E2E tests (#154) 2019-09-25 23:56:24 +09:00
30 changed files with 842 additions and 421 deletions

View File

@@ -2,7 +2,7 @@ version: 2
jobs:
build:
docker:
- image: circleci/golang:1.12.3
- image: circleci/golang:1.13.3
steps:
- run: |
mkdir -p ~/bin
@@ -14,7 +14,7 @@ jobs:
curl -L -o ~/bin/ghcp https://github.com/int128/ghcp/releases/download/v1.5.0/ghcp_linux_amd64
chmod +x ~/bin/ghcp
- run: |
curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b ~/bin v1.16.0
curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b ~/bin v1.21.0
- run: go get github.com/int128/goxzst
- run: go get github.com/tcnksm/ghr
- checkout

View File

@@ -9,7 +9,7 @@ Then kubelogin gets a token from the provider and kubectl access Kubernetes APIs
## Getting Started
### Setup
### Install
Install the latest release from [Homebrew](https://brew.sh/), [Krew](https://github.com/kubernetes-sigs/krew) or [GitHub Releases](https://github.com/int128/kubelogin/releases) as follows:
@@ -26,30 +26,19 @@ unzip kubelogin_linux_amd64.zip
ln -s kubelogin kubectl-oidc_login
```
You need to configure the OIDC provider, Kubernetes API server and role binding.
### Setup
You need to set up the OIDC provider, role binding, Kubernetes API server and kubeconfig.
See the following documents for more:
- [Getting Started with Keycloak](docs/keycloak.md)
- [Getting Started with dex and GitHub](docs/dex.md)
- [Getting Started with Google Identity Platform](docs/google.md)
- [Getting Started with dex and GitHub](docs/dex.md)
- [Getting Started with Keycloak](docs/keycloak.md)
Configure the kubeconfig to run kubelogin as a [client-go credential plugin](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#client-go-credential-plugins).
It provides transparent login without manually running kubelogin command.
For example,
Run the following command to show the setup instruction.
```yaml
users:
- name: keycloak
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
command: kubectl
args:
- oidc-login
- get-token
- --oidc-issuer-url=https://issuer.example.com
- --oidc-client-id=YOUR_CLIENT_ID
- --oidc-client-secret=YOUR_CLIENT_SECRET
```sh
kubectl oidc-login setup
```
### Run
@@ -84,11 +73,6 @@ If the refresh token has expired, kubelogin will perform reauthentication.
You can log out by removing the token cache directory (default `~/.kube/cache/oidc-login`).
Kubelogin will perform authentication if the token cache file does not exist.
### Standalone mode
As well as you can update the ID token in the kubeconfig by running the command.
See [standalone mode](docs/standalone-mode.md) for more.
## Usage
@@ -98,21 +82,21 @@ If you are looking for a specific version, see [the release tags](https://github
Kubelogin supports the following options:
```
% kubelogin get-token -h
% kubectl oidc-login get-token -h
Run as a kubectl credential plugin
Usage:
kubelogin get-token [flags]
Flags:
--listen-port ints Port to bind to the local server. If multiple ports are given, it will try the ports in order (default [8000,18000])
--skip-open-browser If true, it does not open the browser on authentication
--username string If set, perform the resource owner password credentials grant
--password string If set, use the password instead of asking it
--oidc-issuer-url string Issuer URL of the provider (mandatory)
--oidc-client-id string Client ID of the provider (mandatory)
--oidc-client-secret string Client secret of the provider
--oidc-extra-scope strings Scopes to request to the provider
--listen-port ints Port to bind to the local server. If multiple ports are given, it will try the ports in order (default [8000,18000])
--skip-open-browser If true, it does not open the browser on authentication
--username string If set, perform the resource owner password credentials grant
--password string If set, use the password instead of asking it
--certificate-authority string Path to a cert file for the certificate authority
--insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure
--token-cache-dir string Path to a directory for caching tokens (default "~/.kube/cache/oidc-login")
@@ -133,7 +117,7 @@ Global Flags:
--vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging
```
See also the options in [standalone mode](docs/standalone-mode.md).
See also the options of [standalone mode](docs/standalone-mode.md).
### Extra scopes

View File

@@ -1,12 +1,14 @@
# Getting Started with dex and GitHub
## Prerequisite
Prerequisite:
- You have a GitHub account.
- You have an administrator role of the Kubernetes cluster.
- You can configure the Kubernetes API server.
- `kubectl` and `kubelogin` are installed.
## 1. Setup GitHub OAuth
## 1. Set up the OpenID Connect Provider
Open [GitHub OAuth Apps](https://github.com/settings/developers) and create an application with the following setting:
@@ -14,9 +16,7 @@ Open [GitHub OAuth Apps](https://github.com/settings/developers) and create an a
- Homepage URL: `https://dex.example.com`
- Authorization callback URL: `https://dex.example.com/callback`
## 2. Setup dex
Configure the dex with the following config:
Deploy the [dex](https://github.com/dexidp/dex) with the following config:
```yaml
issuer: https://dex.example.com
@@ -29,89 +29,79 @@ connectors:
clientSecret: YOUR_GITHUB_CLIENT_SECRET
redirectURI: https://dex.example.com/callback
staticClients:
- id: kubernetes
- id: YOUR_CLIENT_ID
name: Kubernetes
redirectURIs:
- http://localhost:8000
- http://localhost:18000
- http://localhost:8000
- http://localhost:18000
secret: YOUR_DEX_CLIENT_SECRET
```
Now test authentication with the dex.
## 2. Verify authentication
Run the following command:
```sh
kubectl oidc-login get-token -v1 \
kubectl oidc-login setup \
--oidc-issuer-url=https://dex.example.com \
--oidc-client-id=kubernetes \
--oidc-client-secret=YOUR_DEX_CLIENT_SECRET
--oidc-client-id=YOUR_CLIENT_ID \
--oidc-client-secret=YOUR_CLIENT_SECRET
```
You should get claims like:
It will open the browser and you can log in to the provider.
```
I0827 12:29:03.086531 23722 get_token.go:59] the ID token has the claim: aud=kubernetes
I0827 12:29:03.086553 23722 get_token.go:59] the ID token has the claim: iss=https://dex.example.com
I0827 12:29:03.086561 23722 get_token.go:59] the ID token has the claim: sub=YOUR_SUBJECT
```
## 3. Setup Kubernetes API server
## 3. Bind a role
Configure your Kubernetes API server accepts [OpenID Connect Tokens](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#openid-connect-tokens).
```
--oidc-issuer-url=https://dex.example.com
--oidc-client-id=kubernetes
```
If you are using [kops](https://github.com/kubernetes/kops), run `kops edit cluster` and add the following spec:
```yaml
spec:
kubeAPIServer:
oidcIssuerURL: https://dex.example.com
oidcClientID: kubernetes
```
## 4. Create a role binding
Here assign the `cluster-admin` role to your subject.
Bind the `cluster-admin` role to you.
Apply the following manifest:
```yaml
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: keycloak-admin-group
name: oidc-admin-group
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: User
name: YOUR_SUBJECT
name: https://dex.example.com#YOUR_SUBJECT
```
You can create a custom role and assign it as well.
As well as you can create a custom role and bind it.
## 5. Setup kubeconfig
Configure the kubeconfig like:
## 4. Set up the Kubernetes API server
Add the following options to the kube-apiserver:
```
--oidc-issuer-url=https://dex.example.com
--oidc-client-id=YOUR_CLIENT_ID
```
See [OpenID Connect Tokens](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#openid-connect-tokens) for details.
If you are using [kops](https://github.com/kubernetes/kops), run `kops edit cluster` and append the following settings:
```yaml
spec:
kubeAPIServer:
oidcIssuerURL: https://dex.example.com
oidcClientID: YOUR_CLIENT_ID
```
## 5. Set up the kubeconfig
Add the following user to the kubeconfig:
```yaml
apiVersion: v1
clusters:
- cluster:
server: https://api.example.com
name: example.k8s.local
contexts:
- context:
cluster: example.k8s.local
user: dex
name: dex@example.k8s.local
current-context: dex@example.k8s.local
kind: Config
preferences: {}
users:
- name: dex
- name: google
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
@@ -120,13 +110,14 @@ users:
- oidc-login
- get-token
- --oidc-issuer-url=https://dex.example.com
- --oidc-client-id=kubernetes
- --oidc-client-secret=YOUR_DEX_CLIENT_SECRET
- --oidc-client-id=YOUR_CLIENT_ID
- --oidc-client-secret=YOUR_CLIENT_SECRET
```
You can share the kubeconfig to your team members for on-boarding.
## 6. Run kubectl
## 6. Verify cluster access
Make sure you can access the Kubernetes cluster.
@@ -134,7 +125,6 @@ Make sure you can access the Kubernetes cluster.
% kubectl get nodes
Open http://localhost:8000 for authentication
You got a valid token until 2019-05-16 22:03:13 +0900 JST
Updated ~/.kubeconfig
NAME STATUS ROLES AGE VERSION
ip-1-2-3-4.us-west-2.compute.internal Ready node 21d v1.9.6
ip-1-2-3-5.us-west-2.compute.internal Ready node 20d v1.9.6

View File

@@ -1,56 +1,38 @@
# Getting Started with Google Identity Platform
## Prerequisite
Prerequisite:
- You have a Google account.
- You have the Cluster Admin role of the Kubernetes cluster.
- You have an administrator role of the Kubernetes cluster.
- You can configure the Kubernetes API server.
- `kubectl` and `kubelogin` are installed to your computer.
## 1. Setup Google API
## 1. Set up the OpenID Connect Provider
Open [Google APIs Console](https://console.developers.google.com/apis/credentials) and create an OAuth client with the following setting:
- Application Type: Other
Now test authentication with Google Identity Platform.
## 2. Verify authentication
Run the following command:
```sh
kubectl oidc-login get-token -v1 \
kubectl oidc-login setup \
--oidc-issuer-url=https://accounts.google.com \
--oidc-client-id=YOUR_CLIENT_ID.apps.googleusercontent.com \
--oidc-client-id=YOUR_CLIENT_ID \
--oidc-client-secret=YOUR_CLIENT_SECRET
```
You should get claims like:
It will open the browser and you can log in to the provider.
```
I0827 12:29:03.086531 23722 get_token.go:59] the ID token has the claim: aud=YOUR_CLIENT_ID.apps.googleusercontent.com
I0827 12:29:03.086553 23722 get_token.go:59] the ID token has the claim: iss=https://accounts.google.com
I0827 12:29:03.086561 23722 get_token.go:59] the ID token has the claim: sub=YOUR_SUBJECT
```
## 2. Setup Kubernetes API server
## 3. Bind a role
Configure your Kubernetes API Server accepts [OpenID Connect Tokens](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#openid-connect-tokens).
```
--oidc-issuer-url=https://accounts.google.com
--oidc-client-id=YOUR_CLIENT_ID.apps.googleusercontent.com
```
If you are using [kops](https://github.com/kubernetes/kops), run `kops edit cluster` and append the following settings:
```yaml
spec:
kubeAPIServer:
oidcIssuerURL: https://accounts.google.com
oidcClientID: YOUR_CLIENT_ID.apps.googleusercontent.com
```
## 3. Setup Kubernetes cluster
Here assign the `cluster-admin` role to your subject.
Bind the `cluster-admin` role to you.
Apply the following manifest:
```yaml
kind: ClusterRoleBinding
@@ -63,45 +45,56 @@ roleRef:
name: cluster-admin
subjects:
- kind: User
name: YOUR_SUBJECT
name: https://accounts.google.com#YOUR_SUBJECT
```
You can create a custom role and assign it as well.
As well as you can create a custom role and bind it.
## 4. Setup kubeconfig
Configure the kubeconfig like:
## 4. Set up the Kubernetes API server
Add the following options to the kube-apiserver:
```
--oidc-issuer-url=https://accounts.google.com
--oidc-client-id=YOUR_CLIENT_ID
```
See [OpenID Connect Tokens](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#openid-connect-tokens) for details.
If you are using [kops](https://github.com/kubernetes/kops), run `kops edit cluster` and append the following settings:
```yaml
spec:
kubeAPIServer:
oidcIssuerURL: https://accounts.google.com
oidcClientID: YOUR_CLIENT_ID
```
## 5. Set up the kubeconfig
Add the following user to the kubeconfig:
```yaml
apiVersion: v1
clusters:
- cluster:
server: https://api.example.com
name: example.k8s.local
contexts:
- context:
cluster: example.k8s.local
user: google
name: google@example.k8s.local
current-context: google@example.k8s.local
kind: Config
preferences: {}
users:
- name: google
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
command: kubelogin
command: kubectl
args:
- oidc-login
- get-token
- --oidc-issuer-url=https://accounts.google.com
- --oidc-client-id=YOUR_CLIENT_ID.apps.googleusercontent.com
- --oidc-client-id=YOUR_CLIENT_ID
- --oidc-client-secret=YOUR_CLIENT_SECRET
```
You can share the kubeconfig to your team members for on-boarding.
## 5. Run kubectl
## 6. Verify cluster access
Make sure you can access the Kubernetes cluster.
@@ -109,7 +102,6 @@ Make sure you can access the Kubernetes cluster.
% kubectl get nodes
Open http://localhost:8000 for authentication
You got a valid token until 2019-05-16 22:03:13 +0900 JST
Updated ~/.kubeconfig
NAME STATUS ROLES AGE VERSION
ip-1-2-3-4.us-west-2.compute.internal Ready node 21d v1.9.6
ip-1-2-3-5.us-west-2.compute.internal Ready node 20d v1.9.6

View File

@@ -1,17 +1,18 @@
# Getting Started with Keycloak
## Prerequisite
Prerequisite:
- You have an administrator role of the Keycloak realm.
- You have an administrator role of the Kubernetes cluster.
- You can configure the Kubernetes API server.
- `kubectl` and `kubelogin` are installed.
## 1. Setup Keycloak
## 1. Set up the OpenID Connect Provider
Open the Keycloak and create an OIDC client as follows:
- Client ID: `kubernetes`
- Client ID: `YOUR_CLIENT_ID`
- Valid Redirect URLs:
- `http://localhost:8000`
- `http://localhost:18000` (used if the port 8000 is already in use)
@@ -21,105 +22,94 @@ You can associate client roles by adding the following mapper:
- Name: `groups`
- Mapper Type: `User Client Role`
- Client ID: `kubernetes`
- Client ID: `YOUR_CLIENT_ID`
- Client Role prefix: `kubernetes:`
- Token Claim Name: `groups`
- Add to ID token: on
For example, if you have the `admin` role of the client, you will get a JWT with the claim `{"groups": ["kubernetes:admin"]}`.
Now test authentication with the Keycloak.
## 2. Verify authentication
Run the following command:
```sh
kubectl oidc-login get-token -v1 \
kubectl oidc-login setup \
--oidc-issuer-url=https://keycloak.example.com/auth/realms/YOUR_REALM \
--oidc-client-id=kubernetes \
--oidc-client-id=YOUR_CLIENT_ID \
--oidc-client-secret=YOUR_CLIENT_SECRET
```
You should get claims like:
It will open the browser and you can log in to the provider.
```
I0827 12:29:03.086476 23722 get_token.go:59] the ID token has the claim: groups=[kubernetes:admin]
I0827 12:29:03.086531 23722 get_token.go:59] the ID token has the claim: aud=kubernetes
I0827 12:29:03.086553 23722 get_token.go:59] the ID token has the claim: iss=https://keycloak.example.com/auth/realms/YOUR_REALM
I0827 12:29:03.086561 23722 get_token.go:59] the ID token has the claim: sub=f08655e2-901f-48e5-8c64-bb9f7784d5df
```
## 2. Setup Kubernetes API server
## 3. Bind a role
Configure your Kubernetes API server accepts [OpenID Connect Tokens](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#openid-connect-tokens).
```
--oidc-issuer-url=https://keycloak.example.com/auth/realms/YOUR_REALM
--oidc-client-id=kubernetes
--oidc-groups-claim=groups
```
If you are using [kops](https://github.com/kubernetes/kops), run `kops edit cluster` and add the following spec:
```yaml
spec:
kubeAPIServer:
oidcIssuerURL: https://keycloak.example.com/auth/realms/YOUR_REALM
oidcClientID: kubernetes
oidcGroupsClaim: groups
```
## 3. Setup Kubernetes cluster
Here assign the `cluster-admin` role to the `kubernetes:admin` group.
Bind the `cluster-admin` role to you.
Apply the following manifest:
```yaml
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: keycloak-admin-group
name: oidc-admin-group
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: Group
name: kubernetes:admin
- kind: User
name: https://keycloak.example.com/auth/realms/YOUR_REALM#YOUR_SUBJECT
```
You can create a custom role and assign it as well.
As well as you can create a custom role and bind it.
## 4. Setup kubeconfig
Configure the kubeconfig like:
## 4. Set up the Kubernetes API server
Add the following options to the kube-apiserver:
```
--oidc-issuer-url=https://keycloak.example.com/auth/realms/YOUR_REALM
--oidc-client-id=YOUR_CLIENT_ID
```
See [OpenID Connect Tokens](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#openid-connect-tokens) for details.
If you are using [kops](https://github.com/kubernetes/kops), run `kops edit cluster` and append the following settings:
```yaml
spec:
kubeAPIServer:
oidcIssuerURL: https://keycloak.example.com/auth/realms/YOUR_REALM
oidcClientID: YOUR_CLIENT_ID
```
## 5. Set up the kubeconfig
Add the following user to the kubeconfig:
```yaml
apiVersion: v1
clusters:
- cluster:
server: https://api.example.com
name: example.k8s.local
contexts:
- context:
cluster: example.k8s.local
user: keycloak
name: keycloak@example.k8s.local
current-context: keycloak@example.k8s.local
kind: Config
preferences: {}
users:
- name: keycloak
- name: google
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
command: kubelogin
command: kubectl
args:
- oidc-login
- get-token
- --oidc-issuer-url=https://keycloak.example.com/auth/realms/YOUR_REALM
- --oidc-client-id=kubernetes
- --oidc-client-id=YOUR_CLIENT_ID
- --oidc-client-secret=YOUR_CLIENT_SECRET
```
You can share the kubeconfig to your team members for on-boarding.
## 5. Run kubectl
## 6. Verify cluster access
Make sure you can access the Kubernetes cluster.
@@ -127,7 +117,6 @@ Make sure you can access the Kubernetes cluster.
% kubectl get nodes
Open http://localhost:8000 for authentication
You got a valid token until 2019-05-16 22:03:13 +0900 JST
Updated ~/.kubeconfig
NAME STATUS ROLES AGE VERSION
ip-1-2-3-4.us-west-2.compute.internal Ready node 21d v1.9.6
ip-1-2-3-5.us-west-2.compute.internal Ready node 20d v1.9.6

View File

@@ -78,38 +78,36 @@ If the refresh token has expired, kubelogin will proceed the authentication.
Kubelogin supports the following options:
```
% kubelogin -h
Login to the OpenID Connect provider and update the kubeconfig
% kubectl oidc-login -h
Login to the OpenID Connect provider.
You need to set up the OIDC provider, role binding, Kubernetes API server and kubeconfig.
Run the following command to show the setup instruction:
kubectl oidc-login setup
See https://github.com/int128/kubelogin for more.
Usage:
kubelogin [flags]
kubelogin [command]
Examples:
# Login to the provider using the authorization code flow.
kubelogin
# Login to the provider using the resource owner password credentials flow.
kubelogin --username USERNAME --password PASSWORD
# Run as a credential plugin.
kubelogin get-token --oidc-issuer-url=https://issuer.example.com
main [flags]
main [command]
Available Commands:
get-token Run as a kubectl credential plugin
help Help about any command
setup Show the setup instruction
version Print the version information
Flags:
--kubeconfig string Path to the kubeconfig file
--context string The name of the kubeconfig context to use
--user string The name of the kubeconfig user to use. Prior to --context
--certificate-authority string Path to a cert file for the certificate authority
--insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure
--listen-port ints Port to bind to the local server. If multiple ports are given, it will try the ports in order (default [8000,18000])
--skip-open-browser If true, it does not open the browser on authentication
--username string If set, perform the resource owner password credentials grant
--password string If set, use the password instead of asking it
--certificate-authority string Path to a cert file for the certificate authority
--insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure
--add_dir_header If true, adds the file directory to the header
--alsologtostderr log to standard error as well as files
--log_backtrace_at traceLocation when logging hits line file:N, emit a stack trace (default :0)
@@ -122,8 +120,8 @@ Flags:
--stderrthreshold severity logs at or above this threshold go to stderr (default 2)
-v, --v Level number for the log level verbosity
--vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging
-h, --help help for kubelogin
--version version for kubelogin
-h, --help help for main
--version version for main
```
### Kubeconfig

View File

@@ -1,3 +1,5 @@
EXPIRY := 3650
all: ca.key ca.crt server.key server.crt jws.key
.PHONY: clean
@@ -17,7 +19,9 @@ ca.csr: openssl.cnf ca.key
openssl req -noout -text -in $@
ca.crt: ca.csr ca.key
openssl x509 -req \
openssl x509 \
-req \
-days $(EXPIRY) \
-signkey ca.key \
-in ca.csr \
-out $@
@@ -42,6 +46,7 @@ server.crt: openssl.cnf server.csr ca.key ca.crt
touch CA/index.txt.attr
echo 00 > CA/serial
openssl ca -config openssl.cnf \
-days $(EXPIRY) \
-extensions v3_req \
-batch \
-cert ca.crt \

View File

@@ -1,11 +1,11 @@
-----BEGIN CERTIFICATE-----
MIIBnTCCAQYCCQCuPrhkr+BvGzANBgkqhkiG9w0BAQUFADATMREwDwYDVQQDDAhI
ZWxsbyBDQTAeFw0xOTA4MTgwNjAwMDZaFw0xOTA5MTcwNjAwMDZaMBMxETAPBgNV
MIIBnTCCAQYCCQC/aR7GRyndljANBgkqhkiG9w0BAQUFADATMREwDwYDVQQDDAhI
ZWxsbyBDQTAeFw0xOTA5MjUxNDQ0NTFaFw0yOTA5MjIxNDQ0NTFaMBMxETAPBgNV
BAMMCEhlbGxvIENBMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDnSTDsRx4U
JmaTWHOAZasfN2O37wMcRez7LDM2qfQ8nlXnEAAZ4Pc51itOycWN1nclNVb489i9
J8ALgRKzNumSkfl1sCgJoDds75AC3oRRCbhnEP3Lu4mysxyOtYZNsdST8GBCP0m4
2tWa4W2ditpA44uU4x8opAX2qY919nVLNwIDAQABMA0GCSqGSIb3DQEBBQUAA4GB
ACfgNePlOLnLz1zJrWN6RZ6q0a+SSK8HdgSiKSF66SBIRILFoQmapBLXRY9YyATt
cdgg7pOd1WGCMqlOnhL56c8X5n+j/LGM5hc9PaEJA5vru7EBrnbxCkg0n8yp4Swc
8KFV5IiZ5D8t03AHjrXLQg8/HRzTuFRJJ1nJmc+FbnjT
AE/gsgTC4jzYC3icZdhALJTe3JsZ7geN702dE95zSI5LXAzzHJ/j8wGmorQjrMs2
iNPjVOdTU6cVWa1Ba29wWakVyVCUqDmDiWHaVhM/Qyyxo6mVlZGFwSnto3zq/h4y
KMFJ8lUtFCYMrzo5wqgj2xOjVrN77F6F4XWZbMufh50G
-----END CERTIFICATE-----

View File

@@ -10,7 +10,6 @@ new_certs_dir = $dir
default_md = sha256
policy = policy_match
serial = $dir/serial
default_days = 3650
[ policy_match ]
countryName = optional

View File

@@ -33,7 +33,7 @@ var (
// 4. Verify the kubeconfig.
//
func TestCmd_Run_Standalone(t *testing.T) {
timeout := 1 * time.Second
timeout := 5 * time.Second
type testParameter struct {
startServer func(t *testing.T, h http.Handler) (string, localserver.Shutdowner)
@@ -57,7 +57,7 @@ func TestCmd_Run_Standalone(t *testing.T) {
runTest := func(t *testing.T, p testParameter) {
t.Run("Defaults", func(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), timeout)
ctx, cancel := context.WithTimeout(context.TODO(), timeout)
defer cancel()
ctrl := gomock.NewController(t)
defer ctrl.Finish()
@@ -85,7 +85,7 @@ func TestCmd_Run_Standalone(t *testing.T) {
t.Run("ResourceOwnerPasswordCredentials", func(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), timeout)
ctx, cancel := context.WithTimeout(context.TODO(), timeout)
defer cancel()
ctrl := gomock.NewController(t)
defer ctrl.Finish()
@@ -116,7 +116,7 @@ func TestCmd_Run_Standalone(t *testing.T) {
t.Run("HasValidToken", func(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), timeout)
ctx, cancel := context.WithTimeout(context.TODO(), timeout)
defer cancel()
ctrl := gomock.NewController(t)
defer ctrl.Finish()
@@ -145,7 +145,7 @@ func TestCmd_Run_Standalone(t *testing.T) {
t.Run("HasValidRefreshToken", func(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), timeout)
ctx, cancel := context.WithTimeout(context.TODO(), timeout)
defer cancel()
ctrl := gomock.NewController(t)
defer ctrl.Finish()
@@ -178,7 +178,7 @@ func TestCmd_Run_Standalone(t *testing.T) {
t.Run("HasExpiredRefreshToken", func(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), timeout)
ctx, cancel := context.WithTimeout(context.TODO(), timeout)
defer cancel()
ctrl := gomock.NewController(t)
defer ctrl.Finish()
@@ -218,7 +218,7 @@ func TestCmd_Run_Standalone(t *testing.T) {
t.Run("env:KUBECONFIG", func(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), timeout)
ctx, cancel := context.WithTimeout(context.TODO(), timeout)
defer cancel()
ctrl := gomock.NewController(t)
defer ctrl.Finish()
@@ -245,7 +245,7 @@ func TestCmd_Run_Standalone(t *testing.T) {
t.Run("ExtraScopes", func(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), timeout)
ctx, cancel := context.WithTimeout(context.TODO(), timeout)
defer cancel()
ctrl := gomock.NewController(t)
defer ctrl.Finish()

12
go.mod
View File

@@ -5,20 +5,20 @@ go 1.12
require (
github.com/coreos/go-oidc v2.1.0+incompatible
github.com/dgrijalva/jwt-go v0.0.0-20160705203006-01aeca54ebda
github.com/go-test/deep v1.0.3
github.com/go-test/deep v1.0.4
github.com/golang/mock v1.3.1
github.com/google/wire v0.3.0
github.com/int128/oauth2cli v1.5.0
github.com/int128/oauth2cli v1.7.0
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
github.com/spf13/cobra v0.0.5
github.com/spf13/pflag v1.0.3
github.com/spf13/pflag v1.0.5
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a
golang.org/x/sync v0.0.0-20190423024810-112230192c58
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7
gopkg.in/square/go-jose.v2 v2.3.1 // indirect
gopkg.in/yaml.v2 v2.2.2
gopkg.in/yaml.v2 v2.2.4
k8s.io/apimachinery v0.0.0-20190612205821-1799e75a0719
k8s.io/client-go v0.0.0-20190620085101-78d2af792bab
k8s.io/klog v0.4.0

33
go.sum
View File

@@ -4,8 +4,6 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-oidc v2.0.0+incompatible h1:+RStIopZ8wooMx+Vs5Bt8zMXxV1ABl5LbakNExNmZIg=
github.com/coreos/go-oidc v2.0.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
github.com/coreos/go-oidc v2.1.0+incompatible h1:sdJrfw8akMnCuUlaZU3tE/uYXFgfqom8DBE9so9EBsM=
github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
@@ -20,18 +18,18 @@ github.com/evanphx/json-patch v0.0.0-20190203023257-5858425f7550/go.mod h1:50XU6
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw=
github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho=
github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/gogo/protobuf v0.0.0-20171007142547-342cbe0a0415 h1:WSBJMqJbLxsn+bTCPyPYZfqHdJmc8MK4wrBjMft6BAM=
github.com/gogo/protobuf v0.0.0-20171007142547-342cbe0a0415/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/btree v0.0.0-20160524151835-7d79101e329e/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeqr2z77+8R2RKyh8PG66dcu1V0ck=
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
@@ -47,11 +45,10 @@ github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/imdario/mergo v0.3.5 h1:JboBksRwiiAJWvIYJVo46AfV+IAIKZpfrSzVKj42R4Q=
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/int128/oauth2cli v1.4.1 h1:IsaYMafEDS1jyArxYdmksw+nMsNxiYCQzdkPj3QF9BY=
github.com/int128/oauth2cli v1.4.1/go.mod h1:CMJjyUSgKiobye1M/9byFACOjtB2LRo2mo7boklEKlI=
github.com/int128/oauth2cli v1.5.0 h1:EOBMCWfroql1hPqPhP+EtDhgO7y6ClFZ/NwJEpBCo1s=
github.com/int128/oauth2cli v1.5.0/go.mod h1:ivzuzt+k+bpwLI1Mb1bRq8PiBvwLBsO8L7tX2F9iKKA=
github.com/int128/oauth2cli v1.7.0 h1:lguQEIJ4IcSFRTqQ6y7avnfvPqVe0U6dlkW8mC1Epts=
github.com/int128/oauth2cli v1.7.0/go.mod h1:bucNn0/es9IhOf0a2MWPvJ5xO5f6JYrCfitQTyjI5lA=
github.com/json-iterator/go v0.0.0-20180701071628-ab8a2e0c74be h1:AHimNtVIpiBjPUhEF5KNCkrUyqTSA5zWUl8sQ2bfGBE=
github.com/json-iterator/go v0.0.0-20180701071628-ab8a2e0c74be/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
@@ -70,6 +67,7 @@ github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/9
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4 h1:49lOXmGaUpV9Fz3gd7TFZY106KVlPVa5jcYD1gaQf98=
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU=
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
@@ -83,7 +81,10 @@ github.com/spf13/pflag v1.0.1 h1:aCvUg6QPl3ibpQUxyLkrEkCHtPqYJL4x9AuhqVqFis4=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
@@ -99,14 +100,17 @@ golang.org/x/net v0.0.0-20190206173232-65e2d4e15006 h1:bfLnR+k0tq5Lqt6dflRLcZiz6
golang.org/x/net v0.0.0-20190206173232-65e2d4e15006/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/oauth2 v0.0.0-20190319182350-c85d3e98c914/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a h1:tImsplftrFpALCYumobsd0K86vlAs/eXGFms2txfJfA=
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -121,11 +125,12 @@ golang.org/x/time v0.0.0-20161028155119-f51c12702a4d/go.mod h1:tRJNPiyCQ0inRvYxb
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190422233926-fe54fb35175b/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/inf.v0 v0.9.0 h1:3zYtXIO92bvsdS3ggAdA8Gb4Azj0YU+TVY1uGYNFA8o=
@@ -137,12 +142,14 @@ gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
k8s.io/api v0.0.0-20190620084959-7cf5895f2711 h1:BblVYz/wE5WtBsD/Gvu54KyBUTJMflolzc5I2DTvh50=
k8s.io/api v0.0.0-20190620084959-7cf5895f2711/go.mod h1:TBhBqb1AWbBQbW3XRusr7n7E4v2+5ZY8r8sAMnyFC5A=
k8s.io/apimachinery v0.0.0-20190612205821-1799e75a0719 h1:uV4S5IB5g4Nvi+TBVNf3e9L4wrirlwYJ6w88jUQxTUw=
k8s.io/apimachinery v0.0.0-20190612205821-1799e75a0719/go.mod h1:I4A+glKBHiTgiEjQiCCQfCAIcIMFGt291SmsvcrFzJA=
k8s.io/client-go v0.0.0-20190620085101-78d2af792bab h1:E8Fecph0qbNsAbijJJQryKu4Oi9QTp5cVpjTE+nqg6g=
k8s.io/client-go v0.0.0-20190620085101-78d2af792bab/go.mod h1:E95RaSlHr79aHaX0aGSwcPNfygDiPKOVXdmivCIZT0k=
k8s.io/client-go v11.0.0+incompatible h1:LBbX2+lOwY9flffWlJM7f1Ct8V2SRNiMRDFeiwnJo9o=
k8s.io/klog v0.3.1 h1:RVgyDHY/kFKtLqh67NvEWIgkMneNoIrdkN0CxDSQc68=
k8s.io/klog v0.3.1/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/klog v0.4.0 h1:lCJCxf/LIowc2IGS9TPjWDyXY4nOmdGdfcwwDQCOURQ=

View File

@@ -16,21 +16,13 @@ var Set = wire.NewSet(
wire.Bind(new(Interface), new(*Cmd)),
wire.Struct(new(Root), "*"),
wire.Struct(new(GetToken), "*"),
wire.Struct(new(Setup), "*"),
)
type Interface interface {
Run(ctx context.Context, args []string, version string) int
}
const examples = ` # Login to the provider using the authorization code flow.
%[1]s
# Login to the provider using the resource owner password credentials flow.
%[1]s --username USERNAME --password PASSWORD
# Run as a credential plugin.
%[1]s get-token --oidc-issuer-url=https://issuer.example.com`
var defaultListenPort = []int{8000, 18000}
var defaultTokenCacheDir = homedir.HomeDir() + "/.kube/cache/oidc-login"
@@ -38,6 +30,7 @@ var defaultTokenCacheDir = homedir.HomeDir() + "/.kube/cache/oidc-login"
type Cmd struct {
Root *Root
GetToken *GetToken
Setup *Setup
Logger logger.Interface
}
@@ -54,6 +47,9 @@ func (cmd *Cmd) Run(ctx context.Context, args []string, version string) int {
getTokenCmd := cmd.GetToken.New(ctx)
rootCmd.AddCommand(getTokenCmd)
setupCmd := cmd.Setup.New(ctx)
rootCmd.AddCommand(setupCmd)
versionCmd := &cobra.Command{
Use: "version",
Short: "Print the version information",

View File

@@ -12,24 +12,29 @@ import (
// getTokenOptions represents the options for get-token command.
type getTokenOptions struct {
loginOptions
IssuerURL string
ClientID string
ClientSecret string
ExtraScopes []string
ListenPort []int
SkipOpenBrowser bool
Username string
Password string
CertificateAuthority string
SkipTLSVerify bool
Verbose int
TokenCacheDir string
}
func (o *getTokenOptions) register(f *pflag.FlagSet) {
f.SortFlags = false
o.loginOptions.register(f)
f.StringVar(&o.IssuerURL, "oidc-issuer-url", "", "Issuer URL of the provider (mandatory)")
f.StringVar(&o.ClientID, "oidc-client-id", "", "Client ID of the provider (mandatory)")
f.StringVar(&o.ClientSecret, "oidc-client-secret", "", "Client secret of the provider")
f.StringSliceVar(&o.ExtraScopes, "oidc-extra-scope", nil, "Scopes to request to the provider")
f.IntSliceVar(&o.ListenPort, "listen-port", defaultListenPort, "Port to bind to the local server. If multiple ports are given, it will try the ports in order")
f.BoolVar(&o.SkipOpenBrowser, "skip-open-browser", false, "If true, it does not open the browser on authentication")
f.StringVar(&o.Username, "username", "", "If set, perform the resource owner password credentials grant")
f.StringVar(&o.Password, "password", "", "If set, use the password instead of asking it")
f.StringVar(&o.CertificateAuthority, "certificate-authority", "", "Path to a cert file for the certificate authority")
f.BoolVar(&o.SkipTLSVerify, "insecure-skip-tls-verify", false, "If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure")
f.StringVar(&o.TokenCacheDir, "token-cache-dir", defaultTokenCacheDir, "Path to a directory for caching tokens")

View File

@@ -2,7 +2,6 @@ package cmd
import (
"context"
"fmt"
"github.com/int128/kubelogin/pkg/adaptors/kubeconfig"
"github.com/int128/kubelogin/pkg/adaptors/logger"
@@ -12,38 +11,40 @@ import (
"golang.org/x/xerrors"
)
// kubectlOptions represents kubectl specific options.
type kubectlOptions struct {
const longDescription = `Login to the OpenID Connect provider.
You need to set up the OIDC provider, role binding, Kubernetes API server and kubeconfig.
Run the following command to show the setup instruction:
kubectl oidc-login setup
See https://github.com/int128/kubelogin for more.
`
// rootOptions represents the options for the root command.
type rootOptions struct {
Kubeconfig string
Context string
User string
ListenPort []int
SkipOpenBrowser bool
Username string
Password string
CertificateAuthority string
SkipTLSVerify bool
}
func (o *kubectlOptions) register(f *pflag.FlagSet) {
func (o *rootOptions) register(f *pflag.FlagSet) {
f.SortFlags = false
f.StringVar(&o.Kubeconfig, "kubeconfig", "", "Path to the kubeconfig file")
f.StringVar(&o.Context, "context", "", "The name of the kubeconfig context to use")
f.StringVar(&o.User, "user", "", "The name of the kubeconfig user to use. Prior to --context")
f.StringVar(&o.CertificateAuthority, "certificate-authority", "", "Path to a cert file for the certificate authority")
f.BoolVar(&o.SkipTLSVerify, "insecure-skip-tls-verify", false, "If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure")
}
// loginOptions represents the options for Login use-case.
type loginOptions struct {
ListenPort []int
SkipOpenBrowser bool
Username string
Password string
}
func (o *loginOptions) register(f *pflag.FlagSet) {
f.SortFlags = false
f.IntSliceVar(&o.ListenPort, "listen-port", defaultListenPort, "Port to bind to the local server. If multiple ports are given, it will try the ports in order")
f.BoolVar(&o.SkipOpenBrowser, "skip-open-browser", false, "If true, it does not open the browser on authentication")
f.StringVar(&o.Username, "username", "", "If set, perform the resource owner password credentials grant")
f.StringVar(&o.Password, "password", "", "If set, use the password instead of asking it")
f.StringVar(&o.CertificateAuthority, "certificate-authority", "", "Path to a cert file for the certificate authority")
f.BoolVar(&o.SkipTLSVerify, "insecure-skip-tls-verify", false, "If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure")
}
type Root struct {
@@ -52,15 +53,12 @@ type Root struct {
}
func (cmd *Root) New(ctx context.Context, executable string) *cobra.Command {
var o struct {
kubectlOptions
loginOptions
}
var o rootOptions
rootCmd := &cobra.Command{
Use: executable,
Short: "Login to the OpenID Connect provider and update the kubeconfig",
Example: fmt.Sprintf(examples, executable),
Args: cobra.NoArgs,
Use: executable,
Short: "Login to the OpenID Connect provider",
Long: longDescription,
Args: cobra.NoArgs,
RunE: func(*cobra.Command, []string) error {
in := standalone.Input{
KubeconfigFilename: o.Kubeconfig,
@@ -79,8 +77,7 @@ func (cmd *Root) New(ctx context.Context, executable string) *cobra.Command {
return nil
},
}
o.kubectlOptions.register(rootCmd.Flags())
o.loginOptions.register(rootCmd.Flags())
o.register(rootCmd.Flags())
cmd.Logger.AddFlags(rootCmd.PersistentFlags())
return rootCmd
}

71
pkg/adaptors/cmd/setup.go Normal file
View File

@@ -0,0 +1,71 @@
package cmd
import (
"context"
"reflect"
"github.com/int128/kubelogin/pkg/usecases/setup"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"golang.org/x/xerrors"
)
// setupOptions represents the options for setup command.
type setupOptions struct {
IssuerURL string
ClientID string
ClientSecret string
ExtraScopes []string
ListenPort []int
SkipOpenBrowser bool
CertificateAuthority string
SkipTLSVerify bool
}
func (o *setupOptions) register(f *pflag.FlagSet) {
f.SortFlags = false
f.StringVar(&o.IssuerURL, "oidc-issuer-url", "", "Issuer URL of the provider")
f.StringVar(&o.ClientID, "oidc-client-id", "", "Client ID of the provider")
f.StringVar(&o.ClientSecret, "oidc-client-secret", "", "Client secret of the provider")
f.StringSliceVar(&o.ExtraScopes, "oidc-extra-scope", nil, "Scopes to request to the provider")
f.IntSliceVar(&o.ListenPort, "listen-port", defaultListenPort, "Port to bind to the local server. If multiple ports are given, it will try the ports in order")
f.BoolVar(&o.SkipOpenBrowser, "skip-open-browser", false, "If true, it does not open the browser on authentication")
f.StringVar(&o.CertificateAuthority, "certificate-authority", "", "Path to a cert file for the certificate authority")
f.BoolVar(&o.SkipTLSVerify, "insecure-skip-tls-verify", false, "If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure")
}
type Setup struct {
Setup setup.Interface
}
func (cmd *Setup) New(ctx context.Context) *cobra.Command {
var o setupOptions
c := &cobra.Command{
Use: "setup",
Short: "Show the setup instruction",
Args: cobra.NoArgs,
RunE: func(*cobra.Command, []string) error {
in := setup.Stage2Input{
IssuerURL: o.IssuerURL,
ClientID: o.ClientID,
ClientSecret: o.ClientSecret,
ExtraScopes: o.ExtraScopes,
SkipOpenBrowser: o.SkipOpenBrowser,
ListenPort: o.ListenPort,
ListenPortIsSet: !reflect.DeepEqual(o.ListenPort, defaultListenPort),
CACertFilename: o.CertificateAuthority,
SkipTLSVerify: o.SkipTLSVerify,
}
if in.IssuerURL == "" || in.ClientID == "" {
cmd.Setup.DoStage1()
return nil
}
if err := cmd.Setup.DoStage2(ctx, in); err != nil {
return xerrors.Errorf("error: %w", err)
}
return nil
},
}
o.register(c.Flags())
return c
}

View File

@@ -36,7 +36,7 @@ type Env struct{}
// ReadPassword reads a password from the stdin without echo back.
func (*Env) ReadPassword(prompt string) (string, error) {
if _, err := fmt.Fprint(os.Stderr, "Password: "); err != nil {
if _, err := fmt.Fprint(os.Stderr, prompt); err != nil {
return "", xerrors.Errorf("could not write the prompt: %w", err)
}
b, err := terminal.ReadPassword(int(syscall.Stdin))

View File

@@ -24,10 +24,11 @@ type Interface interface {
// TokenSet represents an output DTO of
// Interface.AuthenticateByCode, Interface.AuthenticateByPassword and Interface.Refresh.
type TokenSet struct {
IDToken string
RefreshToken string
IDTokenExpiry time.Time
IDTokenClaims map[string]string // string representation of claims for logging
IDToken string
RefreshToken string
IDTokenSubject string
IDTokenExpiry time.Time
IDTokenClaims map[string]string // string representation of claims for logging
}
type client struct {

View File

@@ -16,8 +16,9 @@ type DecoderInterface interface {
}
type DecodedIDToken struct {
IDTokenExpiry time.Time
IDTokenClaims map[string]string // string representation of claims for logging
Subject string
Expiry time.Time
Claims map[string]string // string representation of claims for logging
}
type Decoder struct{}
@@ -42,17 +43,18 @@ func (d *Decoder) DecodeIDToken(t string) (*DecodedIDToken, error) {
return nil, xerrors.Errorf("could not decode the json of token: %w", err)
}
return &DecodedIDToken{
IDTokenExpiry: time.Unix(claims.ExpiresAt, 0),
IDTokenClaims: dumpRawClaims(rawClaims),
Subject: claims.Subject,
Expiry: time.Unix(claims.ExpiresAt, 0),
Claims: dumpRawClaims(rawClaims),
}, nil
}
func dumpRawClaims(rawClaims map[string]interface{}) map[string]string {
claims := make(map[string]string)
for k, v := range rawClaims {
switch v.(type) {
switch v := v.(type) {
case float64:
claims[k] = fmt.Sprintf("%.f", v.(float64))
claims[k] = fmt.Sprintf("%.f", v)
default:
claims[k] = fmt.Sprintf("%v", v)
}

View File

@@ -21,10 +21,10 @@ func TestDecoder_DecodeIDToken(t *testing.T) {
if err != nil {
t.Fatalf("DecodeIDToken error: %s", err)
}
if decodedToken.IDTokenExpiry != expiry {
t.Errorf("IDTokenExpiry wants %s but %s", expiry, decodedToken.IDTokenExpiry)
if decodedToken.Expiry != expiry {
t.Errorf("Expiry wants %s but %s", expiry, decodedToken.Expiry)
}
t.Logf("IDTokenClaims=%+v", decodedToken.IDTokenClaims)
t.Logf("Claims=%+v", decodedToken.Claims)
})
t.Run("InvalidToken", func(t *testing.T) {
decodedToken, err := decoder.DecodeIDToken("HEADER.INVALID_TOKEN.SIGNATURE")

View File

@@ -14,6 +14,7 @@ import (
"github.com/int128/kubelogin/pkg/adaptors/tokencache"
"github.com/int128/kubelogin/pkg/usecases/auth"
credentialPluginUseCase "github.com/int128/kubelogin/pkg/usecases/credentialplugin"
"github.com/int128/kubelogin/pkg/usecases/setup"
"github.com/int128/kubelogin/pkg/usecases/standalone"
)
@@ -25,6 +26,7 @@ func NewCmd() cmd.Interface {
wire.Value(auth.DefaultLocalServerReadyFunc),
standalone.Set,
credentialPluginUseCase.Set,
setup.Set,
// adaptors
cmd.Set,
@@ -44,6 +46,8 @@ func NewCmdForHeadless(logger.Interface, auth.LocalServerReadyFunc, credentialPl
auth.Set,
standalone.Set,
credentialPluginUseCase.Set,
setup.Set,
cmd.Set,
env.Set,
kubeconfig.Set,

View File

@@ -15,6 +15,7 @@ import (
"github.com/int128/kubelogin/pkg/adaptors/tokencache"
"github.com/int128/kubelogin/pkg/usecases/auth"
credentialplugin2 "github.com/int128/kubelogin/pkg/usecases/credentialplugin"
"github.com/int128/kubelogin/pkg/usecases/setup"
"github.com/int128/kubelogin/pkg/usecases/standalone"
)
@@ -57,9 +58,17 @@ func NewCmd() cmd.Interface {
GetToken: getToken,
Logger: loggerInterface,
}
setupSetup := &setup.Setup{
Authentication: authentication,
Logger: loggerInterface,
}
cmdSetup := &cmd.Setup{
Setup: setupSetup,
}
cmdCmd := &cmd.Cmd{
Root: root,
GetToken: cmdGetToken,
Setup: cmdSetup,
Logger: loggerInterface,
}
return cmdCmd
@@ -103,9 +112,17 @@ func NewCmdForHeadless(loggerInterface logger.Interface, localServerReadyFunc au
GetToken: getToken,
Logger: loggerInterface,
}
setupSetup := &setup.Setup{
Authentication: authentication,
Logger: loggerInterface,
}
cmdSetup := &cmd.Setup{
Setup: setupSetup,
}
cmdCmd := &cmd.Cmd{
Root: root,
GetToken: cmdGetToken,
Setup: cmdSetup,
Logger: loggerInterface,
}
return cmdCmd

View File

@@ -45,6 +45,7 @@ type Input struct {
// Output represents an output DTO of the Authentication use-case.
type Output struct {
AlreadyHasValidIDToken bool
IDTokenSubject string
IDTokenExpiry time.Time
IDTokenClaims map[string]string
IDToken string
@@ -84,17 +85,18 @@ func (u *Authentication) Do(ctx context.Context, in Input) (*Output, error) {
if err != nil {
return nil, xerrors.Errorf("invalid token and you need to remove the cache: %w", err)
}
if token.IDTokenExpiry.After(time.Now()) { //TODO: inject time service
u.Logger.V(1).Infof("you already have a valid token until %s", token.IDTokenExpiry)
if token.Expiry.After(time.Now()) { //TODO: inject time service
u.Logger.V(1).Infof("you already have a valid token until %s", token.Expiry)
return &Output{
AlreadyHasValidIDToken: true,
IDToken: in.OIDCConfig.IDToken,
RefreshToken: in.OIDCConfig.RefreshToken,
IDTokenExpiry: token.IDTokenExpiry,
IDTokenClaims: token.IDTokenClaims,
IDTokenSubject: token.Subject,
IDTokenExpiry: token.Expiry,
IDTokenClaims: token.Claims,
}, nil
}
u.Logger.V(1).Infof("you have an expired token at %s", token.IDTokenExpiry)
u.Logger.V(1).Infof("you have an expired token at %s", token.Expiry)
}
u.Logger.V(1).Infof("initializing an OIDCFactory client")
@@ -112,10 +114,11 @@ func (u *Authentication) Do(ctx context.Context, in Input) (*Output, error) {
out, err := client.Refresh(ctx, in.OIDCConfig.RefreshToken)
if err == nil {
return &Output{
IDToken: out.IDToken,
RefreshToken: out.RefreshToken,
IDTokenExpiry: out.IDTokenExpiry,
IDTokenClaims: out.IDTokenClaims,
IDToken: out.IDToken,
RefreshToken: out.RefreshToken,
IDTokenSubject: out.IDTokenSubject,
IDTokenExpiry: out.IDTokenExpiry,
IDTokenClaims: out.IDTokenClaims,
}, nil
}
u.Logger.V(1).Infof("could not refresh the token: %s", err)
@@ -130,8 +133,9 @@ func (u *Authentication) Do(ctx context.Context, in Input) (*Output, error) {
func (u *Authentication) doAuthCodeFlow(ctx context.Context, in Input, client oidc.Interface) (*Output, error) {
u.Logger.V(1).Infof("performing the authentication code flow")
readyChan := make(chan string, 1)
defer close(readyChan)
var out Output
var eg errgroup.Group
eg, ctx := errgroup.WithContext(ctx)
eg.Go(func() error {
select {
case url, ok := <-readyChan:
@@ -150,20 +154,20 @@ func (u *Authentication) doAuthCodeFlow(ctx context.Context, in Input, client oi
}
return nil
case <-ctx.Done():
return nil
return xerrors.Errorf("context cancelled while waiting for the local server: %w", ctx.Err())
}
})
eg.Go(func() error {
defer close(readyChan)
tokenSet, err := client.AuthenticateByCode(ctx, in.ListenPort, readyChan)
if err != nil {
return xerrors.Errorf("error while the authorization code flow: %w", err)
}
out = Output{
IDToken: tokenSet.IDToken,
RefreshToken: tokenSet.RefreshToken,
IDTokenExpiry: tokenSet.IDTokenExpiry,
IDTokenClaims: tokenSet.IDTokenClaims,
IDToken: tokenSet.IDToken,
RefreshToken: tokenSet.RefreshToken,
IDTokenSubject: tokenSet.IDTokenSubject,
IDTokenExpiry: tokenSet.IDTokenExpiry,
IDTokenClaims: tokenSet.IDTokenClaims,
}
return nil
})
@@ -187,9 +191,10 @@ func (u *Authentication) doPasswordCredentialsFlow(ctx context.Context, in Input
return nil, xerrors.Errorf("error while the resource owner password credentials flow: %w", err)
}
return &Output{
IDToken: tokenSet.IDToken,
RefreshToken: tokenSet.RefreshToken,
IDTokenExpiry: tokenSet.IDTokenExpiry,
IDTokenClaims: tokenSet.IDTokenClaims,
IDToken: tokenSet.IDToken,
RefreshToken: tokenSet.RefreshToken,
IDTokenSubject: tokenSet.IDTokenSubject,
IDTokenExpiry: tokenSet.IDTokenExpiry,
IDTokenClaims: tokenSet.IDTokenClaims,
}, nil
}

View File

@@ -19,11 +19,13 @@ func TestAuthentication_Do(t *testing.T) {
dummyTokenClaims := map[string]string{"sub": "YOUR_SUBJECT"}
pastTime := time.Now().Add(-time.Hour) //TODO: inject time service
futureTime := time.Now().Add(time.Hour) //TODO: inject time service
timeout := 5 * time.Second
t.Run("AuthorizationCodeFlow", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.TODO()
ctx, cancel := context.WithTimeout(context.TODO(), timeout)
defer cancel()
in := Input{
ListenPort: []int{10000},
SkipOpenBrowser: true,
@@ -36,12 +38,16 @@ func TestAuthentication_Do(t *testing.T) {
}
mockOIDCClient := mock_oidc.NewMockInterface(ctrl)
mockOIDCClient.EXPECT().
AuthenticateByCode(ctx, []int{10000}, gomock.Any()).
AuthenticateByCode(gomock.Any(), []int{10000}, gomock.Any()).
Do(func(_ context.Context, _ []int, readyChan chan<- string) {
readyChan <- "LOCAL_SERVER_URL"
}).
Return(&oidc.TokenSet{
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
IDTokenExpiry: futureTime,
IDTokenClaims: dummyTokenClaims,
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
IDTokenSubject: "YOUR_SUBJECT",
IDTokenExpiry: futureTime,
IDTokenClaims: dummyTokenClaims,
}, nil)
mockOIDCFactory := mock_oidc.NewMockFactoryInterface(ctrl)
mockOIDCFactory.EXPECT().
@@ -60,10 +66,11 @@ func TestAuthentication_Do(t *testing.T) {
t.Errorf("Do returned error: %+v", err)
}
want := &Output{
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
IDTokenExpiry: futureTime,
IDTokenClaims: dummyTokenClaims,
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
IDTokenSubject: "YOUR_SUBJECT",
IDTokenExpiry: futureTime,
IDTokenClaims: dummyTokenClaims,
}
if diff := deep.Equal(want, out); diff != nil {
t.Error(diff)
@@ -73,7 +80,8 @@ func TestAuthentication_Do(t *testing.T) {
t.Run("AuthorizationCodeFlow/OpenBrowser", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.TODO()
ctx, cancel := context.WithTimeout(context.TODO(), timeout)
defer cancel()
in := Input{
ListenPort: []int{10000},
OIDCConfig: kubeconfig.OIDCConfig{
@@ -83,15 +91,16 @@ func TestAuthentication_Do(t *testing.T) {
}
mockOIDCClient := mock_oidc.NewMockInterface(ctrl)
mockOIDCClient.EXPECT().
AuthenticateByCode(ctx, []int{10000}, gomock.Any()).
AuthenticateByCode(gomock.Any(), []int{10000}, gomock.Any()).
Do(func(_ context.Context, _ []int, readyChan chan<- string) {
readyChan <- "LOCAL_SERVER_URL"
}).
Return(&oidc.TokenSet{
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
IDTokenExpiry: futureTime,
IDTokenClaims: dummyTokenClaims,
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
IDTokenSubject: "YOUR_SUBJECT",
IDTokenExpiry: futureTime,
IDTokenClaims: dummyTokenClaims,
}, nil)
mockOIDCFactory := mock_oidc.NewMockFactoryInterface(ctrl)
mockOIDCFactory.EXPECT().
@@ -110,10 +119,11 @@ func TestAuthentication_Do(t *testing.T) {
t.Errorf("Do returned error: %+v", err)
}
want := &Output{
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
IDTokenExpiry: futureTime,
IDTokenClaims: dummyTokenClaims,
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
IDTokenSubject: "YOUR_SUBJECT",
IDTokenExpiry: futureTime,
IDTokenClaims: dummyTokenClaims,
}
if diff := deep.Equal(want, out); diff != nil {
t.Error(diff)
@@ -123,7 +133,8 @@ func TestAuthentication_Do(t *testing.T) {
t.Run("ResourceOwnerPasswordCredentialsFlow/UsePassword", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.TODO()
ctx, cancel := context.WithTimeout(context.TODO(), timeout)
defer cancel()
in := Input{
Username: "USER",
Password: "PASS",
@@ -136,12 +147,13 @@ func TestAuthentication_Do(t *testing.T) {
}
mockOIDCClient := mock_oidc.NewMockInterface(ctrl)
mockOIDCClient.EXPECT().
AuthenticateByPassword(ctx, "USER", "PASS").
AuthenticateByPassword(gomock.Any(), "USER", "PASS").
Return(&oidc.TokenSet{
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
IDTokenExpiry: futureTime,
IDTokenClaims: dummyTokenClaims,
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
IDTokenSubject: "YOUR_SUBJECT",
IDTokenExpiry: futureTime,
IDTokenClaims: dummyTokenClaims,
}, nil)
mockOIDCFactory := mock_oidc.NewMockFactoryInterface(ctrl)
mockOIDCFactory.EXPECT().
@@ -160,10 +172,11 @@ func TestAuthentication_Do(t *testing.T) {
t.Errorf("Do returned error: %+v", err)
}
want := &Output{
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
IDTokenExpiry: futureTime,
IDTokenClaims: dummyTokenClaims,
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
IDTokenSubject: "YOUR_SUBJECT",
IDTokenExpiry: futureTime,
IDTokenClaims: dummyTokenClaims,
}
if diff := deep.Equal(want, out); diff != nil {
t.Error(diff)
@@ -173,7 +186,8 @@ func TestAuthentication_Do(t *testing.T) {
t.Run("ResourceOwnerPasswordCredentialsFlow/AskPassword", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.TODO()
ctx, cancel := context.WithTimeout(context.TODO(), timeout)
defer cancel()
in := Input{
Username: "USER",
OIDCConfig: kubeconfig.OIDCConfig{
@@ -183,12 +197,13 @@ func TestAuthentication_Do(t *testing.T) {
}
mockOIDCClient := mock_oidc.NewMockInterface(ctrl)
mockOIDCClient.EXPECT().
AuthenticateByPassword(ctx, "USER", "PASS").
AuthenticateByPassword(gomock.Any(), "USER", "PASS").
Return(&oidc.TokenSet{
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
IDTokenExpiry: futureTime,
IDTokenClaims: dummyTokenClaims,
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
IDTokenSubject: "YOUR_SUBJECT",
IDTokenExpiry: futureTime,
IDTokenClaims: dummyTokenClaims,
}, nil)
mockOIDCFactory := mock_oidc.NewMockFactoryInterface(ctrl)
mockOIDCFactory.EXPECT().
@@ -208,10 +223,11 @@ func TestAuthentication_Do(t *testing.T) {
t.Errorf("Do returned error: %+v", err)
}
want := &Output{
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
IDTokenExpiry: futureTime,
IDTokenClaims: dummyTokenClaims,
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
IDTokenSubject: "YOUR_SUBJECT",
IDTokenExpiry: futureTime,
IDTokenClaims: dummyTokenClaims,
}
if diff := deep.Equal(want, out); diff != nil {
t.Error(diff)
@@ -221,7 +237,8 @@ func TestAuthentication_Do(t *testing.T) {
t.Run("ResourceOwnerPasswordCredentialsFlow/AskPasswordError", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.TODO()
ctx, cancel := context.WithTimeout(context.TODO(), timeout)
defer cancel()
in := Input{
Username: "USER",
OIDCConfig: kubeconfig.OIDCConfig{
@@ -254,7 +271,8 @@ func TestAuthentication_Do(t *testing.T) {
t.Run("HasValidIDToken", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.TODO()
ctx, cancel := context.WithTimeout(context.TODO(), timeout)
defer cancel()
in := Input{
OIDCConfig: kubeconfig.OIDCConfig{
ClientID: "YOUR_CLIENT_ID",
@@ -266,8 +284,9 @@ func TestAuthentication_Do(t *testing.T) {
mockOIDCDecoder.EXPECT().
DecodeIDToken("VALID_ID_TOKEN").
Return(&oidc.DecodedIDToken{
IDTokenExpiry: futureTime,
IDTokenClaims: dummyTokenClaims,
Subject: "YOUR_SUBJECT",
Expiry: futureTime,
Claims: dummyTokenClaims,
}, nil)
u := Authentication{
OIDCFactory: mock_oidc.NewMockFactoryInterface(ctrl),
@@ -281,6 +300,7 @@ func TestAuthentication_Do(t *testing.T) {
want := &Output{
AlreadyHasValidIDToken: true,
IDToken: "VALID_ID_TOKEN",
IDTokenSubject: "YOUR_SUBJECT",
IDTokenExpiry: futureTime,
IDTokenClaims: dummyTokenClaims,
}
@@ -292,7 +312,8 @@ func TestAuthentication_Do(t *testing.T) {
t.Run("HasValidRefreshToken", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.TODO()
ctx, cancel := context.WithTimeout(context.TODO(), timeout)
defer cancel()
in := Input{
OIDCConfig: kubeconfig.OIDCConfig{
ClientID: "YOUR_CLIENT_ID",
@@ -305,17 +326,19 @@ func TestAuthentication_Do(t *testing.T) {
mockOIDCDecoder.EXPECT().
DecodeIDToken("EXPIRED_ID_TOKEN").
Return(&oidc.DecodedIDToken{
IDTokenExpiry: pastTime,
IDTokenClaims: dummyTokenClaims,
Subject: "YOUR_SUBJECT",
Expiry: pastTime,
Claims: dummyTokenClaims,
}, nil)
mockOIDCClient := mock_oidc.NewMockInterface(ctrl)
mockOIDCClient.EXPECT().
Refresh(ctx, "VALID_REFRESH_TOKEN").
Return(&oidc.TokenSet{
IDToken: "NEW_ID_TOKEN",
RefreshToken: "NEW_REFRESH_TOKEN",
IDTokenExpiry: futureTime,
IDTokenClaims: dummyTokenClaims,
IDToken: "NEW_ID_TOKEN",
RefreshToken: "NEW_REFRESH_TOKEN",
IDTokenSubject: "YOUR_SUBJECT",
IDTokenExpiry: futureTime,
IDTokenClaims: dummyTokenClaims,
}, nil)
mockOIDCFactory := mock_oidc.NewMockFactoryInterface(ctrl)
mockOIDCFactory.EXPECT().
@@ -333,10 +356,11 @@ func TestAuthentication_Do(t *testing.T) {
t.Errorf("Do returned error: %+v", err)
}
want := &Output{
IDToken: "NEW_ID_TOKEN",
RefreshToken: "NEW_REFRESH_TOKEN",
IDTokenExpiry: futureTime,
IDTokenClaims: dummyTokenClaims,
IDToken: "NEW_ID_TOKEN",
RefreshToken: "NEW_REFRESH_TOKEN",
IDTokenSubject: "YOUR_SUBJECT",
IDTokenExpiry: futureTime,
IDTokenClaims: dummyTokenClaims,
}
if diff := deep.Equal(want, out); diff != nil {
t.Error(diff)
@@ -346,9 +370,11 @@ func TestAuthentication_Do(t *testing.T) {
t.Run("HasExpiredRefreshToken", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.TODO()
ctx, cancel := context.WithTimeout(context.TODO(), timeout)
defer cancel()
in := Input{
ListenPort: []int{10000},
ListenPort: []int{10000},
SkipOpenBrowser: true,
OIDCConfig: kubeconfig.OIDCConfig{
ClientID: "YOUR_CLIENT_ID",
ClientSecret: "YOUR_CLIENT_SECRET",
@@ -360,20 +386,25 @@ func TestAuthentication_Do(t *testing.T) {
mockOIDCDecoder.EXPECT().
DecodeIDToken("EXPIRED_ID_TOKEN").
Return(&oidc.DecodedIDToken{
IDTokenExpiry: pastTime,
IDTokenClaims: dummyTokenClaims,
Subject: "YOUR_SUBJECT",
Expiry: pastTime,
Claims: dummyTokenClaims,
}, nil)
mockOIDCClient := mock_oidc.NewMockInterface(ctrl)
mockOIDCClient.EXPECT().
Refresh(ctx, "EXPIRED_REFRESH_TOKEN").
Return(nil, xerrors.New("token has expired"))
mockOIDCClient.EXPECT().
AuthenticateByCode(ctx, []int{10000}, gomock.Any()).
AuthenticateByCode(gomock.Any(), []int{10000}, gomock.Any()).
Do(func(_ context.Context, _ []int, readyChan chan<- string) {
readyChan <- "LOCAL_SERVER_URL"
}).
Return(&oidc.TokenSet{
IDToken: "NEW_ID_TOKEN",
RefreshToken: "NEW_REFRESH_TOKEN",
IDTokenExpiry: futureTime,
IDTokenClaims: dummyTokenClaims,
IDToken: "NEW_ID_TOKEN",
RefreshToken: "NEW_REFRESH_TOKEN",
IDTokenSubject: "YOUR_SUBJECT",
IDTokenExpiry: futureTime,
IDTokenClaims: dummyTokenClaims,
}, nil)
mockOIDCFactory := mock_oidc.NewMockFactoryInterface(ctrl)
mockOIDCFactory.EXPECT().
@@ -391,10 +422,11 @@ func TestAuthentication_Do(t *testing.T) {
t.Errorf("Do returned error: %+v", err)
}
want := &Output{
IDToken: "NEW_ID_TOKEN",
RefreshToken: "NEW_REFRESH_TOKEN",
IDTokenExpiry: futureTime,
IDTokenClaims: dummyTokenClaims,
IDToken: "NEW_ID_TOKEN",
RefreshToken: "NEW_REFRESH_TOKEN",
IDTokenSubject: "YOUR_SUBJECT",
IDTokenExpiry: futureTime,
IDTokenClaims: dummyTokenClaims,
}
if diff := deep.Equal(want, out); diff != nil {
t.Error(diff)

View File

@@ -50,7 +50,18 @@ type GetToken struct {
func (u *GetToken) Do(ctx context.Context, in Input) error {
u.Logger.V(1).Infof("WARNING: log may contain your secrets such as token or password")
out, err := u.getTokenFromCacheOrProvider(ctx, in)
if err != nil {
return xerrors.Errorf("could not get a token from the cache or provider: %w", err)
}
u.Logger.V(1).Infof("writing the token to client-go")
if err := u.Interaction.Write(credentialplugin.Output{Token: out.IDToken, Expiry: out.IDTokenExpiry}); err != nil {
return xerrors.Errorf("could not write the token to client-go: %w", err)
}
return nil
}
func (u *GetToken) getTokenFromCacheOrProvider(ctx context.Context, in Input) (*auth.Output, error) {
u.Logger.V(1).Infof("finding a token from cache directory %s", in.TokenCacheDir)
cacheKey := tokencache.Key{IssuerURL: in.IssuerURL, ClientID: in.ClientID}
cache, err := u.TokenCacheRepository.FindByKey(in.TokenCacheDir, cacheKey)
@@ -58,6 +69,7 @@ func (u *GetToken) Do(ctx context.Context, in Input) error {
u.Logger.V(1).Infof("could not find a token cache: %s", err)
cache = &tokencache.TokenCache{}
}
out, err := u.Authentication.Do(ctx, auth.Input{
OIDCConfig: kubeconfig.OIDCConfig{
IDPIssuerURL: in.IssuerURL,
@@ -75,25 +87,23 @@ func (u *GetToken) Do(ctx context.Context, in Input) error {
SkipTLSVerify: in.SkipTLSVerify,
})
if err != nil {
return xerrors.Errorf("error while authentication: %w", err)
return nil, xerrors.Errorf("error while authentication: %w", err)
}
for k, v := range out.IDTokenClaims {
u.Logger.V(1).Infof("the ID token has the claim: %s=%v", k, v)
}
if !out.AlreadyHasValidIDToken {
u.Logger.Printf("You got a valid token until %s", out.IDTokenExpiry)
cache := tokencache.TokenCache{
IDToken: out.IDToken,
RefreshToken: out.RefreshToken,
}
if err := u.TokenCacheRepository.Save(in.TokenCacheDir, cacheKey, cache); err != nil {
return xerrors.Errorf("could not write the token cache: %w", err)
}
if out.AlreadyHasValidIDToken {
u.Logger.V(1).Infof("you already have a valid token until %s", out.IDTokenExpiry)
return out, nil
}
u.Logger.V(1).Infof("writing the token to client-go")
if err := u.Interaction.Write(credentialplugin.Output{Token: out.IDToken, Expiry: out.IDTokenExpiry}); err != nil {
return xerrors.Errorf("could not write the token to client-go: %w", err)
u.Logger.V(1).Infof("you got a valid token until %s", out.IDTokenExpiry)
newCache := tokencache.TokenCache{
IDToken: out.IDToken,
RefreshToken: out.RefreshToken,
}
return nil
if err := u.TokenCacheRepository.Save(in.TokenCacheDir, cacheKey, newCache); err != nil {
return nil, xerrors.Errorf("could not write the token cache: %w", err)
}
return out, nil
}

View File

@@ -0,0 +1,25 @@
// Package setup provides the use case of setting up environment.
package setup
import (
"context"
"github.com/google/wire"
"github.com/int128/kubelogin/pkg/adaptors/logger"
"github.com/int128/kubelogin/pkg/usecases/auth"
)
var Set = wire.NewSet(
wire.Struct(new(Setup), "*"),
wire.Bind(new(Interface), new(*Setup)),
)
type Interface interface {
DoStage1()
DoStage2(ctx context.Context, in Stage2Input) error
}
type Setup struct {
Authentication auth.Interface
Logger logger.Interface
}

View 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)
}

View File

@@ -0,0 +1,141 @@
package setup
import (
"context"
"fmt"
"strings"
"text/template"
"github.com/int128/kubelogin/pkg/adaptors/kubeconfig"
"github.com/int128/kubelogin/pkg/usecases/auth"
"golang.org/x/xerrors"
)
var stage2Tpl = template.Must(template.New("").Parse(`
## 3. Bind a role
Run the following command:
kubectl apply -f - <<-EOF
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: oidc-cluster-admin
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: User
name: {{ .IssuerURL }}#{{ .Subject }}
EOF
## 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
Add the following user to the kubeconfig:
users:
- name: google
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
command: kubectl
args:
- oidc-login
- get-token
{{- range .Args }}
- {{ . }}
{{- end }}
Run kubectl and verify cluster access.
`))
type stage2Vars struct {
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
SkipOpenBrowser bool
ListenPort []int
ListenPortIsSet bool // true if it is set by the command arg
CACertFilename string // If set, use the CA cert
SkipTLSVerify bool
}
func (u *Setup) DoStage2(ctx context.Context, in Stage2Input) error {
u.Logger.Printf(`## 2. Verify authentication`)
out, err := u.Authentication.Do(ctx, auth.Input{
OIDCConfig: kubeconfig.OIDCConfig{
IDPIssuerURL: in.IssuerURL,
ClientID: in.ClientID,
ClientSecret: in.ClientSecret,
ExtraScopes: in.ExtraScopes,
},
SkipOpenBrowser: in.SkipOpenBrowser,
ListenPort: in.ListenPort,
CACertFilename: in.CACertFilename,
SkipTLSVerify: in.SkipTLSVerify,
})
if err != nil {
return xerrors.Errorf("error while authentication: %w", err)
}
u.Logger.Printf("You got the following claims in the token:")
for k, v := range out.IDTokenClaims {
u.Logger.Printf("\t%s=%s", k, v)
}
v := stage2Vars{
IssuerURL: in.IssuerURL,
ClientID: in.ClientID,
Args: makeCredentialPluginArgs(in),
Subject: out.IDTokenSubject,
}
var b strings.Builder
if err := stage2Tpl.Execute(&b, &v); err != nil {
return xerrors.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.SkipOpenBrowser {
args = append(args, "--skip-open-browser")
}
if in.ListenPortIsSet {
for _, port := range in.ListenPort {
args = append(args, fmt.Sprintf("--listen-port=%d", port))
}
}
if in.CACertFilename != "" {
args = append(args, "--certificate-authority="+in.CACertFilename)
}
if in.SkipTLSVerify {
args = append(args, "--insecure-skip-tls-verify")
}
return args
}

View File

@@ -0,0 +1,59 @@
package setup
import (
"context"
"testing"
"time"
"github.com/golang/mock/gomock"
"github.com/int128/kubelogin/pkg/adaptors/kubeconfig"
"github.com/int128/kubelogin/pkg/adaptors/logger/mock_logger"
"github.com/int128/kubelogin/pkg/usecases/auth"
"github.com/int128/kubelogin/pkg/usecases/auth/mock_auth"
)
func TestSetup_DoStage2(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.Background()
in := Stage2Input{
IssuerURL: "https://accounts.google.com",
ClientID: "YOUR_CLIENT_ID",
ClientSecret: "YOUR_CLIENT_SECRET",
ExtraScopes: []string{"email"},
SkipOpenBrowser: true,
ListenPort: []int{8000},
CACertFilename: "/path/to/cert",
SkipTLSVerify: true,
}
mockAuthentication := mock_auth.NewMockInterface(ctrl)
mockAuthentication.EXPECT().
Do(ctx, auth.Input{
OIDCConfig: kubeconfig.OIDCConfig{
IDPIssuerURL: "https://accounts.google.com",
ClientID: "YOUR_CLIENT_ID",
ClientSecret: "YOUR_CLIENT_SECRET",
ExtraScopes: []string{"email"},
},
SkipOpenBrowser: true,
ListenPort: []int{8000},
CACertFilename: "/path/to/cert",
SkipTLSVerify: true,
}).
Return(&auth.Output{
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
IDTokenSubject: "YOUR_SUBJECT",
IDTokenExpiry: time.Now().Add(time.Hour),
IDTokenClaims: map[string]string{"iss": "https://accounts.google.com"},
}, nil)
u := Setup{
Authentication: mockAuthentication,
Logger: mock_logger.New(t),
}
if err := u.DoStage2(ctx, in); err != nil {
t.Errorf("DoStage2 returned error: %+v", err)
}
}

View File

@@ -2,6 +2,8 @@ package standalone
import (
"context"
"strings"
"text/template"
"github.com/google/wire"
"github.com/int128/kubelogin/pkg/adaptors/kubeconfig"
@@ -35,12 +37,9 @@ type Input struct {
SkipTLSVerify bool
}
const oidcConfigErrorMessage = `No OIDC configuration found. Did you setup kubectl for OIDC authentication?
kubectl config set-credentials CONTEXT_NAME \
--auth-provider oidc \
--auth-provider-arg idp-issuer-url=https://issuer.example.com \
--auth-provider-arg client-id=YOUR_CLIENT_ID \
--auth-provider-arg client-secret=YOUR_CLIENT_SECRET`
const oidcConfigErrorMessage = `You need to set up the kubeconfig for OpenID Connect authentication.
See https://github.com/int128/kubelogin for more.
`
// Standalone provides the use case of explicit login.
//
@@ -62,6 +61,9 @@ func (u *Standalone) Do(ctx context.Context, in Input) error {
u.Logger.Printf(oidcConfigErrorMessage)
return xerrors.Errorf("could not find the current authentication provider: %w", err)
}
if err := u.showDeprecation(in, authProvider); err != nil {
return xerrors.Errorf("could not show deprecation message: %w", err)
}
u.Logger.V(1).Infof("using the authentication provider of the user %s", authProvider.UserName)
u.Logger.V(1).Infof("a token will be written to %s", authProvider.LocationOfOrigin)
@@ -94,3 +96,65 @@ func (u *Standalone) Do(ctx context.Context, in Input) error {
}
return nil
}
var deprecationTpl = template.Must(template.New("").Parse(
`IMPORTANT NOTICE:
The credential plugin mode is available since v1.14.0.
Kubectl will automatically run kubelogin and you do not need to run kubelogin explicitly.
You can switch to the credential plugin mode by setting the following user to
{{ .Kubeconfig }}.
---
users:
- name: oidc
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
command: kubectl
args:
- oidc-login
- get-token
{{- range .Args }}
- {{ . }}
{{- end }}
---
See https://github.com/int128/kubelogin for more.
`))
type deprecationVars struct {
Kubeconfig string
Args []string
}
func (u *Standalone) showDeprecation(in Input, p *kubeconfig.AuthProvider) error {
var args []string
args = append(args, "--oidc-issuer-url="+p.OIDCConfig.IDPIssuerURL)
args = append(args, "--oidc-client-id="+p.OIDCConfig.ClientID)
if p.OIDCConfig.ClientSecret != "" {
args = append(args, "--oidc-client-secret="+p.OIDCConfig.ClientSecret)
}
for _, extraScope := range p.OIDCConfig.ExtraScopes {
args = append(args, "--oidc-extra-scope="+extraScope)
}
if p.OIDCConfig.IDPCertificateAuthority != "" {
args = append(args, "--certificate-authority="+p.OIDCConfig.IDPCertificateAuthority)
}
if in.CACertFilename != "" {
args = append(args, "--certificate-authority="+in.CACertFilename)
}
if in.Username != "" {
args = append(args, "--username="+in.Username)
}
v := deprecationVars{
Kubeconfig: p.LocationOfOrigin,
Args: args,
}
var b strings.Builder
if err := deprecationTpl.Execute(&b, &v); err != nil {
return xerrors.Errorf("could not render the template: %w", err)
}
u.Logger.Printf("%s", b.String())
return nil
}