mirror of
https://github.com/int128/kubelogin.git
synced 2026-03-01 08:20:20 +00:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2b0c101af | ||
|
|
e8161d5a47 | ||
|
|
a3946c7f5f | ||
|
|
6b880febdb | ||
|
|
a51c15aec2 | ||
|
|
77a6b91be8 | ||
|
|
9e27385c0b | ||
|
|
3c50431a09 | ||
|
|
e41fdf3dcd | ||
|
|
dd93a6537d | ||
|
|
822f6c86de |
@@ -1,6 +1,6 @@
|
||||
on: [push]
|
||||
jobs:
|
||||
acceptance-test:
|
||||
system-test:
|
||||
# https://help.github.com/en/actions/automating-your-workflow-with-github-actions/software-installed-on-github-hosted-runners#ubuntu-1804-lts
|
||||
runs-on: ubuntu-18.04
|
||||
steps:
|
||||
@@ -22,7 +22,8 @@ jobs:
|
||||
sudo mv ./kind /usr/local/bin/kind
|
||||
kind version
|
||||
# https://packages.ubuntu.com/xenial/libnss3-tools
|
||||
- run: sudo apt update
|
||||
- run: sudo apt install -y libnss3-tools
|
||||
- run: echo '127.0.0.1 dex-server' | sudo tee -a /etc/hosts
|
||||
- run: make -C acceptance_test -j3 setup
|
||||
- run: make -C acceptance_test test
|
||||
- run: make -C system_test -j3 setup
|
||||
- run: make -C system_test test
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,5 +1,6 @@
|
||||
/.idea
|
||||
|
||||
/system_test/output/
|
||||
/acceptance_test/output/
|
||||
|
||||
/dist/output
|
||||
|
||||
27
README.md
27
README.md
@@ -1,4 +1,4 @@
|
||||
# kubelogin [](https://circleci.com/gh/int128/kubelogin)  [](https://goreportcard.com/report/github.com/int128/kubelogin)
|
||||
# kubelogin [](https://circleci.com/gh/int128/kubelogin)  [](https://goreportcard.com/report/github.com/int128/kubelogin)
|
||||
|
||||
This is a kubectl plugin for [Kubernetes OpenID Connect (OIDC) authentication](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#openid-connect-tokens), also known as `kubectl oidc-login`.
|
||||
|
||||
@@ -46,7 +46,7 @@ users:
|
||||
- --oidc-client-secret=YOUR_CLIENT_SECRET
|
||||
```
|
||||
|
||||
See [the setup guide](docs/setup.md) for more.
|
||||
See [setup guide](docs/setup.md) for more.
|
||||
|
||||
|
||||
### Run
|
||||
@@ -83,21 +83,26 @@ 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.
|
||||
|
||||
You can dump the claims of token by passing `-v1` option.
|
||||
You can dump claims of an ID token by `setup` command.
|
||||
|
||||
```
|
||||
I0221 21:54:08.151850 28231 get_token.go:104] you got a token: {
|
||||
```console
|
||||
% kubectl oidc-login setup --oidc-issuer-url https://accounts.google.com --oidc-client-id REDACTED --oidc-client-secret REDACTED
|
||||
authentication in progress...
|
||||
|
||||
## 2. Verify authentication
|
||||
|
||||
You got a token with the following claims:
|
||||
|
||||
{
|
||||
"sub": "********",
|
||||
"iss": "https://accounts.google.com",
|
||||
"aud": "********",
|
||||
"iat": 1582289639,
|
||||
"exp": 1582293239,
|
||||
"jti": "********",
|
||||
"nonce": "********",
|
||||
"at_hash": "********"
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
You can verify kubelogin works with your provider using [acceptance test](acceptance_test).
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -323,4 +328,4 @@ make
|
||||
./kubelogin
|
||||
```
|
||||
|
||||
See also [the acceptance test](acceptance_test).
|
||||
See also [the system test](system_test).
|
||||
|
||||
@@ -1,100 +1,31 @@
|
||||
CLUSTER_NAME := kubelogin-acceptance-test
|
||||
OUTPUT_DIR := $(CURDIR)/output
|
||||
|
||||
PATH := $(PATH):$(OUTPUT_DIR)/bin
|
||||
export PATH
|
||||
KUBECONFIG := $(OUTPUT_DIR)/kubeconfig.yaml
|
||||
export KUBECONFIG
|
||||
|
||||
# run the login script instead of opening chrome
|
||||
BROWSER := $(OUTPUT_DIR)/bin/chromelogin
|
||||
export BROWSER
|
||||
|
||||
.PHONY: test
|
||||
test: build
|
||||
# see the setup instruction
|
||||
kubectl oidc-login setup \
|
||||
--oidc-issuer-url=https://dex-server:10443/dex \
|
||||
--oidc-client-id=YOUR_CLIENT_ID \
|
||||
--oidc-client-secret=YOUR_CLIENT_SECRET \
|
||||
--oidc-extra-scope=email \
|
||||
--certificate-authority=$(OUTPUT_DIR)/ca.crt
|
||||
# set up the kubeconfig
|
||||
kubectl config set-credentials oidc \
|
||||
--exec-api-version=client.authentication.k8s.io/v1beta1 \
|
||||
--exec-command=kubectl \
|
||||
--exec-arg=oidc-login \
|
||||
--exec-arg=get-token \
|
||||
--exec-arg=--oidc-issuer-url=https://dex-server:10443/dex \
|
||||
--exec-arg=--oidc-client-id=YOUR_CLIENT_ID \
|
||||
--exec-arg=--oidc-client-secret=YOUR_CLIENT_SECRET \
|
||||
--exec-arg=--oidc-extra-scope=email \
|
||||
--exec-arg=--certificate-authority=$(OUTPUT_DIR)/ca.crt
|
||||
# make sure we can access the cluster
|
||||
kubectl --user=oidc cluster-info
|
||||
# switch the current context
|
||||
kubectl config set-context --current --user=oidc
|
||||
# make sure we can access the cluster
|
||||
kubectl cluster-info
|
||||
|
||||
.PHONY: setup
|
||||
setup: build dex cluster setup-chrome
|
||||
|
||||
.PHONY: setup-chrome
|
||||
setup-chrome: $(OUTPUT_DIR)/ca.crt
|
||||
# add the dex server certificate to the trust store
|
||||
mkdir -p ~/.pki/nssdb
|
||||
cd ~/.pki/nssdb && certutil -A -d sql:. -n dex -i $(OUTPUT_DIR)/ca.crt -t "TC,,"
|
||||
|
||||
# build binaries
|
||||
.PHONY: build
|
||||
build: $(OUTPUT_DIR)/bin/kubectl-oidc_login $(OUTPUT_DIR)/bin/chromelogin
|
||||
$(OUTPUT_DIR)/bin/kubectl-oidc_login:
|
||||
go build -o $@ ..
|
||||
$(OUTPUT_DIR)/bin/chromelogin: chromelogin/main.go
|
||||
go build -o $@ ./chromelogin
|
||||
|
||||
# create a Dex server
|
||||
.PHONY: dex
|
||||
dex: $(OUTPUT_DIR)/server.crt $(OUTPUT_DIR)/server.key
|
||||
docker create --name dex-server -p 10443:10443 --network kind quay.io/dexidp/dex:v2.21.0 serve /dex.yaml
|
||||
docker cp $(OUTPUT_DIR)/server.crt dex-server:/
|
||||
docker cp $(OUTPUT_DIR)/server.key dex-server:/
|
||||
docker cp dex.yaml dex-server:/
|
||||
docker start dex-server
|
||||
docker logs dex-server
|
||||
|
||||
$(OUTPUT_DIR)/ca.key:
|
||||
mkdir -p $(OUTPUT_DIR)
|
||||
openssl genrsa -out $@ 2048
|
||||
$(OUTPUT_DIR)/ca.csr: $(OUTPUT_DIR)/ca.key
|
||||
openssl req -new -key $(OUTPUT_DIR)/ca.key -out $@ -subj "/CN=dex-ca" -config openssl.cnf
|
||||
$(OUTPUT_DIR)/ca.crt: $(OUTPUT_DIR)/ca.key $(OUTPUT_DIR)/ca.csr
|
||||
openssl x509 -req -in $(OUTPUT_DIR)/ca.csr -signkey $(OUTPUT_DIR)/ca.key -out $@ -days 10
|
||||
$(OUTPUT_DIR)/server.key:
|
||||
mkdir -p $(OUTPUT_DIR)
|
||||
openssl genrsa -out $@ 2048
|
||||
$(OUTPUT_DIR)/server.csr: openssl.cnf $(OUTPUT_DIR)/server.key
|
||||
openssl req -new -key $(OUTPUT_DIR)/server.key -out $@ -subj "/CN=dex-server" -config openssl.cnf
|
||||
$(OUTPUT_DIR)/server.crt: openssl.cnf $(OUTPUT_DIR)/server.csr $(OUTPUT_DIR)/ca.crt $(OUTPUT_DIR)/ca.key
|
||||
openssl x509 -req -in $(OUTPUT_DIR)/server.csr -CA $(OUTPUT_DIR)/ca.crt -CAkey $(OUTPUT_DIR)/ca.key -CAcreateserial -out $@ -sha256 -days 10 -extensions v3_req -extfile openssl.cnf
|
||||
|
||||
# create a Kubernetes cluster
|
||||
.PHONY: cluster
|
||||
cluster: dex create-cluster
|
||||
# add the Dex container IP to /etc/hosts of kube-apiserver
|
||||
docker inspect -f '{{.NetworkSettings.IPAddress}}' dex-server | sed -e 's,$$, dex-server,' | \
|
||||
kubectl -n kube-system exec -i kube-apiserver-$(CLUSTER_NAME)-control-plane -- tee -a /etc/hosts
|
||||
# wait for kube-apiserver oidc initialization
|
||||
# (oidc authenticator will retry oidc discovery every 10s)
|
||||
sleep 10
|
||||
|
||||
.PHONY: create-cluster
|
||||
create-cluster: $(OUTPUT_DIR)/ca.crt
|
||||
cp $(OUTPUT_DIR)/ca.crt /tmp/kubelogin-acceptance-test-dex-ca.crt
|
||||
kind create cluster --name $(CLUSTER_NAME) --config cluster.yaml
|
||||
cluster:
|
||||
# create a cluster
|
||||
mkdir -p $(OUTPUT_DIR)
|
||||
sed -e "s|OIDC_ISSUER_URL|$(OIDC_ISSUER_URL)|" -e "s|OIDC_CLIENT_ID|$(OIDC_CLIENT_ID)|" cluster.yaml > $(OUTPUT_DIR)/cluster.yaml
|
||||
kind create cluster --name $(CLUSTER_NAME) --config $(OUTPUT_DIR)/cluster.yaml
|
||||
# set up access control
|
||||
kubectl create clusterrole cluster-readonly --verb=get,watch,list --resource='*.*'
|
||||
kubectl create clusterrolebinding cluster-readonly --clusterrole=cluster-readonly --user=admin@example.com
|
||||
kubectl create clusterrolebinding cluster-readonly --clusterrole=cluster-readonly --user=$(YOUR_EMAIL)
|
||||
# set up kubectl
|
||||
kubectl config set-credentials oidc \
|
||||
--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
|
||||
kubectl config set-context --current --user=oidc
|
||||
|
||||
# clean up the resources
|
||||
.PHONY: clean
|
||||
@@ -103,7 +34,9 @@ clean:
|
||||
.PHONY: delete-cluster
|
||||
delete-cluster:
|
||||
kind delete cluster --name $(CLUSTER_NAME)
|
||||
.PHONY: delete-dex
|
||||
delete-dex:
|
||||
docker stop dex-server
|
||||
docker rm dex-server
|
||||
|
||||
.PHONY: check
|
||||
check:
|
||||
docker version
|
||||
kind version
|
||||
kubectl version --client
|
||||
|
||||
@@ -1,109 +1,75 @@
|
||||
# kubelogin/acceptance_test
|
||||
|
||||
This is an acceptance test for walkthrough of the OIDC initial setup and plugin behavior using a real Kubernetes cluster and OpenID Connect provider, running on [GitHub Actions](https://github.com/int128/kubelogin/actions?query=workflow%3Aacceptance-test).
|
||||
|
||||
It is intended to verify the following points:
|
||||
|
||||
- User can set up Kubernetes OIDC authentication and this plugin.
|
||||
- User can access a cluster after login.
|
||||
|
||||
It performs the test using the following components:
|
||||
|
||||
- Kubernetes cluster (Kind)
|
||||
- OIDC provider (Dex)
|
||||
- Browser (Chrome)
|
||||
- kubectl command
|
||||
This is a manual test for verifying Kubernetes OIDC authentication with your OIDC provider.
|
||||
|
||||
|
||||
## How it works
|
||||
## Purpose
|
||||
|
||||
Let's take a look at the diagram.
|
||||
This test checks the following points:
|
||||
|
||||

|
||||
|
||||
It prepares the following resources:
|
||||
|
||||
1. Generate a pair of CA certificate and TLS server certificate for Dex.
|
||||
1. Run Dex on a container.
|
||||
1. Create a Kubernetes cluster using Kind.
|
||||
1. Mutate `/etc/hosts` of the CI machine to access Dex.
|
||||
1. Mutate `/etc/hosts` of the kube-apiserver pod to access Dex.
|
||||
|
||||
It performs the test by the following steps:
|
||||
|
||||
1. Run kubectl.
|
||||
1. kubectl automatically runs kubelogin.
|
||||
1. kubelogin automatically runs [chromelogin](chromelogin).
|
||||
1. chromelogin opens the browser, navigates to `http://localhost:8000` and enter the username and password.
|
||||
1. kubelogin gets an authorization code from the browser.
|
||||
1. kubelogin gets a token.
|
||||
1. kubectl accesses an API with the token.
|
||||
1. kube-apiserver verifies the token by Dex.
|
||||
1. Check if kubectl exited with code 0.
|
||||
1. You can set up your OIDC provider using [setup guide](../docs/setup.md).
|
||||
1. The plugin works with your OIDC provider.
|
||||
|
||||
|
||||
## Run locally
|
||||
## Getting Started
|
||||
|
||||
You need to set up the following components:
|
||||
### Prerequisite
|
||||
|
||||
You need to build the plugin into the parent directory.
|
||||
|
||||
```sh
|
||||
make -C ..
|
||||
```
|
||||
|
||||
You need to set up your provider.
|
||||
See [setup guide](../docs/setup.md) for more.
|
||||
|
||||
You need to install the following tools:
|
||||
|
||||
- Docker
|
||||
- Kind
|
||||
- Chrome or Chromium
|
||||
- kubectl
|
||||
|
||||
You need to add the following line to `/etc/hosts` so that the browser can access the Dex.
|
||||
You can check if the tools are available.
|
||||
|
||||
```
|
||||
127.0.0.1 dex-server
|
||||
```sh
|
||||
make check
|
||||
```
|
||||
|
||||
Run the test.
|
||||
### 1. Create a cluster
|
||||
|
||||
```shell script
|
||||
# run the test
|
||||
make
|
||||
Create a cluster.
|
||||
For example, you can create a cluster with Google account authentication.
|
||||
|
||||
# clean up
|
||||
```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
|
||||
```
|
||||
|
||||
It will do the following steps:
|
||||
|
||||
1. Create a cluster.
|
||||
1. Set up access control. It allows read-only access from your email address.
|
||||
1. Set up kubectl to enable the plugin.
|
||||
|
||||
You can change kubectl configuration in generated `output/kubeconfig.yaml`.
|
||||
|
||||
### 2. Run kubectl
|
||||
|
||||
Make sure you can log in to the provider and access the cluster.
|
||||
|
||||
```console
|
||||
% export KUBECONFIG=$PWD/output/kubeconfig.yaml
|
||||
% kubectl get pods -A
|
||||
```
|
||||
|
||||
### Clean up
|
||||
|
||||
To delete the cluster and generated files:
|
||||
|
||||
```sh
|
||||
make delete-cluster
|
||||
make delete-dex
|
||||
make clean
|
||||
```
|
||||
|
||||
|
||||
## Technical consideration
|
||||
|
||||
### Network and DNS
|
||||
|
||||
Consider the following issues:
|
||||
|
||||
- kube-apiserver runs on the host network of the kind container.
|
||||
- kube-apiserver cannot resolve a service name by kube-dns.
|
||||
- kube-apiserver cannot access a cluster IP.
|
||||
- kube-apiserver can access another container via the Docker network.
|
||||
- Chrome requires exactly match of domain name between Dex URL and a server certificate.
|
||||
|
||||
Consequently,
|
||||
|
||||
- kube-apiserver accesses Dex by resolving `/etc/hosts` and via the Docker network.
|
||||
- kubelogin and Chrome accesses Dex by resolving `/etc/hosts` and via the Docker network.
|
||||
|
||||
### TLS server certificate
|
||||
|
||||
Consider the following issues:
|
||||
|
||||
- kube-apiserver requires `--oidc-issuer` is HTTPS URL.
|
||||
- kube-apiserver requires a CA certificate at startup, if `--oidc-ca-file` is given.
|
||||
- kube-apiserver mounts `/usr/local/share/ca-certificates` from the kind container.
|
||||
- It is possible to mount a file from the CI machine.
|
||||
- It is not possible to issue a certificate using Let's Encrypt in runtime.
|
||||
- Chrome requires a valid certificate in `~/.pki/nssdb`.
|
||||
|
||||
As a result,
|
||||
|
||||
- kube-apiserver uses the CA certificate of `/usr/local/share/ca-certificates/dex-ca.crt`. See the `extraMounts` section of [`cluster.yaml`](cluster.yaml).
|
||||
- kubelogin uses the CA certificate in `output/ca.crt`.
|
||||
- Chrome uses the CA certificate in `~/.pki/nssdb`.
|
||||
|
||||
### Test environment
|
||||
|
||||
- Set the issuer URL to kubectl. See [`kubeconfig_oidc.yaml`](kubeconfig_oidc.yaml).
|
||||
- Set the issuer URL to kube-apiserver. See [`cluster.yaml`](cluster.yaml).
|
||||
- Set `BROWSER` environment variable to run [`chromelogin`](chromelogin) by `xdg-open`.
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
kind: Cluster
|
||||
apiVersion: kind.x-k8s.io/v1alpha4
|
||||
# https://github.com/dexidp/dex/blob/master/Documentation/kubernetes.md
|
||||
kubeadmConfigPatches:
|
||||
- |
|
||||
apiVersion: kubeadm.k8s.io/v1beta2
|
||||
@@ -9,12 +8,6 @@ kubeadmConfigPatches:
|
||||
name: config
|
||||
apiServer:
|
||||
extraArgs:
|
||||
oidc-issuer-url: https://dex-server:10443/dex
|
||||
oidc-client-id: YOUR_CLIENT_ID
|
||||
oidc-issuer-url: OIDC_ISSUER_URL
|
||||
oidc-client-id: OIDC_CLIENT_ID
|
||||
oidc-username-claim: email
|
||||
oidc-ca-file: /usr/local/share/ca-certificates/dex-ca.crt
|
||||
nodes:
|
||||
- role: control-plane
|
||||
extraMounts:
|
||||
- hostPath: /tmp/kubelogin-acceptance-test-dex-ca.crt
|
||||
containerPath: /usr/local/share/ca-certificates/dex-ca.crt
|
||||
|
||||
2
dist/oidc-login.yaml
vendored
2
dist/oidc-login.yaml
vendored
@@ -22,8 +22,6 @@ spec:
|
||||
|
||||
caveats: |
|
||||
You need to setup the OIDC provider, Kubernetes API server, role binding and kubeconfig.
|
||||
See https://github.com/int128/kubelogin for more.
|
||||
|
||||
version: {{ env "VERSION" }}
|
||||
platforms:
|
||||
- uri: https://github.com/int128/kubelogin/releases/download/{{ env "VERSION" }}/kubelogin_linux_amd64.zip
|
||||
|
||||
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
6
go.mod
6
go.mod
@@ -7,7 +7,7 @@ require (
|
||||
github.com/coreos/go-oidc v2.2.1+incompatible
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
github.com/golang/mock v1.4.3
|
||||
github.com/google/go-cmp v0.4.1
|
||||
github.com/google/go-cmp v0.5.0
|
||||
github.com/google/wire v0.4.0
|
||||
github.com/int128/oauth2cli v1.11.0
|
||||
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4
|
||||
@@ -20,7 +20,7 @@ require (
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543
|
||||
gopkg.in/square/go-jose.v2 v2.3.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.3.0
|
||||
k8s.io/apimachinery v0.18.3
|
||||
k8s.io/client-go v0.18.3
|
||||
k8s.io/apimachinery v0.18.4
|
||||
k8s.io/client-go v0.18.4
|
||||
k8s.io/klog v1.0.0
|
||||
)
|
||||
|
||||
16
go.sum
16
go.sum
@@ -84,8 +84,8 @@ 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/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.4.1 h1:/exdXoGamhu5ONeUJH0deniYLWYvQwW66yvlfiiKTu0=
|
||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g=
|
||||
@@ -315,12 +315,12 @@ gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
k8s.io/api v0.18.3 h1:2AJaUQdgUZLoDZHrun21PW2Nx9+ll6cUzvn3IKhSIn0=
|
||||
k8s.io/api v0.18.3/go.mod h1:UOaMwERbqJMfeeeHc8XJKawj4P9TgDRnViIqqBeH2QA=
|
||||
k8s.io/apimachinery v0.18.3 h1:pOGcbVAhxADgUYnjS08EFXs9QMl8qaH5U4fr5LGUrSk=
|
||||
k8s.io/apimachinery v0.18.3/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko=
|
||||
k8s.io/client-go v0.18.3 h1:QaJzz92tsN67oorwzmoB0a9r9ZVHuD5ryjbCKP0U22k=
|
||||
k8s.io/client-go v0.18.3/go.mod h1:4a/dpQEvzAhT1BbuWW09qvIaGw6Gbu1gZYiQZIi1DMw=
|
||||
k8s.io/api v0.18.4 h1:8x49nBRxuXGUlDlwlWd3RMY1SayZrzFfxea3UZSkFw4=
|
||||
k8s.io/api v0.18.4/go.mod h1:lOIQAKYgai1+vz9J7YcDZwC26Z0zQewYOGWdyIPUUQ4=
|
||||
k8s.io/apimachinery v0.18.4 h1:ST2beySjhqwJoIFk6p7Hp5v5O0hYY6Gngq/gUYXTPIA=
|
||||
k8s.io/apimachinery v0.18.4/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko=
|
||||
k8s.io/client-go v0.18.4 h1:un55V1Q/B3JO3A76eS0kUSywgGK/WR3BQ8fHQjNa6Zc=
|
||||
k8s.io/client-go v0.18.4/go.mod h1:f5sXwL4yAZRkAtzOxRWUhA/N8XzGCb+nPZI8PfobZ9g=
|
||||
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
|
||||
k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
|
||||
k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
|
||||
|
||||
@@ -48,6 +48,7 @@ type GetTokenByAuthCodeInput struct {
|
||||
PKCEParams pkce.Params
|
||||
RedirectURLHostname string
|
||||
AuthRequestExtraParams map[string]string
|
||||
LocalServerSuccessHTML string
|
||||
}
|
||||
|
||||
// TokenSet represents an output DTO of
|
||||
@@ -85,6 +86,7 @@ func (c *client) GetTokenByAuthCode(ctx context.Context, in GetTokenByAuthCodeIn
|
||||
LocalServerBindAddress: in.BindAddress,
|
||||
LocalServerReadyChan: localServerReadyChan,
|
||||
RedirectURLHostname: in.RedirectURLHostname,
|
||||
LocalServerSuccessHTML: in.LocalServerSuccessHTML,
|
||||
}
|
||||
token, err := oauth2cli.GetToken(ctx, config)
|
||||
if err != nil {
|
||||
|
||||
35
pkg/templates/authcode_browser.go
Normal file
35
pkg/templates/authcode_browser.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package templates
|
||||
|
||||
// AuthCodeBrowserSuccessHTML is the success page on browser based authentication.
|
||||
const AuthCodeBrowserSuccessHTML = `
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Authenticated</title>
|
||||
<script>
|
||||
window.close()
|
||||
</script>
|
||||
<style>
|
||||
body {
|
||||
background-color: #eee;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
.placeholder {
|
||||
margin: 2em;
|
||||
padding: 2em;
|
||||
background-color: #fff;
|
||||
border-radius: 1em;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="placeholder">
|
||||
<h1>Authenticated</h1>
|
||||
<p>You have logged in to the cluster. You can close this window.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
29
pkg/templates/httpserver/httpserver.go
Normal file
29
pkg/templates/httpserver/httpserver.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/int128/kubelogin/pkg/templates"
|
||||
)
|
||||
|
||||
func main() {
|
||||
http.HandleFunc("/AuthCodeBrowserSuccessHTML", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Add("content-type", "text/html")
|
||||
_, _ = w.Write([]byte(templates.AuthCodeBrowserSuccessHTML))
|
||||
})
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Add("content-type", "text/html")
|
||||
_, _ = w.Write([]byte(`
|
||||
<html>
|
||||
<body>
|
||||
<ul>
|
||||
<li><a href="AuthCodeBrowserSuccessHTML">AuthCodeBrowserSuccessHTML</a></li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
`))
|
||||
})
|
||||
log.Printf("http://localhost:8000")
|
||||
log.Fatal(http.ListenAndServe("127.0.0.1:8000", nil))
|
||||
}
|
||||
6
pkg/templates/package.go
Normal file
6
pkg/templates/package.go
Normal file
@@ -0,0 +1,6 @@
|
||||
// Package templates provides templates such as HTML and messages.
|
||||
//
|
||||
// You can preview HTML pages by running httpserver package.
|
||||
// go run ./httpserver
|
||||
//
|
||||
package templates
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/int128/kubelogin/pkg/adaptors/oidcclient"
|
||||
"github.com/int128/kubelogin/pkg/domain/oidc"
|
||||
"github.com/int128/kubelogin/pkg/domain/pkce"
|
||||
"github.com/int128/kubelogin/pkg/templates"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
@@ -39,6 +40,7 @@ func (u *AuthCode) Do(ctx context.Context, o *AuthCodeOption, client oidcclient.
|
||||
PKCEParams: p,
|
||||
RedirectURLHostname: o.RedirectURLHostname,
|
||||
AuthRequestExtraParams: o.AuthRequestExtraParams,
|
||||
LocalServerSuccessHTML: templates.AuthCodeBrowserSuccessHTML,
|
||||
}
|
||||
readyChan := make(chan string, 1)
|
||||
defer close(readyChan)
|
||||
109
system_test/Makefile
Normal file
109
system_test/Makefile
Normal file
@@ -0,0 +1,109 @@
|
||||
CLUSTER_NAME := kubelogin-system-test
|
||||
OUTPUT_DIR := $(CURDIR)/output
|
||||
|
||||
PATH := $(PATH):$(OUTPUT_DIR)/bin
|
||||
export PATH
|
||||
KUBECONFIG := $(OUTPUT_DIR)/kubeconfig.yaml
|
||||
export KUBECONFIG
|
||||
|
||||
# run the login script instead of opening chrome
|
||||
BROWSER := $(OUTPUT_DIR)/bin/chromelogin
|
||||
export BROWSER
|
||||
|
||||
.PHONY: test
|
||||
test: build
|
||||
# see the setup instruction
|
||||
kubectl oidc-login setup \
|
||||
--oidc-issuer-url=https://dex-server:10443/dex \
|
||||
--oidc-client-id=YOUR_CLIENT_ID \
|
||||
--oidc-client-secret=YOUR_CLIENT_SECRET \
|
||||
--oidc-extra-scope=email \
|
||||
--certificate-authority=$(OUTPUT_DIR)/ca.crt
|
||||
# set up the kubeconfig
|
||||
kubectl config set-credentials oidc \
|
||||
--exec-api-version=client.authentication.k8s.io/v1beta1 \
|
||||
--exec-command=kubectl \
|
||||
--exec-arg=oidc-login \
|
||||
--exec-arg=get-token \
|
||||
--exec-arg=--oidc-issuer-url=https://dex-server:10443/dex \
|
||||
--exec-arg=--oidc-client-id=YOUR_CLIENT_ID \
|
||||
--exec-arg=--oidc-client-secret=YOUR_CLIENT_SECRET \
|
||||
--exec-arg=--oidc-extra-scope=email \
|
||||
--exec-arg=--certificate-authority=$(OUTPUT_DIR)/ca.crt
|
||||
# make sure we can access the cluster
|
||||
kubectl --user=oidc cluster-info
|
||||
# switch the current context
|
||||
kubectl config set-context --current --user=oidc
|
||||
# make sure we can access the cluster
|
||||
kubectl cluster-info
|
||||
|
||||
.PHONY: setup
|
||||
setup: build dex cluster setup-chrome
|
||||
|
||||
.PHONY: setup-chrome
|
||||
setup-chrome: $(OUTPUT_DIR)/ca.crt
|
||||
# add the dex server certificate to the trust store
|
||||
mkdir -p ~/.pki/nssdb
|
||||
cd ~/.pki/nssdb && certutil -A -d sql:. -n dex -i $(OUTPUT_DIR)/ca.crt -t "TC,,"
|
||||
|
||||
# build binaries
|
||||
.PHONY: build
|
||||
build: $(OUTPUT_DIR)/bin/kubectl-oidc_login $(OUTPUT_DIR)/bin/chromelogin
|
||||
$(OUTPUT_DIR)/bin/kubectl-oidc_login:
|
||||
go build -o $@ ..
|
||||
$(OUTPUT_DIR)/bin/chromelogin: chromelogin/main.go
|
||||
go build -o $@ ./chromelogin
|
||||
|
||||
# create a Dex server
|
||||
.PHONY: dex
|
||||
dex: $(OUTPUT_DIR)/server.crt $(OUTPUT_DIR)/server.key
|
||||
docker create --name dex-server -p 10443:10443 --network kind quay.io/dexidp/dex:v2.21.0 serve /dex.yaml
|
||||
docker cp $(OUTPUT_DIR)/server.crt dex-server:/
|
||||
docker cp $(OUTPUT_DIR)/server.key dex-server:/
|
||||
docker cp dex.yaml dex-server:/
|
||||
docker start dex-server
|
||||
docker logs dex-server
|
||||
|
||||
$(OUTPUT_DIR)/ca.key:
|
||||
mkdir -p $(OUTPUT_DIR)
|
||||
openssl genrsa -out $@ 2048
|
||||
$(OUTPUT_DIR)/ca.csr: $(OUTPUT_DIR)/ca.key
|
||||
openssl req -new -key $(OUTPUT_DIR)/ca.key -out $@ -subj "/CN=dex-ca" -config openssl.cnf
|
||||
$(OUTPUT_DIR)/ca.crt: $(OUTPUT_DIR)/ca.key $(OUTPUT_DIR)/ca.csr
|
||||
openssl x509 -req -in $(OUTPUT_DIR)/ca.csr -signkey $(OUTPUT_DIR)/ca.key -out $@ -days 10
|
||||
$(OUTPUT_DIR)/server.key:
|
||||
mkdir -p $(OUTPUT_DIR)
|
||||
openssl genrsa -out $@ 2048
|
||||
$(OUTPUT_DIR)/server.csr: openssl.cnf $(OUTPUT_DIR)/server.key
|
||||
openssl req -new -key $(OUTPUT_DIR)/server.key -out $@ -subj "/CN=dex-server" -config openssl.cnf
|
||||
$(OUTPUT_DIR)/server.crt: openssl.cnf $(OUTPUT_DIR)/server.csr $(OUTPUT_DIR)/ca.crt $(OUTPUT_DIR)/ca.key
|
||||
openssl x509 -req -in $(OUTPUT_DIR)/server.csr -CA $(OUTPUT_DIR)/ca.crt -CAkey $(OUTPUT_DIR)/ca.key -CAcreateserial -out $@ -sha256 -days 10 -extensions v3_req -extfile openssl.cnf
|
||||
|
||||
# create a Kubernetes cluster
|
||||
.PHONY: cluster
|
||||
cluster: dex create-cluster
|
||||
# add the Dex container IP to /etc/hosts of kube-apiserver
|
||||
docker inspect -f '{{.NetworkSettings.IPAddress}}' dex-server | sed -e 's,$$, dex-server,' | \
|
||||
kubectl -n kube-system exec -i kube-apiserver-$(CLUSTER_NAME)-control-plane -- tee -a /etc/hosts
|
||||
# wait for kube-apiserver oidc initialization
|
||||
# (oidc authenticator will retry oidc discovery every 10s)
|
||||
sleep 10
|
||||
|
||||
.PHONY: create-cluster
|
||||
create-cluster: $(OUTPUT_DIR)/ca.crt
|
||||
cp $(OUTPUT_DIR)/ca.crt /tmp/kubelogin-system-test-dex-ca.crt
|
||||
kind create cluster --name $(CLUSTER_NAME) --config cluster.yaml
|
||||
kubectl create clusterrole cluster-readonly --verb=get,watch,list --resource='*.*'
|
||||
kubectl create clusterrolebinding cluster-readonly --clusterrole=cluster-readonly --user=admin@example.com
|
||||
|
||||
# clean up the resources
|
||||
.PHONY: clean
|
||||
clean:
|
||||
-rm -r $(OUTPUT_DIR)
|
||||
.PHONY: delete-cluster
|
||||
delete-cluster:
|
||||
kind delete cluster --name $(CLUSTER_NAME)
|
||||
.PHONY: delete-dex
|
||||
delete-dex:
|
||||
docker stop dex-server
|
||||
docker rm dex-server
|
||||
112
system_test/README.md
Normal file
112
system_test/README.md
Normal file
@@ -0,0 +1,112 @@
|
||||
# kubelogin/system_test
|
||||
|
||||
This is an automated test for verifying behavior of the plugin with a real Kubernetes cluster and OIDC provider.
|
||||
|
||||
|
||||
## Purpose
|
||||
|
||||
This test checks the following points:
|
||||
|
||||
1. User can set up Kubernetes OIDC authentication using [setup guide](../docs/setup.md).
|
||||
1. User can log in to an OIDC provider on a browser.
|
||||
1. User can access the cluster using a token returned from the plugin.
|
||||
|
||||
It depends on the following components:
|
||||
|
||||
- Kubernetes cluster (Kind)
|
||||
- OIDC provider (Dex)
|
||||
- Browser (Chrome)
|
||||
- kubectl command
|
||||
|
||||
|
||||
## How it works
|
||||
|
||||
Let's take a look at the diagram.
|
||||
|
||||

|
||||
|
||||
It prepares the following resources:
|
||||
|
||||
1. Generate a pair of CA certificate and TLS server certificate for Dex.
|
||||
1. Run Dex on a container.
|
||||
1. Create a Kubernetes cluster using Kind.
|
||||
1. Mutate `/etc/hosts` of the CI machine to access Dex.
|
||||
1. Mutate `/etc/hosts` of the kube-apiserver pod to access Dex.
|
||||
|
||||
It performs the test by the following steps:
|
||||
|
||||
1. Run kubectl.
|
||||
1. kubectl automatically runs kubelogin.
|
||||
1. kubelogin automatically runs [chromelogin](chromelogin).
|
||||
1. chromelogin opens the browser, navigates to `http://localhost:8000` and enter the username and password.
|
||||
1. kubelogin gets an authorization code from the browser.
|
||||
1. kubelogin gets a token.
|
||||
1. kubectl accesses an API with the token.
|
||||
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:
|
||||
|
||||
- Docker
|
||||
- Kind
|
||||
- Chrome or Chromium
|
||||
|
||||
You need to add the following line to `/etc/hosts` so that the browser can access the Dex.
|
||||
|
||||
```
|
||||
127.0.0.1 dex-server
|
||||
```
|
||||
|
||||
Run the test.
|
||||
|
||||
```shell script
|
||||
# run the test
|
||||
make
|
||||
|
||||
# clean up
|
||||
make delete-cluster
|
||||
make delete-dex
|
||||
```
|
||||
|
||||
|
||||
## Technical consideration
|
||||
|
||||
### Network and DNS
|
||||
|
||||
Consider the following issues:
|
||||
|
||||
- kube-apiserver runs on the host network of the kind container.
|
||||
- kube-apiserver cannot resolve a service name by kube-dns.
|
||||
- kube-apiserver cannot access a cluster IP.
|
||||
- kube-apiserver can access another container via the Docker network.
|
||||
- Chrome requires exactly match of domain name between Dex URL and a server certificate.
|
||||
|
||||
Consequently,
|
||||
|
||||
- kube-apiserver accesses Dex by resolving `/etc/hosts` and via the Docker network.
|
||||
- kubelogin and Chrome accesses Dex by resolving `/etc/hosts` and via the Docker network.
|
||||
|
||||
### TLS server certificate
|
||||
|
||||
Consider the following issues:
|
||||
|
||||
- kube-apiserver requires `--oidc-issuer` is HTTPS URL.
|
||||
- kube-apiserver requires a CA certificate at startup, if `--oidc-ca-file` is given.
|
||||
- kube-apiserver mounts `/usr/local/share/ca-certificates` from the kind container.
|
||||
- It is possible to mount a file from the CI machine.
|
||||
- It is not possible to issue a certificate using Let's Encrypt in runtime.
|
||||
- Chrome requires a valid certificate in `~/.pki/nssdb`.
|
||||
|
||||
As a result,
|
||||
|
||||
- kube-apiserver uses the CA certificate of `/usr/local/share/ca-certificates/dex-ca.crt`. See the `extraMounts` section of [`cluster.yaml`](cluster.yaml).
|
||||
- kubelogin uses the CA certificate in `output/ca.crt`.
|
||||
- Chrome uses the CA certificate in `~/.pki/nssdb`.
|
||||
|
||||
### Test environment
|
||||
|
||||
- Set the issuer URL to kube-apiserver. See [`cluster.yaml`](cluster.yaml).
|
||||
- Set `BROWSER` environment variable to run [`chromelogin`](chromelogin) by `xdg-open`.
|
||||
20
system_test/cluster.yaml
Normal file
20
system_test/cluster.yaml
Normal file
@@ -0,0 +1,20 @@
|
||||
kind: Cluster
|
||||
apiVersion: kind.x-k8s.io/v1alpha4
|
||||
# https://github.com/dexidp/dex/blob/master/Documentation/kubernetes.md
|
||||
kubeadmConfigPatches:
|
||||
- |
|
||||
apiVersion: kubeadm.k8s.io/v1beta2
|
||||
kind: ClusterConfiguration
|
||||
metadata:
|
||||
name: config
|
||||
apiServer:
|
||||
extraArgs:
|
||||
oidc-issuer-url: https://dex-server:10443/dex
|
||||
oidc-client-id: YOUR_CLIENT_ID
|
||||
oidc-username-claim: email
|
||||
oidc-ca-file: /usr/local/share/ca-certificates/dex-ca.crt
|
||||
nodes:
|
||||
- role: control-plane
|
||||
extraMounts:
|
||||
- hostPath: /tmp/kubelogin-system-test-dex-ca.crt
|
||||
containerPath: /usr/local/share/ca-certificates/dex-ca.crt
|
||||
Reference in New Issue
Block a user