mirror of
https://github.com/int128/kubelogin.git
synced 2026-04-22 09:16:37 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b776bac764 | ||
|
|
4bf77886a8 | ||
|
|
ea711f91b4 | ||
|
|
cfc6376f69 |
188
README.md
188
README.md
@@ -3,136 +3,208 @@
|
|||||||
`kubelogin` is a command to get an OpenID Connect (OIDC) token for `kubectl` authentication.
|
`kubelogin` is a command to get an OpenID Connect (OIDC) token for `kubectl` authentication.
|
||||||
|
|
||||||
|
|
||||||
## Getting Started
|
## TL;DR
|
||||||
|
|
||||||
Download [the latest release](https://github.com/int128/kubelogin/releases) and save it as `/usr/local/bin/kubelogin`.
|
1. Setup your OpenID Connect provider, e.g. Google Identity Platform or Keycloak.
|
||||||
|
1. Setup your Kubernetes cluster.
|
||||||
|
1. Setup your `kubectl`.
|
||||||
|
|
||||||
You have to configure `kubectl` to authenticate with OIDC.
|
```
|
||||||
See the later section for details.
|
% kubelogin --help
|
||||||
|
2018/08/15 19:08:58 Usage:
|
||||||
|
kubelogin [OPTIONS]
|
||||||
|
|
||||||
|
Application Options:
|
||||||
|
--kubeconfig= Path to the kubeconfig file (default: ~/.kube/config) [$KUBECONFIG]
|
||||||
|
--insecure-skip-tls-verify If set, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure
|
||||||
|
[$KUBELOGIN_INSECURE_SKIP_TLS_VERIFY]
|
||||||
|
|
||||||
|
Help Options:
|
||||||
|
-h, --help Show this help message
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Getting Started with Google Account
|
||||||
|
|
||||||
|
### 1. Setup Google API
|
||||||
|
|
||||||
|
Open [Google APIs Console](https://console.developers.google.com/apis/credentials) and create an OAuth client as follows:
|
||||||
|
|
||||||
|
- Application Type: Web application
|
||||||
|
- Redirect URL: `http://localhost:8000/`
|
||||||
|
|
||||||
|
### 2. Setup Kubernetes cluster
|
||||||
|
|
||||||
|
Configure your Kubernetes API Server accepts [OpenID Connect Tokens](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#openid-connect-tokens).
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
Here assign the `cluster-admin` role to your user.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
name: oidc-admin-group
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: cluster-admin
|
||||||
|
subjects:
|
||||||
|
- kind: User
|
||||||
|
name: https://accounts.google.com#1234567890
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Setup kubectl and kubelogin
|
||||||
|
|
||||||
|
Setup `kubectl` to authenticate with your identity provider.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
kubectl config set-credentials CLUSTER_NAME \
|
kubectl config set-credentials CLUSTER_NAME \
|
||||||
--auth-provider oidc \
|
--auth-provider oidc \
|
||||||
--auth-provider-arg idp-issuer-url=https://keycloak.example.com/auth/realms/hello \
|
--auth-provider-arg idp-issuer-url=https://accounts.google.com \
|
||||||
--auth-provider-arg client-id=kubernetes \
|
--auth-provider-arg client-id=YOUR_CLIENT_ID.apps.googleusercontent.com \
|
||||||
--auth-provider-arg client-secret=YOUR_CLIENT_SECRET
|
--auth-provider-arg client-secret=YOUR_CLIENT_SECRET
|
||||||
```
|
```
|
||||||
|
|
||||||
Run `kubelogin`.
|
Download [the latest release](https://github.com/int128/kubelogin/releases) and save it.
|
||||||
|
|
||||||
|
Run `kubelogin` and open http://localhost:8000 in your browser.
|
||||||
|
|
||||||
```
|
```
|
||||||
% kubelogin
|
% kubelogin
|
||||||
2018/08/10 10:36:38 Reading .kubeconfig
|
2018/08/10 10:36:38 Reading .kubeconfig
|
||||||
2018/08/10 10:36:38 Using current context: devops.hidetake.org
|
2018/08/10 10:36:38 Using current context: hello.k8s.local
|
||||||
2018/08/10 10:36:41 Open http://localhost:8000 for authorization
|
2018/08/10 10:36:41 Open http://localhost:8000 for authorization
|
||||||
2018/08/10 10:36:45 GET /
|
2018/08/10 10:36:45 GET /
|
||||||
2018/08/10 10:37:07 GET /?state=...&session_state=...&code=ey...
|
2018/08/10 10:37:07 GET /?state=...&session_state=...&code=ey...
|
||||||
2018/08/10 10:37:08 Updated .kubeconfig
|
2018/08/10 10:37:08 Updated .kubeconfig
|
||||||
```
|
```
|
||||||
|
|
||||||
Now your `~/.kube/config` looks like:
|
Now your `~/.kube/config` should be like:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
# ~/.kube/config (snip)
|
|
||||||
users:
|
users:
|
||||||
- name: hello.k8s.local
|
- name: hello.k8s.local
|
||||||
user:
|
user:
|
||||||
auth-provider:
|
auth-provider:
|
||||||
config:
|
config:
|
||||||
idp-issuer-url: https://keycloak.example.com/auth/realms/hello
|
idp-issuer-url: https://accounts.google.com
|
||||||
client-id: kubernetes
|
client-id: YOUR_CLIENT_ID.apps.googleusercontent.com
|
||||||
client-secret: YOUR_SECRET
|
client-secret: YOUR_SECRET
|
||||||
id-token: ey... # kubelogin will update ID token here
|
id-token: ey... # kubelogin will update ID token here
|
||||||
refresh-token: ey... # kubelogin will update refresh token here
|
refresh-token: ey... # kubelogin will update refresh token here
|
||||||
name: oidc
|
name: oidc
|
||||||
```
|
```
|
||||||
|
|
||||||
Make sure you can access to the Kubernetes cluster:
|
Make sure you can access to the Kubernetes cluster.
|
||||||
|
|
||||||
```
|
```
|
||||||
% kubectl version
|
% kubectl get nodes
|
||||||
Client Version: version.Info{...}
|
NAME STATUS ROLES AGE VERSION
|
||||||
Server Version: version.Info{...}
|
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
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Configuration
|
## Getting Started with Keycloak
|
||||||
|
|
||||||
You can set the following environment variable:
|
### 1. Setup Keycloak
|
||||||
|
|
||||||
- `KUBECONFIG` - Path to the config. Defaults to `~/.kube/config`.
|
Create an OIDC client as follows:
|
||||||
|
|
||||||
|
- Redirect URL: `http://localhost:8000/`
|
||||||
## Prerequisite
|
- Issuer URL: `https://keycloak.example.com/auth/realms/YOUR_REALM`
|
||||||
|
|
||||||
You have to setup your OIDC identity provider and Kubernetes cluster.
|
|
||||||
|
|
||||||
### 1. Setup OIDC Identity Provider
|
|
||||||
|
|
||||||
This tutorial assumes you have created an OIDC client with the following:
|
|
||||||
|
|
||||||
- Issuer URL: `https://keycloak.example.com/auth/realms/hello`
|
|
||||||
- Client ID: `kubernetes`
|
- Client ID: `kubernetes`
|
||||||
- Client Secret: `YOUR_CLIENT_SECRET`
|
- Groups claim: `groups`
|
||||||
- Allowed redirect URLs: `http://localhost:8000/`
|
|
||||||
- Groups claim: `groups` (optional for group based access controll)
|
|
||||||
|
|
||||||
### 2. Setup Kubernetes API Server
|
Then create a group `kubernetes:admin` and join to it.
|
||||||
|
|
||||||
Configure the Kubernetes API server allows your identity provider.
|
### 2. Setup Kubernetes cluster
|
||||||
|
|
||||||
If you are using [kops](https://github.com/kubernetes/kops), `kops edit cluster` and append the following settings:
|
Configure your Kubernetes API Server accepts [OpenID Connect Tokens](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#openid-connect-tokens).
|
||||||
|
If you are using [kops](https://github.com/kubernetes/kops), run `kops edit cluster` and append the following settings:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
spec:
|
spec:
|
||||||
kubeAPIServer:
|
kubeAPIServer:
|
||||||
|
oidcIssuerURL: https://keycloak.example.com/auth/realms/YOUR_REALM
|
||||||
oidcClientID: kubernetes
|
oidcClientID: kubernetes
|
||||||
oidcGroupsClaim: groups
|
oidcGroupsClaim: groups
|
||||||
oidcIssuerURL: https://keycloak.example.com/auth/realms/hello
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Setup kubectl
|
Here assign the `cluster-admin` role to the `kubernetes:admin` group.
|
||||||
|
|
||||||
Run the following command to configure `kubectl` to authenticate by your identity provider.
|
```yaml
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
name: keycloak-admin-group
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: cluster-admin
|
||||||
|
subjects:
|
||||||
|
- kind: Group
|
||||||
|
name: /kubernetes:admin
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Setup kubectl and kubelogin
|
||||||
|
|
||||||
|
Setup `kubectl` to authenticate with your identity provider.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
kubectl config set-credentials CLUSTER_NAME \
|
kubectl config set-credentials CLUSTER_NAME \
|
||||||
--auth-provider oidc \
|
--auth-provider oidc \
|
||||||
--auth-provider-arg idp-issuer-url=https://keycloak.example.com/auth/realms/hello \
|
--auth-provider-arg idp-issuer-url=https://keycloak.example.com/auth/realms/YOUR_REALM \
|
||||||
--auth-provider-arg client-id=kubernetes \
|
--auth-provider-arg client-id=kubernetes \
|
||||||
--auth-provider-arg client-secret=YOUR_CLIENT_SECRET
|
--auth-provider-arg client-secret=YOUR_CLIENT_SECRET
|
||||||
```
|
```
|
||||||
|
|
||||||
In actual team operation, you can share the following config to your team members for easy setup.
|
Download [the latest release](https://github.com/int128/kubelogin/releases) and save it.
|
||||||
|
|
||||||
```yaml
|
Run `kubelogin` and make sure you can access to the cluster.
|
||||||
#!/bin/sh
|
See the previous section for details.
|
||||||
|
|
||||||
|
|
||||||
|
## Tips
|
||||||
|
|
||||||
|
### Config file
|
||||||
|
|
||||||
|
You can set the environment variable `KUBECONFIG` to point the config file.
|
||||||
|
Default to `~/.kube/config`.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
export KUBECONFIG="$PWD/.kubeconfig"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Setup script
|
||||||
|
|
||||||
|
In actual team operation, you can share the following script to your team members for easy setup.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
#!/bin/sh -xe
|
||||||
CLUSTER_NAME="hello.k8s.local"
|
CLUSTER_NAME="hello.k8s.local"
|
||||||
|
|
||||||
# Set the certificate
|
export KUBECONFIG="$PWD/.kubeconfig"
|
||||||
mkdir -p "$HOME/.kube"
|
|
||||||
cat > "$HOME/.kube/$CLUSTER_NAME.crt" <<EOF
|
|
||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MII...
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# Set the cluster
|
|
||||||
kubectl config set-cluster "$CLUSTER_NAME" \
|
kubectl config set-cluster "$CLUSTER_NAME" \
|
||||||
--server https://api-xxx.xxx.elb.amazonaws.com \
|
--server https://api-xxx.xxx.elb.amazonaws.com \
|
||||||
--certificate-authority "$HOME/.kube/$CLUSTER_NAME.crt"
|
--certificate-authority "$PWD/cluster.crt"
|
||||||
|
|
||||||
# Set the credentials
|
|
||||||
kubectl config set-credentials "$CLUSTER_NAME" \
|
kubectl config set-credentials "$CLUSTER_NAME" \
|
||||||
--auth-provider oidc \
|
--auth-provider oidc \
|
||||||
--auth-provider-arg idp-issuer-url=https://keycloak.example.com/auth/realms/hello \
|
--auth-provider-arg idp-issuer-url=https://accounts.google.com \
|
||||||
--auth-provider-arg client-id=kubernetes \
|
--auth-provider-arg client-id=YOUR_CLIENT_ID.apps.googleusercontent.com \
|
||||||
--auth-provider-arg client-secret=YOUR_SECRET
|
--auth-provider-arg client-secret=YOUR_CLIENT_SECRET
|
||||||
|
|
||||||
# Set the context
|
|
||||||
kubectl config set-context "$CLUSTER_NAME" --cluster "$CLUSTER_NAME" --user "$CLUSTER_NAME"
|
kubectl config set-context "$CLUSTER_NAME" --cluster "$CLUSTER_NAME" --user "$CLUSTER_NAME"
|
||||||
|
|
||||||
# Set the current context
|
|
||||||
kubectl config use-context "$CLUSTER_NAME"
|
kubectl config use-context "$CLUSTER_NAME"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -2,37 +2,11 @@ package kubeconfig
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
|
|
||||||
homedir "github.com/mitchellh/go-homedir"
|
|
||||||
"k8s.io/client-go/tools/clientcmd"
|
"k8s.io/client-go/tools/clientcmd"
|
||||||
"k8s.io/client-go/tools/clientcmd/api"
|
"k8s.io/client-go/tools/clientcmd/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
const userKubeConfig = "~/.kube/config"
|
|
||||||
|
|
||||||
// Find returns path to the kubeconfig file,
|
|
||||||
// that is given by env:KUBECONFIG or ~/.kube/config.
|
|
||||||
// This returns an error if it is not found or I/O error occurred.
|
|
||||||
func Find() (string, error) {
|
|
||||||
path := os.Getenv("KUBECONFIG")
|
|
||||||
if path == "" {
|
|
||||||
var err error
|
|
||||||
path, err = homedir.Expand(userKubeConfig)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("Could not expand %s: %s", userKubeConfig, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
info, err := os.Stat(path)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("Could not stat %s: %s", userKubeConfig, err)
|
|
||||||
}
|
|
||||||
if info.IsDir() {
|
|
||||||
return "", fmt.Errorf("%s should be a file", userKubeConfig)
|
|
||||||
}
|
|
||||||
return path, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load loads the file and returns the Config.
|
// Load loads the file and returns the Config.
|
||||||
func Load(path string) (*api.Config, error) {
|
func Load(path string) (*api.Config, error) {
|
||||||
config, err := clientcmd.LoadFromFile(path)
|
config, err := clientcmd.LoadFromFile(path)
|
||||||
|
|||||||
47
main.go
47
main.go
@@ -2,16 +2,53 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"github.com/int128/kubelogin/authn"
|
"github.com/int128/kubelogin/authn"
|
||||||
"github.com/int128/kubelogin/kubeconfig"
|
"github.com/int128/kubelogin/kubeconfig"
|
||||||
|
flags "github.com/jessevdk/go-flags"
|
||||||
|
homedir "github.com/mitchellh/go-homedir"
|
||||||
|
"golang.org/x/oauth2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
type options struct {
|
||||||
path, err := kubeconfig.Find()
|
KubeConfig string `long:"kubeconfig" default:"~/.kube/config" env:"KUBECONFIG" description:"Path to the kubeconfig file"`
|
||||||
|
SkipTLSVerify bool `long:"insecure-skip-tls-verify" env:"KUBELOGIN_INSECURE_SKIP_TLS_VERIFY" description:"If set, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure"`
|
||||||
|
// CertificateAuthority string `long:"certificate-authority" env:"KUBELOGIN_CERTIFICATE_AUTHORITY" description:"Path to a cert file for the certificate authority"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *options) ExpandKubeConfig() (string, error) {
|
||||||
|
d, err := homedir.Expand(o.KubeConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Could not find kubeconfig: %s", err)
|
return "", fmt.Errorf("Could not expand %s", o.KubeConfig)
|
||||||
|
}
|
||||||
|
return d, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseOptions() (*options, error) {
|
||||||
|
var o options
|
||||||
|
parser := flags.NewParser(&o, flags.HelpFlag)
|
||||||
|
args, err := parser.Parse()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(args) > 0 {
|
||||||
|
return nil, fmt.Errorf("Too many argument")
|
||||||
|
}
|
||||||
|
return &o, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
opts, err := parseOptions()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
path, err := opts.ExpandKubeConfig()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
log.Printf("Reading %s", path)
|
log.Printf("Reading %s", path)
|
||||||
cfg, err := kubeconfig.Load(path)
|
cfg, err := kubeconfig.Load(path)
|
||||||
@@ -28,7 +65,11 @@ func main() {
|
|||||||
log.Fatalf("Could not find auth-provider: %s", err)
|
log.Fatalf("Could not find auth-provider: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
client := &http.Client{Transport: &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: opts.SkipTLSVerify},
|
||||||
|
}}
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
ctx = context.WithValue(ctx, oauth2.HTTPClient, client)
|
||||||
token, err := authn.GetTokenSet(ctx, authProvider.IDPIssuerURL(), authProvider.ClientID(), authProvider.ClientSecret())
|
token, err := authn.GetTokenSet(ctx, authProvider.IDPIssuerURL(), authProvider.ClientID(), authProvider.ClientSecret())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Authentication error: %s", err)
|
log.Fatalf("Authentication error: %s", err)
|
||||||
|
|||||||
Reference in New Issue
Block a user