mirror of
https://github.com/int128/kubelogin.git
synced 2026-03-02 00:40:19 +00:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7011f03094 | ||
|
|
6aef98cef7 | ||
|
|
93bb1d39b9 | ||
|
|
c8116e2eae | ||
|
|
f2de8dd987 | ||
|
|
915fb35bc8 | ||
|
|
51ccd70af3 | ||
|
|
c6df597fb0 | ||
|
|
ee78f6f735 | ||
|
|
6e484a2b89 | ||
|
|
8050db7e05 | ||
|
|
cd54ca0df0 | ||
|
|
45f83b0b0e | ||
|
|
51b7ca1600 | ||
|
|
83f85a9b53 | ||
|
|
d82c8a2dd1 | ||
|
|
072bee6992 | ||
|
|
5c07850a68 | ||
|
|
5c8c80f055 | ||
|
|
bc7bfabfb2 | ||
|
|
73112546de |
@@ -11,11 +11,12 @@ jobs:
|
||||
curl -L -o ~/bin/kubectl https://storage.googleapis.com/kubernetes-release/release/v1.14.0/bin/linux/amd64/kubectl
|
||||
chmod +x ~/bin/kubectl
|
||||
- run: |
|
||||
go get -v \
|
||||
golang.org/x/lint/golint \
|
||||
github.com/int128/goxzst \
|
||||
github.com/tcnksm/ghr \
|
||||
github.com/int128/ghcp
|
||||
curl -L -o ~/bin/ghcp https://github.com/int128/ghcp/releases/download/v1.3.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
|
||||
- run: go get github.com/int128/goxzst
|
||||
- run: go get github.com/tcnksm/ghr
|
||||
- checkout
|
||||
# workaround for https://github.com/golang/go/issues/27925
|
||||
- run: sed -e '/^k8s.io\/client-go /d' -i go.sum
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -2,4 +2,4 @@
|
||||
/dist
|
||||
/kubelogin
|
||||
/kubectl-oidc_login
|
||||
/.kubeconfig
|
||||
/.kubeconfig*
|
||||
|
||||
3
Makefile
3
Makefile
@@ -8,8 +8,7 @@ LDFLAGS := -X main.version=$(CIRCLE_TAG)
|
||||
all: $(TARGET)
|
||||
|
||||
check:
|
||||
golint
|
||||
go vet
|
||||
golangci-lint run
|
||||
$(MAKE) -C adaptors_test/keys/testdata
|
||||
go test -v -race ./...
|
||||
|
||||
|
||||
76
README.md
76
README.md
@@ -1,7 +1,7 @@
|
||||
# kubelogin [](https://circleci.com/gh/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).
|
||||
It gets a token from the OIDC provider and writes it to the kubeconfig.
|
||||
It updates the kubeconfig file with an ID token and refresh token got from the OIDC provider.
|
||||
|
||||
|
||||
## Getting Started
|
||||
@@ -24,7 +24,7 @@ brew install kubelogin
|
||||
kubectl krew install oidc-login
|
||||
|
||||
# GitHub Releases
|
||||
curl -LO https://github.com/int128/kubelogin/releases/download/v1.9.1/kubelogin_linux_amd64.zip
|
||||
curl -LO https://github.com/int128/kubelogin/releases/download/v1.11.0/kubelogin_linux_amd64.zip
|
||||
unzip kubelogin_linux_amd64.zip
|
||||
ln -s kubelogin kubectl-oidc_login
|
||||
```
|
||||
@@ -33,10 +33,9 @@ After initial setup or when the token has been expired, just run:
|
||||
|
||||
```
|
||||
% kubelogin
|
||||
Using current-context: hello.k8s.local
|
||||
Open http://localhost:8000 for authorization
|
||||
Got a token for subject 0123456789 (valid until 2019-04-12 11:00:49 +0900 JST)
|
||||
Updated ~/.kube/config
|
||||
Open http://localhost:8000 for authentication
|
||||
You got a valid token until 2019-05-16 22:03:13 +0900 JST
|
||||
Updated ~/.kubeconfig
|
||||
```
|
||||
|
||||
or run as a kubectl plugin:
|
||||
@@ -54,28 +53,30 @@ For more, see the following documents:
|
||||
- [Getting Started with Google Identity Platform](docs/google.md)
|
||||
- [Team Operation](docs/team_ops.md)
|
||||
|
||||
If you are using other platforms, please contribute documents via pull requests.
|
||||
|
||||
|
||||
## Configuration
|
||||
|
||||
This supports the following options.
|
||||
This document is for the development version.
|
||||
If you are looking for a specific version, see [the release tags](https://github.com/int128/kubelogin/tags).
|
||||
|
||||
Kubelogin supports the following options.
|
||||
|
||||
```
|
||||
kubelogin [OPTIONS]
|
||||
|
||||
Application Options:
|
||||
--kubeconfig= Path to the kubeconfig file (default: ~/.kube/config) [$KUBECONFIG]
|
||||
--listen-port= Port used by kubelogin to bind its webserver (default: 8000) [$KUBELOGIN_LISTEN_PORT]
|
||||
--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]
|
||||
--skip-open-browser If set, it does not open the browser on authentication. [$KUBELOGIN_SKIP_OPEN_BROWSER]
|
||||
-v, --v= If set to 1 or greater, show debug log (default: 0)
|
||||
|
||||
Help Options:
|
||||
-h, --help Show this help message
|
||||
Options:
|
||||
--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
|
||||
--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
|
||||
--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
|
||||
-v, --v int If set to 1 or greater, it shows debug log
|
||||
```
|
||||
|
||||
This also supports the following keys of `auth-provider` in kubeconfig.
|
||||
See [kubectl authentication](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#using-kubectl).
|
||||
It supports the following keys of `auth-provider` in a kubeconfig.
|
||||
See [kubectl authentication](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#using-kubectl) for more.
|
||||
|
||||
Key | Direction | Value
|
||||
----|-----------|------
|
||||
@@ -89,15 +90,21 @@ Key | Direction | Value
|
||||
`refresh-token` | Write | Refresh token got from the provider.
|
||||
|
||||
|
||||
### Kubeconfig path
|
||||
### Kubeconfig
|
||||
|
||||
You can set the environment variable `KUBECONFIG` to point the config file.
|
||||
Default to `~/.kube/config`.
|
||||
You can set path to the kubeconfig file by the option or the environment variable just like kubectl.
|
||||
It defaults to `~/.kube/config`.
|
||||
|
||||
```sh
|
||||
export KUBECONFIG="$PWD/.kubeconfig"
|
||||
# by the option
|
||||
kubelogin --kubeconfig /path/to/kubeconfig
|
||||
|
||||
# by the environment variable
|
||||
KUBECONFIG="/path/to/kubeconfig1:/path/to/kubeconfig2" kubelogin
|
||||
```
|
||||
|
||||
If you set multiple files, kubelogin will find the file which has the current authentication (i.e. `user` and `auth-provider`) and write a token to it.
|
||||
|
||||
|
||||
### Extra scopes
|
||||
|
||||
@@ -115,17 +122,30 @@ sed -i '' -e s/SCOPES/email,profile/ $KUBECONFIG
|
||||
```
|
||||
|
||||
|
||||
### Redirect URIs
|
||||
|
||||
By default kubelogin starts the local server at port 8000 or 18000.
|
||||
You need to register the following redirect URIs to the OIDC provider:
|
||||
|
||||
- `http://localhost:8000`
|
||||
- `http://localhost:18000` (used if port 8000 is already in use)
|
||||
|
||||
You can change the ports by the option:
|
||||
|
||||
```sh
|
||||
kubelogin --listen-port 12345 --listen-port 23456
|
||||
```
|
||||
|
||||
|
||||
### CA Certificates
|
||||
|
||||
You can set your self-signed certificates for the OIDC provider (not Kubernetes API server) by `idp-certificate-authority` and `idp-certificate-authority-data` in the kubeconfig.
|
||||
You can set your self-signed certificates for the OIDC provider (not Kubernetes API server) by kubeconfig or option.
|
||||
|
||||
```sh
|
||||
kubectl config set-credentials keycloak \
|
||||
--auth-provider-arg idp-certificate-authority=$HOME/.kube/keycloak-ca.pem
|
||||
```
|
||||
|
||||
If kubelogin could not parse the certificate, it shows a warning and skips it.
|
||||
|
||||
|
||||
### HTTP Proxy
|
||||
|
||||
|
||||
@@ -2,16 +2,29 @@ package adaptors
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/int128/kubelogin/adaptors/interfaces"
|
||||
"github.com/int128/kubelogin/kubeconfig"
|
||||
"github.com/int128/kubelogin/usecases/interfaces"
|
||||
"github.com/jessevdk/go-flags"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/pflag"
|
||||
"go.uber.org/dig"
|
||||
)
|
||||
|
||||
const usage = `Login to the OpenID Connect provider and update the kubeconfig.
|
||||
kubelogin %[2]s
|
||||
|
||||
Examples:
|
||||
# Login to the current provider and update ~/.kube/config
|
||||
%[1]s
|
||||
|
||||
Options:
|
||||
%[3]s
|
||||
Usage:
|
||||
%[1]s [options]`
|
||||
|
||||
var defaultListenPort = []int{8000, 18000}
|
||||
|
||||
func NewCmd(i Cmd) adaptors.Cmd {
|
||||
return &i
|
||||
}
|
||||
@@ -23,32 +36,43 @@ type Cmd struct {
|
||||
}
|
||||
|
||||
func (cmd *Cmd) Run(ctx context.Context, args []string, version string) int {
|
||||
executable := executableName(args[0])
|
||||
f := pflag.NewFlagSet(executable, pflag.ContinueOnError)
|
||||
f.SortFlags = false
|
||||
f.Usage = func() {
|
||||
cmd.Logger.Printf(usage, executable, version, f.FlagUsages())
|
||||
}
|
||||
var o cmdOptions
|
||||
parser := flags.NewParser(&o, flags.HelpFlag)
|
||||
parser.LongDescription = fmt.Sprintf(`Version %s
|
||||
This updates the kubeconfig for Kubernetes OpenID Connect (OIDC) authentication.`,
|
||||
version)
|
||||
args, err := parser.ParseArgs(args[1:])
|
||||
if err != nil {
|
||||
cmd.Logger.Printf("Error: %s", err)
|
||||
f.StringVar(&o.KubeConfig, "kubeconfig", "", "Path to the kubeconfig file")
|
||||
f.StringVar(&o.KubeContext, "context", "", "The name of the kubeconfig context to use")
|
||||
f.StringVar(&o.KubeUser, "user", "", "The name of the kubeconfig user to use. Prior to --context")
|
||||
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")
|
||||
f.IntVarP(&o.Verbose, "v", "v", 0, "If set to 1 or greater, it shows debug log")
|
||||
|
||||
if err := f.Parse(args[1:]); err != nil {
|
||||
if err == pflag.ErrHelp {
|
||||
return 1
|
||||
}
|
||||
cmd.Logger.Printf("Error: invalid arguments: %s", err)
|
||||
return 1
|
||||
}
|
||||
if len(args) > 0 {
|
||||
if len(f.Args()) > 0 {
|
||||
cmd.Logger.Printf("Error: too many arguments")
|
||||
return 1
|
||||
}
|
||||
cmd.Logger.SetLevel(adaptors.LogLevel(o.Verbose))
|
||||
kubeConfig, err := o.ExpandKubeConfig()
|
||||
if err != nil {
|
||||
cmd.Logger.Printf("Error: invalid option: %s", err)
|
||||
return 1
|
||||
}
|
||||
|
||||
cmd.Logger.SetLevel(adaptors.LogLevel(o.Verbose))
|
||||
in := usecases.LoginIn{
|
||||
KubeConfig: kubeConfig,
|
||||
ListenPort: o.ListenPort,
|
||||
SkipTLSVerify: o.SkipTLSVerify,
|
||||
SkipOpenBrowser: o.SkipOpenBrowser,
|
||||
KubeConfigFilename: o.KubeConfig,
|
||||
KubeContextName: kubeconfig.ContextName(o.KubeContext),
|
||||
KubeUserName: kubeconfig.UserName(o.KubeUser),
|
||||
CertificateAuthorityFilename: o.CertificateAuthority,
|
||||
SkipTLSVerify: o.SkipTLSVerify,
|
||||
ListenPort: o.ListenPort,
|
||||
SkipOpenBrowser: o.SkipOpenBrowser,
|
||||
}
|
||||
if err := cmd.Login.Do(ctx, in); err != nil {
|
||||
cmd.Logger.Printf("Error: %s", err)
|
||||
@@ -58,18 +82,19 @@ func (cmd *Cmd) Run(ctx context.Context, args []string, version string) int {
|
||||
}
|
||||
|
||||
type cmdOptions struct {
|
||||
KubeConfig string `long:"kubeconfig" default:"~/.kube/config" env:"KUBECONFIG" description:"Path to the kubeconfig file"`
|
||||
ListenPort int `long:"listen-port" default:"8000" env:"KUBELOGIN_LISTEN_PORT" description:"Port used by kubelogin to bind its webserver"`
|
||||
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"`
|
||||
SkipOpenBrowser bool `long:"skip-open-browser" env:"KUBELOGIN_SKIP_OPEN_BROWSER" description:"If set, it does not open the browser on authentication."`
|
||||
Verbose int `long:"v" short:"v" default:"0" description:"If set to 1 or greater, show debug log"`
|
||||
KubeConfig string
|
||||
KubeContext string
|
||||
KubeUser string
|
||||
CertificateAuthority string
|
||||
SkipTLSVerify bool
|
||||
ListenPort []int
|
||||
SkipOpenBrowser bool
|
||||
Verbose int
|
||||
}
|
||||
|
||||
// ExpandKubeConfig returns an expanded KubeConfig path.
|
||||
func (c *cmdOptions) ExpandKubeConfig() (string, error) {
|
||||
d, err := homedir.Expand(c.KubeConfig)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "could not expand %s", c.KubeConfig)
|
||||
func executableName(arg0 string) string {
|
||||
if strings.HasPrefix(arg0, "kubectl-") {
|
||||
return strings.ReplaceAll(strings.ReplaceAll(arg0, "-", " "), "_", "-")
|
||||
}
|
||||
return d, nil
|
||||
return arg0
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"github.com/int128/kubelogin/adaptors/mock_adaptors"
|
||||
"github.com/int128/kubelogin/usecases/interfaces"
|
||||
"github.com/int128/kubelogin/usecases/mock_usecases"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
)
|
||||
|
||||
func TestCmd_Run(t *testing.T) {
|
||||
@@ -24,8 +23,7 @@ func TestCmd_Run(t *testing.T) {
|
||||
login := mock_usecases.NewMockLogin(ctrl)
|
||||
login.EXPECT().
|
||||
Do(ctx, usecases.LoginIn{
|
||||
KubeConfig: expand(t, "~/.kube/config"),
|
||||
ListenPort: 8000,
|
||||
ListenPort: defaultListenPort,
|
||||
})
|
||||
|
||||
logger := mock_adaptors.NewLogger(t, ctrl)
|
||||
@@ -50,10 +48,13 @@ func TestCmd_Run(t *testing.T) {
|
||||
login := mock_usecases.NewMockLogin(ctrl)
|
||||
login.EXPECT().
|
||||
Do(ctx, usecases.LoginIn{
|
||||
KubeConfig: expand(t, "~/.kube/config"),
|
||||
ListenPort: 10080,
|
||||
SkipTLSVerify: true,
|
||||
SkipOpenBrowser: true,
|
||||
KubeConfigFilename: "/path/to/kubeconfig",
|
||||
KubeContextName: "hello.k8s.local",
|
||||
KubeUserName: "google",
|
||||
CertificateAuthorityFilename: "/path/to/cacert",
|
||||
SkipTLSVerify: true,
|
||||
ListenPort: []int{10080, 20080},
|
||||
SkipOpenBrowser: true,
|
||||
})
|
||||
|
||||
logger := mock_adaptors.NewLogger(t, ctrl)
|
||||
@@ -65,9 +66,14 @@ func TestCmd_Run(t *testing.T) {
|
||||
Logger: logger,
|
||||
}
|
||||
exitCode := cmd.Run(ctx, []string{executable,
|
||||
"--kubeconfig", "/path/to/kubeconfig",
|
||||
"--context", "hello.k8s.local",
|
||||
"--user", "google",
|
||||
"--listen-port", "10080",
|
||||
"--insecure-skip-tls-verify",
|
||||
"--listen-port", "20080",
|
||||
"--skip-open-browser",
|
||||
"--certificate-authority", "/path/to/cacert",
|
||||
"--insecure-skip-tls-verify",
|
||||
"-v1",
|
||||
}, version)
|
||||
if exitCode != 0 {
|
||||
@@ -89,10 +95,17 @@ func TestCmd_Run(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func expand(t *testing.T, path string) string {
|
||||
d, err := homedir.Expand(path)
|
||||
if err != nil {
|
||||
t.Fatalf("could not expand: %s", err)
|
||||
}
|
||||
return d
|
||||
func TestCmd_executableName(t *testing.T) {
|
||||
t.Run("kubelogin", func(t *testing.T) {
|
||||
e := executableName("kubelogin")
|
||||
if e != "kubelogin" {
|
||||
t.Errorf("executableName wants kubelogin but %s", e)
|
||||
}
|
||||
})
|
||||
t.Run("kubectl-oidc_login", func(t *testing.T) {
|
||||
e := executableName("kubectl-oidc_login")
|
||||
if e != "kubectl oidc-login" {
|
||||
t.Errorf("executableName wants kubectl oidc-login but %s", e)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -22,17 +22,39 @@ type HTTP struct {
|
||||
Logger adaptors.Logger
|
||||
}
|
||||
|
||||
func (*HTTP) NewClientConfig() adaptors.HTTPClientConfig {
|
||||
return &httpClientConfig{
|
||||
certPool: x509.NewCertPool(),
|
||||
}
|
||||
}
|
||||
|
||||
func (h *HTTP) NewClient(config adaptors.HTTPClientConfig) (*http.Client, error) {
|
||||
pool := x509.NewCertPool()
|
||||
if filename := config.OIDCConfig.IDPCertificateAuthority(); filename != "" {
|
||||
h.Logger.Debugf(1, "Loading the certificate %s", filename)
|
||||
err := appendCertificateFromFile(pool, filename)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not load the certificate of idp-certificate-authority")
|
||||
}
|
||||
}
|
||||
if data := config.OIDCConfig.IDPCertificateAuthorityData(); data != "" {
|
||||
h.Logger.Debugf(1, "Loading the certificate of idp-certificate-authority-data")
|
||||
err := appendEncodedCertificate(pool, data)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not load the certificate of idp-certificate-authority-data")
|
||||
}
|
||||
}
|
||||
if config.CertificateAuthorityFilename != "" {
|
||||
h.Logger.Debugf(1, "Loading the certificate %s", config.CertificateAuthorityFilename)
|
||||
err := appendCertificateFromFile(pool, config.CertificateAuthorityFilename)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not load the certificate")
|
||||
}
|
||||
}
|
||||
|
||||
var tlsConfig tls.Config
|
||||
if len(pool.Subjects()) > 0 {
|
||||
tlsConfig.RootCAs = pool
|
||||
}
|
||||
tlsConfig.InsecureSkipVerify = config.SkipTLSVerify
|
||||
return &http.Client{
|
||||
Transport: &infrastructure.LoggingTransport{
|
||||
Base: &http.Transport{
|
||||
TLSClientConfig: config.TLSConfig(),
|
||||
TLSClientConfig: &tlsConfig,
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
},
|
||||
Logger: h.Logger,
|
||||
@@ -40,43 +62,24 @@ func (h *HTTP) NewClient(config adaptors.HTTPClientConfig) (*http.Client, error)
|
||||
}, nil
|
||||
}
|
||||
|
||||
type httpClientConfig struct {
|
||||
certPool *x509.CertPool
|
||||
skipTLSVerify bool
|
||||
}
|
||||
|
||||
func (c *httpClientConfig) AddCertificateFromFile(filename string) error {
|
||||
func appendCertificateFromFile(pool *x509.CertPool, filename string) error {
|
||||
b, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not read %s", filename)
|
||||
}
|
||||
if c.certPool.AppendCertsFromPEM(b) != true {
|
||||
if !pool.AppendCertsFromPEM(b) {
|
||||
return errors.Errorf("could not append certificate from %s", filename)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *httpClientConfig) AddEncodedCertificate(base64String string) error {
|
||||
func appendEncodedCertificate(pool *x509.CertPool, base64String string) error {
|
||||
b, err := base64.StdEncoding.DecodeString(base64String)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not decode base64")
|
||||
}
|
||||
if c.certPool.AppendCertsFromPEM(b) != true {
|
||||
if !pool.AppendCertsFromPEM(b) {
|
||||
return errors.Errorf("could not append certificate")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *httpClientConfig) TLSConfig() *tls.Config {
|
||||
tlsConfig := &tls.Config{
|
||||
InsecureSkipVerify: c.skipTLSVerify,
|
||||
}
|
||||
if len(c.certPool.Subjects()) > 0 {
|
||||
tlsConfig.RootCAs = c.certPool
|
||||
}
|
||||
return tlsConfig
|
||||
}
|
||||
|
||||
func (c *httpClientConfig) SetSkipTLSVerify(b bool) {
|
||||
c.skipTLSVerify = b
|
||||
}
|
||||
|
||||
@@ -2,49 +2,43 @@ package adaptors
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net/http"
|
||||
|
||||
"github.com/coreos/go-oidc"
|
||||
"k8s.io/client-go/tools/clientcmd/api"
|
||||
"github.com/int128/kubelogin/kubeconfig"
|
||||
)
|
||||
|
||||
//go:generate mockgen -package mock_adaptors -destination ../mock_adaptors/mock_adaptors.go github.com/int128/kubelogin/adaptors/interfaces KubeConfig,HTTP,HTTPClientConfig,OIDC,Logger
|
||||
//go:generate mockgen -package mock_adaptors -destination ../mock_adaptors/mock_adaptors.go github.com/int128/kubelogin/adaptors/interfaces KubeConfig,HTTP,OIDC,Logger
|
||||
|
||||
type Cmd interface {
|
||||
Run(ctx context.Context, args []string, version string) int
|
||||
}
|
||||
|
||||
type KubeConfig interface {
|
||||
LoadFromFile(filename string) (*api.Config, error)
|
||||
WriteToFile(config *api.Config, filename string) error
|
||||
LoadByDefaultRules(filename string) (*kubeconfig.Config, error)
|
||||
LoadFromFile(filename string) (*kubeconfig.Config, error)
|
||||
WriteToFile(config *kubeconfig.Config, filename string) error
|
||||
}
|
||||
|
||||
type HTTP interface {
|
||||
NewClientConfig() HTTPClientConfig
|
||||
NewClient(config HTTPClientConfig) (*http.Client, error)
|
||||
}
|
||||
|
||||
type HTTPClientConfig interface {
|
||||
AddCertificateFromFile(filename string) error
|
||||
AddEncodedCertificate(base64String string) error
|
||||
SetSkipTLSVerify(b bool)
|
||||
|
||||
TLSConfig() *tls.Config
|
||||
type HTTPClientConfig struct {
|
||||
OIDCConfig kubeconfig.OIDCConfig
|
||||
CertificateAuthorityFilename string
|
||||
SkipTLSVerify bool
|
||||
}
|
||||
|
||||
type OIDC interface {
|
||||
Authenticate(ctx context.Context, in OIDCAuthenticateIn, cb OIDCAuthenticateCallback) (*OIDCAuthenticateOut, error)
|
||||
VerifyIDToken(ctx context.Context, in OIDCVerifyTokenIn) (*oidc.IDToken, error)
|
||||
Verify(ctx context.Context, in OIDCVerifyIn) (*oidc.IDToken, error)
|
||||
}
|
||||
|
||||
type OIDCAuthenticateIn struct {
|
||||
Issuer string
|
||||
ClientID string
|
||||
ClientSecret string
|
||||
ExtraScopes []string // Additional scopes
|
||||
Config kubeconfig.OIDCConfig
|
||||
Client *http.Client // HTTP client for oidc and oauth2
|
||||
LocalServerPort int // HTTP server port
|
||||
LocalServerPort []int // HTTP server port candidates
|
||||
SkipOpenBrowser bool // skip opening browser if true
|
||||
}
|
||||
|
||||
@@ -58,11 +52,9 @@ type OIDCAuthenticateOut struct {
|
||||
RefreshToken string
|
||||
}
|
||||
|
||||
type OIDCVerifyTokenIn struct {
|
||||
IDToken string
|
||||
Issuer string
|
||||
ClientID string
|
||||
Client *http.Client
|
||||
type OIDCVerifyIn struct {
|
||||
Config kubeconfig.OIDCConfig
|
||||
Client *http.Client
|
||||
}
|
||||
|
||||
type Logger interface {
|
||||
|
||||
@@ -2,6 +2,7 @@ package adaptors
|
||||
|
||||
import (
|
||||
"github.com/int128/kubelogin/adaptors/interfaces"
|
||||
"github.com/int128/kubelogin/kubeconfig"
|
||||
"github.com/pkg/errors"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
"k8s.io/client-go/tools/clientcmd/api"
|
||||
@@ -13,16 +14,29 @@ func NewKubeConfig() adaptors.KubeConfig {
|
||||
|
||||
type KubeConfig struct{}
|
||||
|
||||
func (*KubeConfig) LoadFromFile(filename string) (*api.Config, error) {
|
||||
// LoadByDefaultRules loads the config by the default rules, that is same as kubectl.
|
||||
func (*KubeConfig) LoadByDefaultRules(filename string) (*kubeconfig.Config, error) {
|
||||
rules := clientcmd.NewDefaultClientConfigLoadingRules()
|
||||
rules.ExplicitPath = filename
|
||||
config, err := rules.Load()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not read the kubeconfig")
|
||||
}
|
||||
return (*kubeconfig.Config)(config), err
|
||||
}
|
||||
|
||||
// LoadFromFile loads the config from the single file.
|
||||
func (*KubeConfig) LoadFromFile(filename string) (*kubeconfig.Config, error) {
|
||||
config, err := clientcmd.LoadFromFile(filename)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not read the kubeconfig from %s", filename)
|
||||
}
|
||||
return config, err
|
||||
return (*kubeconfig.Config)(config), err
|
||||
}
|
||||
|
||||
func (*KubeConfig) WriteToFile(config *api.Config, filename string) error {
|
||||
err := clientcmd.WriteToFile(*config, filename)
|
||||
// WriteToFile writes the config to the single file.
|
||||
func (*KubeConfig) WriteToFile(config *kubeconfig.Config, filename string) error {
|
||||
err := clientcmd.WriteToFile(*(*api.Config)(config), filename)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not write the kubeconfig to %s", filename)
|
||||
}
|
||||
|
||||
74
adaptors/kubeconfig_test.go
Normal file
74
adaptors/kubeconfig_test.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package adaptors
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestKubeConfig_LoadByDefaultRules(t *testing.T) {
|
||||
var adaptor KubeConfig
|
||||
|
||||
t.Run("google.yaml>keycloak.yaml", func(t *testing.T) {
|
||||
setenv(t, "KUBECONFIG", "testdata/kubeconfig.google.yaml"+string(os.PathListSeparator)+"testdata/kubeconfig.keycloak.yaml")
|
||||
defer unsetenv(t, "KUBECONFIG")
|
||||
|
||||
config, err := adaptor.LoadByDefaultRules("")
|
||||
if err != nil {
|
||||
t.Fatalf("Could not load the configs: %s", err)
|
||||
}
|
||||
if w := "google@hello.k8s.local"; w != config.CurrentContext {
|
||||
t.Errorf("CurrentContext wants %s but %s", w, config.CurrentContext)
|
||||
}
|
||||
if _, ok := config.Contexts["google@hello.k8s.local"]; !ok {
|
||||
t.Errorf("Contexts[google@hello.k8s.local] is missing")
|
||||
}
|
||||
if _, ok := config.Contexts["keycloak@hello.k8s.local"]; !ok {
|
||||
t.Errorf("Contexts[keycloak@hello.k8s.local] is missing")
|
||||
}
|
||||
if _, ok := config.AuthInfos["google"]; !ok {
|
||||
t.Errorf("AuthInfos[google] is missing")
|
||||
}
|
||||
if _, ok := config.AuthInfos["keycloak"]; !ok {
|
||||
t.Errorf("AuthInfos[keycloak] is missing")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("keycloak.yaml>google.yaml", func(t *testing.T) {
|
||||
setenv(t, "KUBECONFIG", "testdata/kubeconfig.keycloak.yaml"+string(os.PathListSeparator)+"testdata/kubeconfig.google.yaml")
|
||||
defer unsetenv(t, "KUBECONFIG")
|
||||
|
||||
config, err := adaptor.LoadByDefaultRules("")
|
||||
if err != nil {
|
||||
t.Fatalf("Could not load the configs: %s", err)
|
||||
}
|
||||
if w := "keycloak@hello.k8s.local"; w != config.CurrentContext {
|
||||
t.Errorf("CurrentContext wants %s but %s", w, config.CurrentContext)
|
||||
}
|
||||
if _, ok := config.Contexts["google@hello.k8s.local"]; !ok {
|
||||
t.Errorf("Contexts[google@hello.k8s.local] is missing")
|
||||
}
|
||||
if _, ok := config.Contexts["keycloak@hello.k8s.local"]; !ok {
|
||||
t.Errorf("Contexts[keycloak@hello.k8s.local] is missing")
|
||||
}
|
||||
if _, ok := config.AuthInfos["google"]; !ok {
|
||||
t.Errorf("AuthInfos[google] is missing")
|
||||
}
|
||||
if _, ok := config.AuthInfos["keycloak"]; !ok {
|
||||
t.Errorf("AuthInfos[keycloak] is missing")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func setenv(t *testing.T, key, value string) {
|
||||
t.Helper()
|
||||
if err := os.Setenv(key, value); err != nil {
|
||||
t.Fatalf("Could not set the env var %s=%s: %s", key, value, err)
|
||||
}
|
||||
}
|
||||
|
||||
func unsetenv(t *testing.T, key string) {
|
||||
t.Helper()
|
||||
if err := os.Unsetenv(key); err != nil {
|
||||
t.Fatalf("Could not unset the env var %s: %s", key, err)
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,15 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/int128/kubelogin/adaptors/interfaces (interfaces: KubeConfig,HTTP,HTTPClientConfig,OIDC,Logger)
|
||||
// Source: github.com/int128/kubelogin/adaptors/interfaces (interfaces: KubeConfig,HTTP,OIDC,Logger)
|
||||
|
||||
// Package mock_adaptors is a generated GoMock package.
|
||||
package mock_adaptors
|
||||
|
||||
import (
|
||||
context "context"
|
||||
tls "crypto/tls"
|
||||
go_oidc "github.com/coreos/go-oidc"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
interfaces "github.com/int128/kubelogin/adaptors/interfaces"
|
||||
api "k8s.io/client-go/tools/clientcmd/api"
|
||||
kubeconfig "github.com/int128/kubelogin/kubeconfig"
|
||||
http "net/http"
|
||||
reflect "reflect"
|
||||
)
|
||||
@@ -38,10 +37,23 @@ func (m *MockKubeConfig) EXPECT() *MockKubeConfigMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// LoadByDefaultRules mocks base method
|
||||
func (m *MockKubeConfig) LoadByDefaultRules(arg0 string) (*kubeconfig.Config, error) {
|
||||
ret := m.ctrl.Call(m, "LoadByDefaultRules", arg0)
|
||||
ret0, _ := ret[0].(*kubeconfig.Config)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// LoadByDefaultRules indicates an expected call of LoadByDefaultRules
|
||||
func (mr *MockKubeConfigMockRecorder) LoadByDefaultRules(arg0 interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadByDefaultRules", reflect.TypeOf((*MockKubeConfig)(nil).LoadByDefaultRules), arg0)
|
||||
}
|
||||
|
||||
// LoadFromFile mocks base method
|
||||
func (m *MockKubeConfig) LoadFromFile(arg0 string) (*api.Config, error) {
|
||||
func (m *MockKubeConfig) LoadFromFile(arg0 string) (*kubeconfig.Config, error) {
|
||||
ret := m.ctrl.Call(m, "LoadFromFile", arg0)
|
||||
ret0, _ := ret[0].(*api.Config)
|
||||
ret0, _ := ret[0].(*kubeconfig.Config)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
@@ -52,7 +64,7 @@ func (mr *MockKubeConfigMockRecorder) LoadFromFile(arg0 interface{}) *gomock.Cal
|
||||
}
|
||||
|
||||
// WriteToFile mocks base method
|
||||
func (m *MockKubeConfig) WriteToFile(arg0 *api.Config, arg1 string) error {
|
||||
func (m *MockKubeConfig) WriteToFile(arg0 *kubeconfig.Config, arg1 string) error {
|
||||
ret := m.ctrl.Call(m, "WriteToFile", arg0, arg1)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
@@ -99,87 +111,6 @@ func (mr *MockHTTPMockRecorder) NewClient(arg0 interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewClient", reflect.TypeOf((*MockHTTP)(nil).NewClient), arg0)
|
||||
}
|
||||
|
||||
// NewClientConfig mocks base method
|
||||
func (m *MockHTTP) NewClientConfig() interfaces.HTTPClientConfig {
|
||||
ret := m.ctrl.Call(m, "NewClientConfig")
|
||||
ret0, _ := ret[0].(interfaces.HTTPClientConfig)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// NewClientConfig indicates an expected call of NewClientConfig
|
||||
func (mr *MockHTTPMockRecorder) NewClientConfig() *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewClientConfig", reflect.TypeOf((*MockHTTP)(nil).NewClientConfig))
|
||||
}
|
||||
|
||||
// MockHTTPClientConfig is a mock of HTTPClientConfig interface
|
||||
type MockHTTPClientConfig struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockHTTPClientConfigMockRecorder
|
||||
}
|
||||
|
||||
// MockHTTPClientConfigMockRecorder is the mock recorder for MockHTTPClientConfig
|
||||
type MockHTTPClientConfigMockRecorder struct {
|
||||
mock *MockHTTPClientConfig
|
||||
}
|
||||
|
||||
// NewMockHTTPClientConfig creates a new mock instance
|
||||
func NewMockHTTPClientConfig(ctrl *gomock.Controller) *MockHTTPClientConfig {
|
||||
mock := &MockHTTPClientConfig{ctrl: ctrl}
|
||||
mock.recorder = &MockHTTPClientConfigMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockHTTPClientConfig) EXPECT() *MockHTTPClientConfigMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// AddCertificateFromFile mocks base method
|
||||
func (m *MockHTTPClientConfig) AddCertificateFromFile(arg0 string) error {
|
||||
ret := m.ctrl.Call(m, "AddCertificateFromFile", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// AddCertificateFromFile indicates an expected call of AddCertificateFromFile
|
||||
func (mr *MockHTTPClientConfigMockRecorder) AddCertificateFromFile(arg0 interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddCertificateFromFile", reflect.TypeOf((*MockHTTPClientConfig)(nil).AddCertificateFromFile), arg0)
|
||||
}
|
||||
|
||||
// AddEncodedCertificate mocks base method
|
||||
func (m *MockHTTPClientConfig) AddEncodedCertificate(arg0 string) error {
|
||||
ret := m.ctrl.Call(m, "AddEncodedCertificate", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// AddEncodedCertificate indicates an expected call of AddEncodedCertificate
|
||||
func (mr *MockHTTPClientConfigMockRecorder) AddEncodedCertificate(arg0 interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddEncodedCertificate", reflect.TypeOf((*MockHTTPClientConfig)(nil).AddEncodedCertificate), arg0)
|
||||
}
|
||||
|
||||
// SetSkipTLSVerify mocks base method
|
||||
func (m *MockHTTPClientConfig) SetSkipTLSVerify(arg0 bool) {
|
||||
m.ctrl.Call(m, "SetSkipTLSVerify", arg0)
|
||||
}
|
||||
|
||||
// SetSkipTLSVerify indicates an expected call of SetSkipTLSVerify
|
||||
func (mr *MockHTTPClientConfigMockRecorder) SetSkipTLSVerify(arg0 interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSkipTLSVerify", reflect.TypeOf((*MockHTTPClientConfig)(nil).SetSkipTLSVerify), arg0)
|
||||
}
|
||||
|
||||
// TLSConfig mocks base method
|
||||
func (m *MockHTTPClientConfig) TLSConfig() *tls.Config {
|
||||
ret := m.ctrl.Call(m, "TLSConfig")
|
||||
ret0, _ := ret[0].(*tls.Config)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// TLSConfig indicates an expected call of TLSConfig
|
||||
func (mr *MockHTTPClientConfigMockRecorder) TLSConfig() *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TLSConfig", reflect.TypeOf((*MockHTTPClientConfig)(nil).TLSConfig))
|
||||
}
|
||||
|
||||
// MockOIDC is a mock of OIDC interface
|
||||
type MockOIDC struct {
|
||||
ctrl *gomock.Controller
|
||||
@@ -216,17 +147,17 @@ func (mr *MockOIDCMockRecorder) Authenticate(arg0, arg1, arg2 interface{}) *gomo
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Authenticate", reflect.TypeOf((*MockOIDC)(nil).Authenticate), arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
// VerifyIDToken mocks base method
|
||||
func (m *MockOIDC) VerifyIDToken(arg0 context.Context, arg1 interfaces.OIDCVerifyTokenIn) (*go_oidc.IDToken, error) {
|
||||
ret := m.ctrl.Call(m, "VerifyIDToken", arg0, arg1)
|
||||
// Verify mocks base method
|
||||
func (m *MockOIDC) Verify(arg0 context.Context, arg1 interfaces.OIDCVerifyIn) (*go_oidc.IDToken, error) {
|
||||
ret := m.ctrl.Call(m, "Verify", arg0, arg1)
|
||||
ret0, _ := ret[0].(*go_oidc.IDToken)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// VerifyIDToken indicates an expected call of VerifyIDToken
|
||||
func (mr *MockOIDCMockRecorder) VerifyIDToken(arg0, arg1 interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyIDToken", reflect.TypeOf((*MockOIDC)(nil).VerifyIDToken), arg0, arg1)
|
||||
// Verify indicates an expected call of Verify
|
||||
func (mr *MockOIDCMockRecorder) Verify(arg0, arg1 interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Verify", reflect.TypeOf((*MockOIDC)(nil).Verify), arg0, arg1)
|
||||
}
|
||||
|
||||
// MockLogger is a mock of Logger interface
|
||||
|
||||
@@ -20,23 +20,23 @@ func (*OIDC) Authenticate(ctx context.Context, in adaptors.OIDCAuthenticateIn, c
|
||||
if in.Client != nil {
|
||||
ctx = context.WithValue(ctx, oauth2.HTTPClient, in.Client)
|
||||
}
|
||||
provider, err := oidc.NewProvider(ctx, in.Issuer)
|
||||
provider, err := oidc.NewProvider(ctx, in.Config.IDPIssuerURL())
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not discovery the OIDC issuer")
|
||||
}
|
||||
flow := oauth2cli.AuthCodeFlow{
|
||||
Config: oauth2.Config{
|
||||
config := oauth2cli.Config{
|
||||
OAuth2Config: oauth2.Config{
|
||||
Endpoint: provider.Endpoint(),
|
||||
ClientID: in.ClientID,
|
||||
ClientSecret: in.ClientSecret,
|
||||
Scopes: append(in.ExtraScopes, oidc.ScopeOpenID),
|
||||
ClientID: in.Config.ClientID(),
|
||||
ClientSecret: in.Config.ClientSecret(),
|
||||
Scopes: append(in.Config.ExtraScopes(), oidc.ScopeOpenID),
|
||||
},
|
||||
LocalServerPort: in.LocalServerPort,
|
||||
SkipOpenBrowser: in.SkipOpenBrowser,
|
||||
AuthCodeOptions: []oauth2.AuthCodeOption{oauth2.AccessTypeOffline},
|
||||
ShowLocalServerURL: cb.ShowLocalServerURL,
|
||||
}
|
||||
token, err := flow.GetToken(ctx)
|
||||
token, err := oauth2cli.GetToken(ctx, config)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not get a token")
|
||||
}
|
||||
@@ -44,7 +44,7 @@ func (*OIDC) Authenticate(ctx context.Context, in adaptors.OIDCAuthenticateIn, c
|
||||
if !ok {
|
||||
return nil, errors.Errorf("id_token is missing in the token response: %s", token)
|
||||
}
|
||||
verifier := provider.Verifier(&oidc.Config{ClientID: in.ClientID})
|
||||
verifier := provider.Verifier(&oidc.Config{ClientID: in.Config.ClientID()})
|
||||
verifiedIDToken, err := verifier.Verify(ctx, idToken)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not verify the id_token")
|
||||
@@ -56,16 +56,16 @@ func (*OIDC) Authenticate(ctx context.Context, in adaptors.OIDCAuthenticateIn, c
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (*OIDC) VerifyIDToken(ctx context.Context, in adaptors.OIDCVerifyTokenIn) (*oidc.IDToken, error) {
|
||||
func (*OIDC) Verify(ctx context.Context, in adaptors.OIDCVerifyIn) (*oidc.IDToken, error) {
|
||||
if in.Client != nil {
|
||||
ctx = context.WithValue(ctx, oauth2.HTTPClient, in.Client)
|
||||
}
|
||||
provider, err := oidc.NewProvider(ctx, in.Issuer)
|
||||
provider, err := oidc.NewProvider(ctx, in.Config.IDPIssuerURL())
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not discovery the OIDC issuer")
|
||||
}
|
||||
verifier := provider.Verifier(&oidc.Config{ClientID: in.ClientID})
|
||||
verifiedIDToken, err := verifier.Verify(ctx, in.IDToken)
|
||||
verifier := provider.Verifier(&oidc.Config{ClientID: in.Config.ClientID()})
|
||||
verifiedIDToken, err := verifier.Verify(ctx, in.Config.IDToken())
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not verify the id_token")
|
||||
}
|
||||
|
||||
17
adaptors/testdata/kubeconfig.google.yaml
vendored
Normal file
17
adaptors/testdata/kubeconfig.google.yaml
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
apiVersion: v1
|
||||
clusters: []
|
||||
contexts:
|
||||
- context:
|
||||
cluster: hello.k8s.local
|
||||
user: google
|
||||
name: google@hello.k8s.local
|
||||
current-context: google@hello.k8s.local
|
||||
kind: Config
|
||||
preferences: {}
|
||||
users:
|
||||
- name: google
|
||||
user:
|
||||
auth-provider:
|
||||
config:
|
||||
client-id: CLIENT_ID.apps.googleusercontent.com
|
||||
name: oidc
|
||||
16
adaptors/testdata/kubeconfig.keycloak.yaml
vendored
Normal file
16
adaptors/testdata/kubeconfig.keycloak.yaml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
apiVersion: v1
|
||||
contexts:
|
||||
- context:
|
||||
cluster: hello.k8s.local
|
||||
user: keycloak
|
||||
name: keycloak@hello.k8s.local
|
||||
current-context: keycloak@hello.k8s.local
|
||||
kind: Config
|
||||
preferences: {}
|
||||
users:
|
||||
- name: keycloak
|
||||
user:
|
||||
auth-provider:
|
||||
config:
|
||||
client-id: kubernetes
|
||||
name: oidc
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
// Config represents server configuration.
|
||||
type Config struct {
|
||||
Addr string
|
||||
Issuer string
|
||||
Scope string
|
||||
TLSServerCert string
|
||||
@@ -20,7 +21,7 @@ type Config struct {
|
||||
// Start starts a HTTP server.
|
||||
func Start(t *testing.T, c Config) *http.Server {
|
||||
s := &http.Server{
|
||||
Addr: "localhost:9000",
|
||||
Addr: c.Addr,
|
||||
Handler: newHandler(t, c),
|
||||
}
|
||||
go func() {
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"crypto/tls"
|
||||
"net/http"
|
||||
"os"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -18,10 +19,11 @@ import (
|
||||
)
|
||||
|
||||
// Run the integration tests.
|
||||
// This assumes that port 800x and 900x are available.
|
||||
//
|
||||
// 1. Start the auth server at port 9000.
|
||||
// 1. Start the auth server at port 900x.
|
||||
// 2. Run the Cmd.
|
||||
// 3. Open a request for port 8000.
|
||||
// 3. Open a request for port 800x.
|
||||
// 4. Wait for the Cmd.
|
||||
// 5. Shutdown the auth server.
|
||||
//
|
||||
@@ -29,26 +31,64 @@ func TestCmd_Run(t *testing.T) {
|
||||
timeout := 1 * time.Second
|
||||
|
||||
t.Run("NoTLS", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
idToken := newIDToken(t, "http://localhost:9000")
|
||||
idToken := newIDToken(t, "http://localhost:9001")
|
||||
serverConfig := authserver.Config{
|
||||
Issuer: "http://localhost:9000",
|
||||
Addr: "localhost:9001",
|
||||
Issuer: "http://localhost:9001",
|
||||
IDToken: idToken,
|
||||
IDTokenKeyPair: keys.JWSKeyPair,
|
||||
RefreshToken: "REFRESH_TOKEN",
|
||||
}
|
||||
server := authserver.Start(t, serverConfig)
|
||||
defer server.Shutdown(ctx)
|
||||
defer shutdown(t, ctx, server)
|
||||
|
||||
kubeConfigFilename := kubeconfig.Create(t, &kubeconfig.Values{
|
||||
Issuer: "http://localhost:9000",
|
||||
Issuer: serverConfig.Issuer,
|
||||
})
|
||||
defer os.Remove(kubeConfigFilename)
|
||||
|
||||
startBrowserRequest(t, ctx, nil)
|
||||
runCmd(t, ctx, "--kubeconfig", kubeConfigFilename, "--skip-open-browser")
|
||||
var wg sync.WaitGroup
|
||||
startBrowserRequest(t, ctx, &wg, "http://localhost:8001", nil)
|
||||
runCmd(t, ctx, "--kubeconfig", kubeConfigFilename, "--skip-open-browser", "--listen-port", "8001")
|
||||
wg.Wait()
|
||||
kubeconfig.Verify(t, kubeConfigFilename, kubeconfig.AuthProviderConfig{
|
||||
IDToken: idToken,
|
||||
RefreshToken: "REFRESH_TOKEN",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("env:KUBECONFIG", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
idToken := newIDToken(t, "http://localhost:9002")
|
||||
serverConfig := authserver.Config{
|
||||
Addr: "localhost:9002",
|
||||
Issuer: "http://localhost:9002",
|
||||
IDToken: idToken,
|
||||
IDTokenKeyPair: keys.JWSKeyPair,
|
||||
RefreshToken: "REFRESH_TOKEN",
|
||||
}
|
||||
server := authserver.Start(t, serverConfig)
|
||||
defer shutdown(t, ctx, server)
|
||||
|
||||
kubeConfigFilename := kubeconfig.Create(t, &kubeconfig.Values{
|
||||
Issuer: serverConfig.Issuer,
|
||||
})
|
||||
defer os.Remove(kubeConfigFilename)
|
||||
|
||||
setenv(t, "KUBECONFIG", kubeConfigFilename+string(os.PathListSeparator)+"kubeconfig/testdata/dummy.yaml")
|
||||
defer unsetenv(t, "KUBECONFIG")
|
||||
|
||||
var wg sync.WaitGroup
|
||||
startBrowserRequest(t, ctx, &wg, "http://localhost:8002", nil)
|
||||
runCmd(t, ctx, "--skip-open-browser", "--listen-port", "8002")
|
||||
wg.Wait()
|
||||
kubeconfig.Verify(t, kubeConfigFilename, kubeconfig.AuthProviderConfig{
|
||||
IDToken: idToken,
|
||||
RefreshToken: "REFRESH_TOKEN",
|
||||
@@ -56,28 +96,32 @@ func TestCmd_Run(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("ExtraScopes", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
idToken := newIDToken(t, "http://localhost:9000")
|
||||
idToken := newIDToken(t, "http://localhost:9003")
|
||||
serverConfig := authserver.Config{
|
||||
Issuer: "http://localhost:9000",
|
||||
Addr: "localhost:9003",
|
||||
Issuer: "http://localhost:9003",
|
||||
IDToken: idToken,
|
||||
IDTokenKeyPair: keys.JWSKeyPair,
|
||||
RefreshToken: "REFRESH_TOKEN",
|
||||
Scope: "profile groups openid",
|
||||
}
|
||||
server := authserver.Start(t, serverConfig)
|
||||
defer server.Shutdown(ctx)
|
||||
defer shutdown(t, ctx, server)
|
||||
|
||||
kubeConfigFilename := kubeconfig.Create(t, &kubeconfig.Values{
|
||||
Issuer: "http://localhost:9000",
|
||||
Issuer: serverConfig.Issuer,
|
||||
ExtraScopes: "profile,groups",
|
||||
})
|
||||
defer os.Remove(kubeConfigFilename)
|
||||
|
||||
startBrowserRequest(t, ctx, nil)
|
||||
runCmd(t, ctx, "--kubeconfig", kubeConfigFilename, "--skip-open-browser")
|
||||
var wg sync.WaitGroup
|
||||
startBrowserRequest(t, ctx, &wg, "http://localhost:8003", nil)
|
||||
runCmd(t, ctx, "--kubeconfig", kubeConfigFilename, "--skip-open-browser", "--listen-port", "8003")
|
||||
wg.Wait()
|
||||
kubeconfig.Verify(t, kubeConfigFilename, kubeconfig.AuthProviderConfig{
|
||||
IDToken: idToken,
|
||||
RefreshToken: "REFRESH_TOKEN",
|
||||
@@ -85,12 +129,14 @@ func TestCmd_Run(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("CACert", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
idToken := newIDToken(t, "https://localhost:9000")
|
||||
idToken := newIDToken(t, "https://localhost:9004")
|
||||
serverConfig := authserver.Config{
|
||||
Issuer: "https://localhost:9000",
|
||||
Addr: "localhost:9004",
|
||||
Issuer: "https://localhost:9004",
|
||||
IDToken: idToken,
|
||||
IDTokenKeyPair: keys.JWSKeyPair,
|
||||
RefreshToken: "REFRESH_TOKEN",
|
||||
@@ -98,16 +144,18 @@ func TestCmd_Run(t *testing.T) {
|
||||
TLSServerKey: keys.TLSServerKey,
|
||||
}
|
||||
server := authserver.Start(t, serverConfig)
|
||||
defer server.Shutdown(ctx)
|
||||
defer shutdown(t, ctx, server)
|
||||
|
||||
kubeConfigFilename := kubeconfig.Create(t, &kubeconfig.Values{
|
||||
Issuer: "https://localhost:9000",
|
||||
Issuer: serverConfig.Issuer,
|
||||
IDPCertificateAuthority: keys.TLSCACert,
|
||||
})
|
||||
defer os.Remove(kubeConfigFilename)
|
||||
|
||||
startBrowserRequest(t, ctx, keys.TLSCACertAsConfig)
|
||||
runCmd(t, ctx, "--kubeconfig", kubeConfigFilename, "--skip-open-browser")
|
||||
var wg sync.WaitGroup
|
||||
startBrowserRequest(t, ctx, &wg, "http://localhost:8004", keys.TLSCACertAsConfig)
|
||||
runCmd(t, ctx, "--kubeconfig", kubeConfigFilename, "--skip-open-browser", "--listen-port", "8004")
|
||||
wg.Wait()
|
||||
kubeconfig.Verify(t, kubeConfigFilename, kubeconfig.AuthProviderConfig{
|
||||
IDToken: idToken,
|
||||
RefreshToken: "REFRESH_TOKEN",
|
||||
@@ -115,12 +163,14 @@ func TestCmd_Run(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("CACertData", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
idToken := newIDToken(t, "https://localhost:9000")
|
||||
idToken := newIDToken(t, "https://localhost:9005")
|
||||
serverConfig := authserver.Config{
|
||||
Issuer: "https://localhost:9000",
|
||||
Addr: "localhost:9005",
|
||||
Issuer: "https://localhost:9005",
|
||||
IDToken: idToken,
|
||||
IDTokenKeyPair: keys.JWSKeyPair,
|
||||
RefreshToken: "REFRESH_TOKEN",
|
||||
@@ -128,16 +178,18 @@ func TestCmd_Run(t *testing.T) {
|
||||
TLSServerKey: keys.TLSServerKey,
|
||||
}
|
||||
server := authserver.Start(t, serverConfig)
|
||||
defer server.Shutdown(ctx)
|
||||
defer shutdown(t, ctx, server)
|
||||
|
||||
kubeConfigFilename := kubeconfig.Create(t, &kubeconfig.Values{
|
||||
Issuer: "https://localhost:9000",
|
||||
Issuer: serverConfig.Issuer,
|
||||
IDPCertificateAuthorityData: keys.TLSCACertAsBase64,
|
||||
})
|
||||
defer os.Remove(kubeConfigFilename)
|
||||
|
||||
startBrowserRequest(t, ctx, keys.TLSCACertAsConfig)
|
||||
runCmd(t, ctx, "--kubeconfig", kubeConfigFilename, "--skip-open-browser")
|
||||
var wg sync.WaitGroup
|
||||
startBrowserRequest(t, ctx, &wg, "http://localhost:8005", keys.TLSCACertAsConfig)
|
||||
runCmd(t, ctx, "--kubeconfig", kubeConfigFilename, "--skip-open-browser", "--listen-port", "8005")
|
||||
wg.Wait()
|
||||
kubeconfig.Verify(t, kubeConfigFilename, kubeconfig.AuthProviderConfig{
|
||||
IDToken: idToken,
|
||||
RefreshToken: "REFRESH_TOKEN",
|
||||
@@ -145,19 +197,21 @@ func TestCmd_Run(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("AlreadyHaveValidToken", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
serverConfig := authserver.Config{
|
||||
Issuer: "http://localhost:9000",
|
||||
Addr: "localhost:9006",
|
||||
Issuer: "http://localhost:9006",
|
||||
IDTokenKeyPair: keys.JWSKeyPair,
|
||||
}
|
||||
server := authserver.Start(t, serverConfig)
|
||||
defer server.Shutdown(ctx)
|
||||
defer shutdown(t, ctx, server)
|
||||
|
||||
idToken := newIDToken(t, "http://localhost:9000")
|
||||
idToken := newIDToken(t, serverConfig.Issuer)
|
||||
kubeConfigFilename := kubeconfig.Create(t, &kubeconfig.Values{
|
||||
Issuer: "http://localhost:9000",
|
||||
Issuer: serverConfig.Issuer,
|
||||
IDToken: idToken,
|
||||
})
|
||||
defer os.Remove(kubeConfigFilename)
|
||||
@@ -171,11 +225,19 @@ func TestCmd_Run(t *testing.T) {
|
||||
|
||||
func newIDToken(t *testing.T, issuer string) string {
|
||||
t.Helper()
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.StandardClaims{
|
||||
var claims struct {
|
||||
jwt.StandardClaims
|
||||
Groups []string `json:"groups"`
|
||||
}
|
||||
claims.StandardClaims = jwt.StandardClaims{
|
||||
Issuer: issuer,
|
||||
Audience: "kubernetes",
|
||||
ExpiresAt: time.Now().Add(time.Hour).Unix(),
|
||||
})
|
||||
Subject: "SUBJECT",
|
||||
IssuedAt: time.Now().Unix(),
|
||||
}
|
||||
claims.Groups = []string{"admin", "users"}
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
|
||||
s, err := token.SignedString(keys.JWSKeyPair)
|
||||
if err != nil {
|
||||
t.Fatalf("Could not sign the claims: %s", err)
|
||||
@@ -198,16 +260,17 @@ func runCmd(t *testing.T, ctx context.Context, args ...string) {
|
||||
}
|
||||
}
|
||||
|
||||
func startBrowserRequest(t *testing.T, ctx context.Context, tlsConfig *tls.Config) {
|
||||
func startBrowserRequest(t *testing.T, ctx context.Context, wg *sync.WaitGroup, url string, tlsConfig *tls.Config) {
|
||||
t.Helper()
|
||||
client := http.Client{Transport: &http.Transport{TLSClientConfig: tlsConfig}}
|
||||
req, err := http.NewRequest("GET", "http://localhost:8000/", nil)
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
t.Errorf("could not create a request: %s", err)
|
||||
return
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
@@ -219,4 +282,25 @@ func startBrowserRequest(t *testing.T, ctx context.Context, tlsConfig *tls.Confi
|
||||
t.Errorf("StatusCode wants 200 but %d", resp.StatusCode)
|
||||
}
|
||||
}()
|
||||
wg.Add(1)
|
||||
}
|
||||
|
||||
func shutdown(t *testing.T, ctx context.Context, s *http.Server) {
|
||||
if err := s.Shutdown(ctx); err != nil {
|
||||
t.Errorf("Could not shutdown the auth server: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func setenv(t *testing.T, key, value string) {
|
||||
t.Helper()
|
||||
if err := os.Setenv(key, value); err != nil {
|
||||
t.Fatalf("Could not set the env var %s=%s: %s", key, value, err)
|
||||
}
|
||||
}
|
||||
|
||||
func unsetenv(t *testing.T, key string) {
|
||||
t.Helper()
|
||||
if err := os.Unsetenv(key); err != nil {
|
||||
t.Fatalf("Could not unset the env var %s: %s", key, err)
|
||||
}
|
||||
}
|
||||
|
||||
4
adaptors_test/kubeconfig/testdata/dummy.yaml
vendored
Normal file
4
adaptors_test/kubeconfig/testdata/dummy.yaml
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
apiVersion: v1
|
||||
current-context: dummy
|
||||
kind: Config
|
||||
preferences: {}
|
||||
@@ -2,30 +2,37 @@
|
||||
|
||||
## Prerequisite
|
||||
|
||||
- You have administrator access to the Keycloak.
|
||||
- You have the Cluster Admin role of the Kubernetes cluster.
|
||||
- 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 to your computer.
|
||||
- `kubectl` and `kubelogin` are installed.
|
||||
|
||||
## 1. Setup Keycloak
|
||||
|
||||
Open the Keycloak and create an OIDC client as follows:
|
||||
|
||||
- Redirect URL: `http://localhost:8000/`
|
||||
- Issuer URL: `https://keycloak.example.com/auth/realms/YOUR_REALM`
|
||||
- Client ID: `kubernetes`
|
||||
- Groups claim: `groups`
|
||||
- Valid Redirect URLs:
|
||||
- `http://localhost:8000`
|
||||
- `http://localhost:18000` (used if the port 8000 is already in use)
|
||||
- Issuer URL: `https://keycloak.example.com/auth/realms/YOUR_REALM`
|
||||
|
||||
Create a group `kubernetes:admin` and join to it.
|
||||
This is used for group based access control.
|
||||
You can associate client roles by adding the following mapper:
|
||||
|
||||
- Name: `groups`
|
||||
- Mapper Type: `User Client Role`
|
||||
- Client ID: `kubernetes`
|
||||
- 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"]}`.
|
||||
|
||||
## 2. Setup Kubernetes API server
|
||||
|
||||
Configure your Kubernetes API server accepts [OpenID Connect Tokens](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#openid-connect-tokens).
|
||||
|
||||
### kops
|
||||
|
||||
If you are using [kops](https://github.com/kubernetes/kops), run `kops edit cluster` and append the following settings:
|
||||
If you are using [kops](https://github.com/kubernetes/kops), run `kops edit cluster` and add the following spec:
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
@@ -50,7 +57,7 @@ roleRef:
|
||||
name: cluster-admin
|
||||
subjects:
|
||||
- kind: Group
|
||||
name: /kubernetes:admin
|
||||
name: kubernetes:admin
|
||||
```
|
||||
|
||||
You can create a custom role and assign it as well.
|
||||
|
||||
11
go.mod
11
go.mod
@@ -5,24 +5,19 @@ require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
github.com/gogo/protobuf v1.2.1 // indirect
|
||||
github.com/golang/mock v1.2.0
|
||||
github.com/golang/mock v1.3.1
|
||||
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf // indirect
|
||||
github.com/imdario/mergo v0.3.7 // indirect
|
||||
github.com/int128/oauth2cli v1.2.1
|
||||
github.com/jessevdk/go-flags v1.4.0
|
||||
github.com/int128/oauth2cli v1.4.0
|
||||
github.com/json-iterator/go v1.1.6 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.1 // indirect
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
|
||||
github.com/spf13/pflag v1.0.3 // indirect
|
||||
github.com/spf13/pflag v1.0.3
|
||||
github.com/stretchr/testify v1.3.0 // indirect
|
||||
go.uber.org/dig v1.7.0
|
||||
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c // indirect
|
||||
golang.org/x/net v0.0.0-20190328230028-74de082e2cca // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20190319182350-c85d3e98c914
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.3.0 // indirect
|
||||
|
||||
26
go.sum
26
go.sum
@@ -8,26 +8,20 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumC
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
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/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeqr2z77+8R2RKyh8PG66dcu1V0ck=
|
||||
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
|
||||
github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI=
|
||||
github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/int128/oauth2cli v1.1.0 h1:qAT6C8GyaLaSf0aseQUTcJvZ+j2MueETzGkpoFow0kc=
|
||||
github.com/int128/oauth2cli v1.1.0/go.mod h1:R1iBtRu+y4+DF4efDU0UePUYWjWfggwFI1KY1dw5E1M=
|
||||
github.com/int128/oauth2cli v1.2.1 h1:rhYQ++Kijz/sleAfzy2u2qEsQJCQSHVYjANgOM/LfLA=
|
||||
github.com/int128/oauth2cli v1.2.1/go.mod h1:R1iBtRu+y4+DF4efDU0UePUYWjWfggwFI1KY1dw5E1M=
|
||||
github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/int128/oauth2cli v1.4.0 h1:Xt4uk2lIb9Mf9Xyd5o43Hf9iV5izb2jYK3zRX/cPgh0=
|
||||
github.com/int128/oauth2cli v1.4.0/go.mod h1:81pWOyFVt1TRyZ7lZDtZuAGOE/S/jEpb1mpocRopI6U=
|
||||
github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||
@@ -47,18 +41,17 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
go.uber.org/dig v1.7.0 h1:E5/L92iQTNJTjfgJF2KgU+/JpMaiuvK2DHLBj0+kSZk=
|
||||
go.uber.org/dig v1.7.0/go.mod h1:z+dSd2TP9Usi48jL8M3v63iSBVkiwtVyMKxMZYYauPg=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c h1:Vj5n4GlwjmQteupaxJ9+0FNOmBrHfq7vN4btdGoDZgI=
|
||||
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190328230028-74de082e2cca h1:hyA6yiAgbUwuWqtscNvWAI7U1CtlaD1KilQ6iudt1aI=
|
||||
golang.org/x/net v0.0.0-20190328230028-74de082e2cca/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
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 h1:jIOcLT9BZzyJ9ce+IwwZ+aF9yeCqzrR+NrD68a/SHKw=
|
||||
golang.org/x/oauth2 v0.0.0-20190319182350-c85d3e98c914/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6 h1:bjcUS9ztw9kFmmIxJInhon/0Is3p+EHBKNgquIzo1OI=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/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/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
@@ -66,6 +59,7 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
|
||||
@@ -2,79 +2,55 @@ package kubeconfig
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"k8s.io/client-go/tools/clientcmd/api"
|
||||
)
|
||||
|
||||
// FindOIDCAuthProvider returns the current OIDC authProvider.
|
||||
// If the context, auth-info or auth-provider does not exist, this returns an error.
|
||||
// If auth-provider is not "oidc", this returns an error.
|
||||
func FindOIDCAuthProvider(config *api.Config) (*OIDCAuthProvider, error) {
|
||||
context := config.Contexts[config.CurrentContext]
|
||||
if context == nil {
|
||||
return nil, errors.Errorf("context %s does not exist", config.CurrentContext)
|
||||
}
|
||||
authInfo := config.AuthInfos[context.AuthInfo]
|
||||
if authInfo == nil {
|
||||
return nil, errors.Errorf("auth-info %s does not exist", context.AuthInfo)
|
||||
}
|
||||
if authInfo.AuthProvider == nil {
|
||||
return nil, errors.Errorf("auth-provider is not set")
|
||||
}
|
||||
if authInfo.AuthProvider.Name != "oidc" {
|
||||
return nil, errors.Errorf("auth-provider name is %s but must be oidc", authInfo.AuthProvider.Name)
|
||||
}
|
||||
return (*OIDCAuthProvider)(authInfo.AuthProvider), nil
|
||||
}
|
||||
|
||||
// OIDCAuthProvider represents OIDC configuration in the kubeconfig.
|
||||
type OIDCAuthProvider api.AuthProviderConfig
|
||||
// OIDCConfig represents config of an oidc auth-provider.
|
||||
type OIDCConfig map[string]string
|
||||
|
||||
// IDPIssuerURL returns the idp-issuer-url.
|
||||
func (c *OIDCAuthProvider) IDPIssuerURL() string {
|
||||
return c.Config["idp-issuer-url"]
|
||||
func (c OIDCConfig) IDPIssuerURL() string {
|
||||
return c["idp-issuer-url"]
|
||||
}
|
||||
|
||||
// ClientID returns the client-id.
|
||||
func (c *OIDCAuthProvider) ClientID() string {
|
||||
return c.Config["client-id"]
|
||||
func (c OIDCConfig) ClientID() string {
|
||||
return c["client-id"]
|
||||
}
|
||||
|
||||
// ClientSecret returns the client-secret.
|
||||
func (c *OIDCAuthProvider) ClientSecret() string {
|
||||
return c.Config["client-secret"]
|
||||
func (c OIDCConfig) ClientSecret() string {
|
||||
return c["client-secret"]
|
||||
}
|
||||
|
||||
// IDPCertificateAuthority returns the idp-certificate-authority.
|
||||
func (c *OIDCAuthProvider) IDPCertificateAuthority() string {
|
||||
return c.Config["idp-certificate-authority"]
|
||||
func (c OIDCConfig) IDPCertificateAuthority() string {
|
||||
return c["idp-certificate-authority"]
|
||||
}
|
||||
|
||||
// IDPCertificateAuthorityData returns the idp-certificate-authority-data.
|
||||
func (c *OIDCAuthProvider) IDPCertificateAuthorityData() string {
|
||||
return c.Config["idp-certificate-authority-data"]
|
||||
func (c OIDCConfig) IDPCertificateAuthorityData() string {
|
||||
return c["idp-certificate-authority-data"]
|
||||
}
|
||||
|
||||
// ExtraScopes returns the extra-scopes.
|
||||
func (c *OIDCAuthProvider) ExtraScopes() []string {
|
||||
if c.Config["extra-scopes"] == "" {
|
||||
func (c OIDCConfig) ExtraScopes() []string {
|
||||
if c["extra-scopes"] == "" {
|
||||
return []string{}
|
||||
}
|
||||
return strings.Split(c.Config["extra-scopes"], ",")
|
||||
return strings.Split(c["extra-scopes"], ",")
|
||||
}
|
||||
|
||||
// IDToken returns the id-token.
|
||||
func (c *OIDCAuthProvider) IDToken() string {
|
||||
return c.Config["id-token"]
|
||||
func (c OIDCConfig) IDToken() string {
|
||||
return c["id-token"]
|
||||
}
|
||||
|
||||
// SetIDToken replaces the id-token.
|
||||
func (c *OIDCAuthProvider) SetIDToken(idToken string) {
|
||||
c.Config["id-token"] = idToken
|
||||
func (c OIDCConfig) SetIDToken(idToken string) {
|
||||
c["id-token"] = idToken
|
||||
}
|
||||
|
||||
// SetRefreshToken replaces the refresh-token.
|
||||
func (c *OIDCAuthProvider) SetRefreshToken(refreshToken string) {
|
||||
c.Config["refresh-token"] = refreshToken
|
||||
func (c OIDCConfig) SetRefreshToken(refreshToken string) {
|
||||
c["refresh-token"] = refreshToken
|
||||
}
|
||||
|
||||
66
kubeconfig/kubeconfig.go
Normal file
66
kubeconfig/kubeconfig.go
Normal file
@@ -0,0 +1,66 @@
|
||||
// Package kubeconfig provides the models of kubeconfig file.
|
||||
package kubeconfig
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"k8s.io/client-go/tools/clientcmd/api"
|
||||
)
|
||||
|
||||
type ContextName string
|
||||
type UserName string
|
||||
|
||||
// Config represents a config.
|
||||
type Config api.Config
|
||||
|
||||
// Context represents a context.
|
||||
type Context api.Context
|
||||
|
||||
// User represents a user.
|
||||
type User api.AuthInfo
|
||||
|
||||
// CurrentAuth represents the current authentication, that is,
|
||||
// context, user and auth-provider.
|
||||
type CurrentAuth struct {
|
||||
ContextName ContextName // empty if UserName is given
|
||||
Context *Context // nil if UserName is given
|
||||
UserName UserName
|
||||
User *User
|
||||
OIDCConfig OIDCConfig
|
||||
}
|
||||
|
||||
// FindCurrentAuth resolves the current context and user.
|
||||
// If contextName is given, this returns the user of the context.
|
||||
// If userName is given, this ignores the context and returns the user.
|
||||
// If any context or user is not found, this returns an error.
|
||||
func FindCurrentAuth(config *Config, contextName ContextName, userName UserName) (*CurrentAuth, error) {
|
||||
var kubeContext *Context
|
||||
if userName == "" {
|
||||
if contextName == "" {
|
||||
contextName = ContextName(config.CurrentContext)
|
||||
}
|
||||
contextNode := config.Contexts[string(contextName)]
|
||||
if contextNode == nil {
|
||||
return nil, errors.Errorf("context %s does not exist", contextName)
|
||||
}
|
||||
kubeContext = (*Context)(contextNode)
|
||||
userName = UserName(kubeContext.AuthInfo)
|
||||
}
|
||||
userNode := config.AuthInfos[string(userName)]
|
||||
if userNode == nil {
|
||||
return nil, errors.Errorf("user %s does not exist", userName)
|
||||
}
|
||||
user := (*User)(userNode)
|
||||
if user.AuthProvider == nil {
|
||||
return nil, errors.Errorf("auth-provider is missing")
|
||||
}
|
||||
if user.AuthProvider.Name != "oidc" {
|
||||
return nil, errors.Errorf("auth-provider must be oidc but is %s", user.AuthProvider.Name)
|
||||
}
|
||||
return &CurrentAuth{
|
||||
ContextName: contextName,
|
||||
Context: kubeContext,
|
||||
UserName: userName,
|
||||
User: user,
|
||||
OIDCConfig: user.AuthProvider.Config,
|
||||
}, nil
|
||||
}
|
||||
@@ -3,7 +3,7 @@ class Kubelogin < Formula
|
||||
homepage "https://github.com/int128/kubelogin"
|
||||
url "https://github.com/int128/kubelogin/releases/download/{{ env "VERSION" }}/kubelogin_darwin_amd64.zip"
|
||||
version "{{ env "VERSION" }}"
|
||||
sha256 "{{ .darwin_amd64_zip_sha256 }}"
|
||||
sha256 "{{ sha256 .darwin_amd64_archive }}"
|
||||
def install
|
||||
bin.install "kubelogin" => "kubelogin"
|
||||
ln_s bin/"kubelogin", bin/"kubectl-oidc_login"
|
||||
|
||||
@@ -26,7 +26,7 @@ spec:
|
||||
version: {{ env "VERSION" }}
|
||||
platforms:
|
||||
- uri: https://github.com/int128/kubelogin/releases/download/{{ env "VERSION" }}/kubelogin_linux_amd64.zip
|
||||
sha256: "{{ .linux_amd64_zip_sha256 }}"
|
||||
sha256: "{{ sha256 .linux_amd64_archive }}"
|
||||
bin: kubelogin
|
||||
files:
|
||||
- from: "kubelogin"
|
||||
@@ -36,7 +36,7 @@ spec:
|
||||
os: linux
|
||||
arch: amd64
|
||||
- uri: https://github.com/int128/kubelogin/releases/download/{{ env "VERSION" }}/kubelogin_darwin_amd64.zip
|
||||
sha256: "{{ .darwin_amd64_zip_sha256 }}"
|
||||
sha256: "{{ sha256 .darwin_amd64_archive }}"
|
||||
bin: kubelogin
|
||||
files:
|
||||
- from: "kubelogin"
|
||||
@@ -46,7 +46,7 @@ spec:
|
||||
os: darwin
|
||||
arch: amd64
|
||||
- uri: https://github.com/int128/kubelogin/releases/download/{{ env "VERSION" }}/kubelogin_windows_amd64.zip
|
||||
sha256: "{{ .windows_amd64_zip_sha256 }}"
|
||||
sha256: "{{ sha256 .windows_amd64_archive }}"
|
||||
bin: kubelogin.exe
|
||||
files:
|
||||
- from: "kubelogin.exe"
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package usecases
|
||||
|
||||
import "context"
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/int128/kubelogin/kubeconfig"
|
||||
)
|
||||
|
||||
//go:generate mockgen -package mock_usecases -destination ../mock_usecases/mock_usecases.go github.com/int128/kubelogin/usecases/interfaces Login
|
||||
|
||||
@@ -9,8 +13,11 @@ type Login interface {
|
||||
}
|
||||
|
||||
type LoginIn struct {
|
||||
KubeConfig string
|
||||
SkipTLSVerify bool
|
||||
SkipOpenBrowser bool
|
||||
ListenPort int
|
||||
KubeConfigFilename string // Default to the environment variable or global config as kubectl
|
||||
KubeContextName kubeconfig.ContextName // Default to the current context but ignored if KubeUserName is set
|
||||
KubeUserName kubeconfig.UserName // Default to the user of the context
|
||||
CertificateAuthorityFilename string // Optional
|
||||
SkipTLSVerify bool
|
||||
SkipOpenBrowser bool
|
||||
ListenPort []int
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
const oidcConfigErrorMessage = `No OIDC configuration found. Did you setup kubectl for OIDC authentication?
|
||||
kubectl config set-credentials %[1]s \
|
||||
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 \
|
||||
@@ -31,58 +31,47 @@ type Login struct {
|
||||
}
|
||||
|
||||
func (u *Login) Do(ctx context.Context, in usecases.LoginIn) error {
|
||||
u.Logger.Debugf(1, "WARNING: Log may contain your secrets, e.g. token or password")
|
||||
u.Logger.Debugf(1, "WARNING: log may contain your secrets such as token or password")
|
||||
|
||||
u.Logger.Debugf(1, "Loading %s", in.KubeConfig)
|
||||
cfg, err := u.KubeConfig.LoadFromFile(in.KubeConfig)
|
||||
mergedKubeConfig, err := u.KubeConfig.LoadByDefaultRules(in.KubeConfigFilename)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not read the kubeconfig")
|
||||
return errors.Wrapf(err, "could not load the kubeconfig")
|
||||
}
|
||||
auth, err := kubeconfig.FindCurrentAuth(mergedKubeConfig, in.KubeContextName, in.KubeUserName)
|
||||
if err != nil {
|
||||
u.Logger.Printf(oidcConfigErrorMessage)
|
||||
return errors.Wrapf(err, "could not find the current authentication provider")
|
||||
}
|
||||
u.Logger.Debugf(1, "Using the authentication provider of the user %s", auth.UserName)
|
||||
destinationKubeConfigFilename := auth.User.LocationOfOrigin
|
||||
if destinationKubeConfigFilename == "" {
|
||||
return errors.Errorf("could not determine the kubeconfig to write")
|
||||
}
|
||||
u.Logger.Debugf(1, "A token will be written to %s", destinationKubeConfigFilename)
|
||||
|
||||
hc, err := u.HTTP.NewClient(adaptors.HTTPClientConfig{
|
||||
OIDCConfig: auth.OIDCConfig,
|
||||
CertificateAuthorityFilename: in.CertificateAuthorityFilename,
|
||||
SkipTLSVerify: in.SkipTLSVerify,
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not set up a HTTP client")
|
||||
}
|
||||
|
||||
u.Logger.Printf("Using current-context: %s", cfg.CurrentContext)
|
||||
authProvider, err := kubeconfig.FindOIDCAuthProvider(cfg)
|
||||
if err != nil {
|
||||
u.Logger.Printf(oidcConfigErrorMessage, cfg.CurrentContext)
|
||||
return errors.Wrapf(err, "could not find an oidc auth-provider in the kubeconfig")
|
||||
}
|
||||
|
||||
clientConfig := u.HTTP.NewClientConfig()
|
||||
clientConfig.SetSkipTLSVerify(in.SkipTLSVerify)
|
||||
if authProvider.IDPCertificateAuthority() != "" {
|
||||
filename := authProvider.IDPCertificateAuthority()
|
||||
u.Logger.Printf("Using the certificate %s", filename)
|
||||
if err := clientConfig.AddCertificateFromFile(filename); err != nil {
|
||||
u.Logger.Printf("Skip the certificate %s: %s", filename, err)
|
||||
if auth.OIDCConfig.IDToken() != "" {
|
||||
u.Logger.Debugf(1, "Found the ID token in the kubeconfig")
|
||||
token, err := u.OIDC.Verify(ctx, adaptors.OIDCVerifyIn{Config: auth.OIDCConfig, Client: hc})
|
||||
if err == nil {
|
||||
u.Logger.Printf("You already have a valid token until %s", token.Expiry)
|
||||
u.dumpIDToken(token)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if authProvider.IDPCertificateAuthorityData() != "" {
|
||||
encoded := authProvider.IDPCertificateAuthorityData()
|
||||
u.Logger.Printf("Using the certificate of idp-certificate-authority-data")
|
||||
if err := clientConfig.AddEncodedCertificate(encoded); err != nil {
|
||||
u.Logger.Printf("Skip the certificate of idp-certificate-authority-data: %s", err)
|
||||
}
|
||||
}
|
||||
hc, err := u.HTTP.NewClient(clientConfig)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not create a HTTP client")
|
||||
}
|
||||
|
||||
if token := u.verifyIDToken(ctx, adaptors.OIDCVerifyTokenIn{
|
||||
IDToken: authProvider.IDToken(),
|
||||
Issuer: authProvider.IDPIssuerURL(),
|
||||
ClientID: authProvider.ClientID(),
|
||||
Client: hc,
|
||||
}); token != nil {
|
||||
u.Logger.Printf("You already have a valid token (until %s)", token.Expiry)
|
||||
return nil
|
||||
u.Logger.Debugf(1, "The ID token was invalid: %s", err)
|
||||
}
|
||||
|
||||
out, err := u.OIDC.Authenticate(ctx,
|
||||
adaptors.OIDCAuthenticateIn{
|
||||
Issuer: authProvider.IDPIssuerURL(),
|
||||
ClientID: authProvider.ClientID(),
|
||||
ClientSecret: authProvider.ClientSecret(),
|
||||
ExtraScopes: authProvider.ExtraScopes(),
|
||||
Config: auth.OIDCConfig,
|
||||
Client: hc,
|
||||
LocalServerPort: in.ListenPort,
|
||||
SkipOpenBrowser: in.SkipOpenBrowser,
|
||||
@@ -93,31 +82,42 @@ func (u *Login) Do(ctx context.Context, in usecases.LoginIn) error {
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not get token from OIDC provider")
|
||||
return errors.Wrapf(err, "could not get a token from the OIDC provider")
|
||||
}
|
||||
u.Logger.Printf("You got a valid token until %s", out.VerifiedIDToken.Expiry)
|
||||
u.dumpIDToken(out.VerifiedIDToken)
|
||||
|
||||
u.Logger.Printf("Got a token for subject %s (valid until %s)", out.VerifiedIDToken.Subject, out.VerifiedIDToken.Expiry)
|
||||
u.Logger.Debugf(1, "Got an ID token %+v", out.VerifiedIDToken)
|
||||
authProvider.SetIDToken(out.IDToken)
|
||||
authProvider.SetRefreshToken(out.RefreshToken)
|
||||
|
||||
u.Logger.Debugf(1, "Writing the ID token and refresh token to %s", in.KubeConfig)
|
||||
if err := u.KubeConfig.WriteToFile(cfg, in.KubeConfig); err != nil {
|
||||
return errors.Wrapf(err, "could not update the kubeconfig")
|
||||
if err := u.writeToken(destinationKubeConfigFilename, auth.UserName, out); err != nil {
|
||||
return errors.Wrapf(err, "could not write the token to the kubeconfig")
|
||||
}
|
||||
u.Logger.Printf("Updated %s", in.KubeConfig)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *Login) verifyIDToken(ctx context.Context, in adaptors.OIDCVerifyTokenIn) *oidc.IDToken {
|
||||
if in.IDToken == "" {
|
||||
return nil
|
||||
func (u *Login) dumpIDToken(token *oidc.IDToken) {
|
||||
var claims map[string]interface{}
|
||||
if err := token.Claims(&claims); err != nil {
|
||||
u.Logger.Debugf(1, "Error while inspection of the ID token: %s", err)
|
||||
}
|
||||
token, err := u.OIDC.VerifyIDToken(ctx, in)
|
||||
if err != nil {
|
||||
u.Logger.Debugf(1, "Could not verify the ID token in the kubeconfig: %s", err)
|
||||
return nil
|
||||
for k, v := range claims {
|
||||
u.Logger.Debugf(1, "The ID token has the claim: %s=%v", k, v)
|
||||
}
|
||||
u.Logger.Debugf(1, "Verified token %+v", token)
|
||||
return token
|
||||
}
|
||||
|
||||
func (u *Login) writeToken(filename string, userName kubeconfig.UserName, out *adaptors.OIDCAuthenticateOut) error {
|
||||
config, err := u.KubeConfig.LoadFromFile(filename)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not load %s", filename)
|
||||
}
|
||||
auth, err := kubeconfig.FindCurrentAuth(config, "", userName)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not find the user %s in %s", userName, filename)
|
||||
}
|
||||
auth.OIDCConfig.SetIDToken(out.IDToken)
|
||||
auth.OIDCConfig.SetRefreshToken(out.RefreshToken)
|
||||
u.Logger.Debugf(1, "Writing the ID token and refresh token to %s", filename)
|
||||
if err := u.KubeConfig.WriteToFile(config, filename); err != nil {
|
||||
return errors.Wrapf(err, "could not update %s", filename)
|
||||
}
|
||||
u.Logger.Printf("Updated %s", filename)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -9,89 +9,174 @@ import (
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/int128/kubelogin/adaptors/interfaces"
|
||||
"github.com/int128/kubelogin/adaptors/mock_adaptors"
|
||||
"github.com/int128/kubelogin/kubeconfig"
|
||||
"github.com/int128/kubelogin/usecases/interfaces"
|
||||
"github.com/pkg/errors"
|
||||
"k8s.io/client-go/tools/clientcmd/api"
|
||||
)
|
||||
|
||||
type loginTestFixture struct {
|
||||
googleOIDCConfig kubeconfig.OIDCConfig
|
||||
googleOIDCConfigWithToken kubeconfig.OIDCConfig
|
||||
googleKubeConfig *kubeconfig.Config
|
||||
googleKubeConfigWithToken *kubeconfig.Config
|
||||
keycloakOIDCConfig kubeconfig.OIDCConfig
|
||||
keycloakOIDCConfigWithToken kubeconfig.OIDCConfig
|
||||
keycloakKubeConfig *kubeconfig.Config
|
||||
keycloakKubeConfigWithToken *kubeconfig.Config
|
||||
mergedKubeConfig *kubeconfig.Config
|
||||
}
|
||||
|
||||
func newLoginTestFixture() loginTestFixture {
|
||||
var f loginTestFixture
|
||||
f.googleOIDCConfig = kubeconfig.OIDCConfig{
|
||||
"client-id": "GOOGLE_CLIENT_ID",
|
||||
"client-secret": "GOOGLE_CLIENT_SECRET",
|
||||
"idp-issuer-url": "https://accounts.google.com",
|
||||
}
|
||||
f.googleKubeConfig = &kubeconfig.Config{
|
||||
APIVersion: "v1",
|
||||
CurrentContext: "googleContext",
|
||||
Contexts: map[string]*api.Context{
|
||||
"googleContext": {
|
||||
LocationOfOrigin: "/path/to/google",
|
||||
AuthInfo: "google",
|
||||
Cluster: "example.k8s.local",
|
||||
},
|
||||
},
|
||||
AuthInfos: map[string]*api.AuthInfo{
|
||||
"google": {
|
||||
LocationOfOrigin: "/path/to/google",
|
||||
AuthProvider: &api.AuthProviderConfig{
|
||||
Name: "oidc",
|
||||
Config: f.googleOIDCConfig,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
f.googleOIDCConfigWithToken = kubeconfig.OIDCConfig{
|
||||
"client-id": "GOOGLE_CLIENT_ID",
|
||||
"client-secret": "GOOGLE_CLIENT_SECRET",
|
||||
"idp-issuer-url": "https://accounts.google.com",
|
||||
"id-token": "YOUR_ID_TOKEN",
|
||||
"refresh-token": "YOUR_REFRESH_TOKEN",
|
||||
}
|
||||
f.googleKubeConfigWithToken = &kubeconfig.Config{
|
||||
APIVersion: "v1",
|
||||
CurrentContext: "googleContext",
|
||||
Contexts: map[string]*api.Context{
|
||||
"googleContext": {
|
||||
LocationOfOrigin: "/path/to/google",
|
||||
AuthInfo: "google",
|
||||
Cluster: "example.k8s.local",
|
||||
},
|
||||
},
|
||||
AuthInfos: map[string]*api.AuthInfo{
|
||||
"google": {
|
||||
LocationOfOrigin: "/path/to/google",
|
||||
AuthProvider: &api.AuthProviderConfig{
|
||||
Name: "oidc",
|
||||
Config: f.googleOIDCConfigWithToken,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
f.keycloakOIDCConfig = kubeconfig.OIDCConfig{
|
||||
"client-id": "KEYCLOAK_CLIENT_ID",
|
||||
"client-secret": "KEYCLOAK_CLIENT_SECRET",
|
||||
"idp-issuer-url": "https://keycloak.example.com",
|
||||
}
|
||||
f.keycloakKubeConfig = &kubeconfig.Config{
|
||||
APIVersion: "v1",
|
||||
CurrentContext: "googleContext",
|
||||
Contexts: map[string]*api.Context{
|
||||
"keycloakContext": {
|
||||
LocationOfOrigin: "/path/to/keycloak",
|
||||
AuthInfo: "keycloak",
|
||||
Cluster: "example.k8s.local",
|
||||
},
|
||||
},
|
||||
AuthInfos: map[string]*api.AuthInfo{
|
||||
"keycloak": {
|
||||
LocationOfOrigin: "/path/to/keycloak",
|
||||
AuthProvider: &api.AuthProviderConfig{
|
||||
Name: "oidc",
|
||||
Config: f.keycloakOIDCConfig,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
f.keycloakOIDCConfigWithToken = kubeconfig.OIDCConfig{
|
||||
"client-id": "KEYCLOAK_CLIENT_ID",
|
||||
"client-secret": "KEYCLOAK_CLIENT_SECRET",
|
||||
"idp-issuer-url": "https://keycloak.example.com",
|
||||
"id-token": "YOUR_ID_TOKEN",
|
||||
"refresh-token": "YOUR_REFRESH_TOKEN",
|
||||
}
|
||||
f.keycloakKubeConfigWithToken = &kubeconfig.Config{
|
||||
APIVersion: "v1",
|
||||
CurrentContext: "googleContext",
|
||||
Contexts: map[string]*api.Context{
|
||||
"keycloakContext": {
|
||||
LocationOfOrigin: "/path/to/keycloak",
|
||||
AuthInfo: "keycloak",
|
||||
Cluster: "example.k8s.local",
|
||||
},
|
||||
},
|
||||
AuthInfos: map[string]*api.AuthInfo{
|
||||
"keycloak": {
|
||||
LocationOfOrigin: "/path/to/keycloak",
|
||||
AuthProvider: &api.AuthProviderConfig{
|
||||
Name: "oidc",
|
||||
Config: f.keycloakOIDCConfigWithToken,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
f.mergedKubeConfig = &kubeconfig.Config{
|
||||
APIVersion: "v1",
|
||||
CurrentContext: "googleContext",
|
||||
Contexts: map[string]*api.Context{
|
||||
"googleContext": {
|
||||
LocationOfOrigin: "/path/to/google",
|
||||
AuthInfo: "google",
|
||||
Cluster: "example.k8s.local",
|
||||
},
|
||||
"keycloakContext": {
|
||||
LocationOfOrigin: "/path/to/keycloak",
|
||||
AuthInfo: "keycloak",
|
||||
Cluster: "example.k8s.local",
|
||||
},
|
||||
},
|
||||
AuthInfos: map[string]*api.AuthInfo{
|
||||
"google": {
|
||||
LocationOfOrigin: "/path/to/google",
|
||||
AuthProvider: &api.AuthProviderConfig{
|
||||
Name: "oidc",
|
||||
Config: f.googleOIDCConfig,
|
||||
},
|
||||
},
|
||||
"keycloak": {
|
||||
LocationOfOrigin: "/path/to/keycloak",
|
||||
AuthProvider: &api.AuthProviderConfig{
|
||||
Name: "oidc",
|
||||
Config: f.keycloakOIDCConfig,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func TestLogin_Do(t *testing.T) {
|
||||
httpClient := &http.Client{}
|
||||
|
||||
newMockKubeConfig := func(ctrl *gomock.Controller, in *api.Config, out *api.Config) adaptors.KubeConfig {
|
||||
kubeConfig := mock_adaptors.NewMockKubeConfig(ctrl)
|
||||
kubeConfig.EXPECT().
|
||||
LoadFromFile("/path/to/kubeconfig").
|
||||
Return(in, nil)
|
||||
kubeConfig.EXPECT().
|
||||
WriteToFile(out, "/path/to/kubeconfig")
|
||||
return kubeConfig
|
||||
}
|
||||
|
||||
newMockHTTP := func(ctrl *gomock.Controller, config adaptors.HTTPClientConfig) adaptors.HTTP {
|
||||
mockHTTP := mock_adaptors.NewMockHTTP(ctrl)
|
||||
mockHTTP.EXPECT().
|
||||
NewClientConfig().
|
||||
Return(config)
|
||||
mockHTTP.EXPECT().
|
||||
NewClient(config).
|
||||
Return(httpClient, nil)
|
||||
return mockHTTP
|
||||
}
|
||||
|
||||
newInConfig := func() *api.Config {
|
||||
return &api.Config{
|
||||
APIVersion: "v1",
|
||||
CurrentContext: "default",
|
||||
Contexts: map[string]*api.Context{
|
||||
"default": {
|
||||
AuthInfo: "google",
|
||||
Cluster: "example.k8s.local",
|
||||
},
|
||||
},
|
||||
AuthInfos: map[string]*api.AuthInfo{
|
||||
"google": {
|
||||
AuthProvider: &api.AuthProviderConfig{
|
||||
Name: "oidc",
|
||||
Config: map[string]string{
|
||||
"client-id": "YOUR_CLIENT_ID",
|
||||
"client-secret": "YOUR_CLIENT_SECRET",
|
||||
"idp-issuer-url": "https://accounts.google.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
newOutConfig := func(in *api.Config) *api.Config {
|
||||
config := in.DeepCopy()
|
||||
config.AuthInfos["google"].AuthProvider.Config["id-token"] = "YOUR_ID_TOKEN"
|
||||
config.AuthInfos["google"].AuthProvider.Config["refresh-token"] = "YOUR_REFRESH_TOKEN"
|
||||
return config
|
||||
}
|
||||
|
||||
t.Run("Defaults", func(t *testing.T) {
|
||||
inConfig := newInConfig()
|
||||
outConfig := newOutConfig(inConfig)
|
||||
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
ctx := context.TODO()
|
||||
|
||||
httpClientConfig := mock_adaptors.NewMockHTTPClientConfig(ctrl)
|
||||
httpClientConfig.EXPECT().
|
||||
SetSkipTLSVerify(false)
|
||||
|
||||
newMockOIDC := func(ctrl *gomock.Controller, ctx context.Context, in adaptors.OIDCAuthenticateIn) *mock_adaptors.MockOIDC {
|
||||
mockOIDC := mock_adaptors.NewMockOIDC(ctrl)
|
||||
mockOIDC.EXPECT().
|
||||
Authenticate(ctx, adaptors.OIDCAuthenticateIn{
|
||||
Issuer: "https://accounts.google.com",
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
ExtraScopes: []string{},
|
||||
LocalServerPort: 10000,
|
||||
Client: httpClient,
|
||||
}, gomock.Any()).
|
||||
Authenticate(ctx, in, gomock.Any()).
|
||||
Do(func(_ context.Context, _ adaptors.OIDCAuthenticateIn, cb adaptors.OIDCAuthenticateCallback) {
|
||||
cb.ShowLocalServerURL("http://localhost:10000")
|
||||
}).
|
||||
@@ -100,58 +185,218 @@ func TestLogin_Do(t *testing.T) {
|
||||
IDToken: "YOUR_ID_TOKEN",
|
||||
RefreshToken: "YOUR_REFRESH_TOKEN",
|
||||
}, nil)
|
||||
return mockOIDC
|
||||
}
|
||||
|
||||
t.Run("Defaults", func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
ctx := context.TODO()
|
||||
f := newLoginTestFixture()
|
||||
|
||||
mockHTTP := mock_adaptors.NewMockHTTP(ctrl)
|
||||
mockHTTP.EXPECT().
|
||||
NewClient(adaptors.HTTPClientConfig{
|
||||
OIDCConfig: f.googleOIDCConfig,
|
||||
}).
|
||||
Return(httpClient, nil)
|
||||
|
||||
mockKubeConfig := mock_adaptors.NewMockKubeConfig(ctrl)
|
||||
mockKubeConfig.EXPECT().
|
||||
LoadByDefaultRules("").
|
||||
Return(f.mergedKubeConfig, nil)
|
||||
mockKubeConfig.EXPECT().
|
||||
LoadFromFile("/path/to/google").
|
||||
Return(f.googleKubeConfig, nil)
|
||||
mockKubeConfig.EXPECT().
|
||||
WriteToFile(f.googleKubeConfigWithToken, "/path/to/google")
|
||||
|
||||
oidcIn := adaptors.OIDCAuthenticateIn{
|
||||
Config: f.googleOIDCConfig,
|
||||
LocalServerPort: []int{10000},
|
||||
Client: httpClient,
|
||||
}
|
||||
|
||||
u := Login{
|
||||
KubeConfig: newMockKubeConfig(ctrl, inConfig, outConfig),
|
||||
HTTP: newMockHTTP(ctrl, httpClientConfig),
|
||||
OIDC: mockOIDC,
|
||||
KubeConfig: mockKubeConfig,
|
||||
HTTP: mockHTTP,
|
||||
OIDC: newMockOIDC(ctrl, ctx, oidcIn),
|
||||
Logger: mock_adaptors.NewLogger(t, ctrl),
|
||||
}
|
||||
if err := u.Do(ctx, usecases.LoginIn{
|
||||
KubeConfig: "/path/to/kubeconfig",
|
||||
ListenPort: 10000,
|
||||
ListenPort: []int{10000},
|
||||
}); err != nil {
|
||||
t.Errorf("Do returned error: %+v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("KubeConfigFilename", func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
ctx := context.TODO()
|
||||
f := newLoginTestFixture()
|
||||
|
||||
mockHTTP := mock_adaptors.NewMockHTTP(ctrl)
|
||||
mockHTTP.EXPECT().
|
||||
NewClient(adaptors.HTTPClientConfig{
|
||||
OIDCConfig: f.googleOIDCConfig,
|
||||
}).
|
||||
Return(httpClient, nil)
|
||||
|
||||
mockKubeConfig := mock_adaptors.NewMockKubeConfig(ctrl)
|
||||
mockKubeConfig.EXPECT().
|
||||
LoadByDefaultRules("/path/to/kubeconfig").
|
||||
Return(f.mergedKubeConfig, nil)
|
||||
mockKubeConfig.EXPECT().
|
||||
LoadFromFile("/path/to/google").
|
||||
Return(f.googleKubeConfig, nil)
|
||||
mockKubeConfig.EXPECT().
|
||||
WriteToFile(f.googleKubeConfigWithToken, "/path/to/google")
|
||||
|
||||
oidcIn := adaptors.OIDCAuthenticateIn{
|
||||
Config: f.googleOIDCConfig,
|
||||
LocalServerPort: []int{10000},
|
||||
Client: httpClient,
|
||||
}
|
||||
|
||||
u := Login{
|
||||
KubeConfig: mockKubeConfig,
|
||||
HTTP: mockHTTP,
|
||||
OIDC: newMockOIDC(ctrl, ctx, oidcIn),
|
||||
Logger: mock_adaptors.NewLogger(t, ctrl),
|
||||
}
|
||||
if err := u.Do(ctx, usecases.LoginIn{
|
||||
KubeConfigFilename: "/path/to/kubeconfig",
|
||||
ListenPort: []int{10000},
|
||||
}); err != nil {
|
||||
t.Errorf("Do returned error: %+v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("KubeContextName", func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
ctx := context.TODO()
|
||||
f := newLoginTestFixture()
|
||||
|
||||
mockHTTP := mock_adaptors.NewMockHTTP(ctrl)
|
||||
mockHTTP.EXPECT().
|
||||
NewClient(adaptors.HTTPClientConfig{
|
||||
OIDCConfig: f.keycloakOIDCConfig,
|
||||
}).
|
||||
Return(httpClient, nil)
|
||||
|
||||
mockKubeConfig := mock_adaptors.NewMockKubeConfig(ctrl)
|
||||
mockKubeConfig.EXPECT().
|
||||
LoadByDefaultRules("").
|
||||
Return(f.mergedKubeConfig, nil)
|
||||
mockKubeConfig.EXPECT().
|
||||
LoadFromFile("/path/to/keycloak").
|
||||
Return(f.keycloakKubeConfig, nil)
|
||||
mockKubeConfig.EXPECT().
|
||||
WriteToFile(f.keycloakKubeConfigWithToken, "/path/to/keycloak")
|
||||
|
||||
oidcIn := adaptors.OIDCAuthenticateIn{
|
||||
Config: f.keycloakOIDCConfig,
|
||||
LocalServerPort: []int{10000},
|
||||
Client: httpClient,
|
||||
}
|
||||
|
||||
u := Login{
|
||||
KubeConfig: mockKubeConfig,
|
||||
HTTP: mockHTTP,
|
||||
OIDC: newMockOIDC(ctrl, ctx, oidcIn),
|
||||
Logger: mock_adaptors.NewLogger(t, ctrl),
|
||||
}
|
||||
if err := u.Do(ctx, usecases.LoginIn{
|
||||
KubeContextName: "keycloakContext",
|
||||
ListenPort: []int{10000},
|
||||
}); err != nil {
|
||||
t.Errorf("Do returned error: %+v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("KubeUserName", func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
ctx := context.TODO()
|
||||
f := newLoginTestFixture()
|
||||
|
||||
mockHTTP := mock_adaptors.NewMockHTTP(ctrl)
|
||||
mockHTTP.EXPECT().
|
||||
NewClient(adaptors.HTTPClientConfig{
|
||||
OIDCConfig: f.keycloakOIDCConfig,
|
||||
}).
|
||||
Return(httpClient, nil)
|
||||
|
||||
mockKubeConfig := mock_adaptors.NewMockKubeConfig(ctrl)
|
||||
mockKubeConfig.EXPECT().
|
||||
LoadByDefaultRules("").
|
||||
Return(f.mergedKubeConfig, nil)
|
||||
mockKubeConfig.EXPECT().
|
||||
LoadFromFile("/path/to/keycloak").
|
||||
Return(f.keycloakKubeConfig, nil)
|
||||
mockKubeConfig.EXPECT().
|
||||
WriteToFile(f.keycloakKubeConfigWithToken, "/path/to/keycloak")
|
||||
|
||||
oidcIn := adaptors.OIDCAuthenticateIn{
|
||||
Config: f.keycloakOIDCConfig,
|
||||
LocalServerPort: []int{10000},
|
||||
Client: httpClient,
|
||||
}
|
||||
|
||||
u := Login{
|
||||
KubeConfig: mockKubeConfig,
|
||||
HTTP: mockHTTP,
|
||||
OIDC: newMockOIDC(ctrl, ctx, oidcIn),
|
||||
Logger: mock_adaptors.NewLogger(t, ctrl),
|
||||
}
|
||||
if err := u.Do(ctx, usecases.LoginIn{
|
||||
KubeUserName: "keycloak",
|
||||
ListenPort: []int{10000},
|
||||
}); err != nil {
|
||||
t.Errorf("Do returned error: %+v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("SkipTLSVerify", func(t *testing.T) {
|
||||
inConfig := newInConfig()
|
||||
outConfig := newOutConfig(inConfig)
|
||||
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
ctx := context.TODO()
|
||||
f := newLoginTestFixture()
|
||||
|
||||
httpClientConfig := mock_adaptors.NewMockHTTPClientConfig(ctrl)
|
||||
httpClientConfig.EXPECT().
|
||||
SetSkipTLSVerify(true)
|
||||
mockHTTP := mock_adaptors.NewMockHTTP(ctrl)
|
||||
mockHTTP.EXPECT().
|
||||
NewClient(adaptors.HTTPClientConfig{
|
||||
OIDCConfig: f.googleOIDCConfig,
|
||||
SkipTLSVerify: true,
|
||||
}).
|
||||
Return(httpClient, nil)
|
||||
|
||||
mockOIDC := mock_adaptors.NewMockOIDC(ctrl)
|
||||
mockOIDC.EXPECT().
|
||||
Authenticate(ctx, adaptors.OIDCAuthenticateIn{
|
||||
Issuer: "https://accounts.google.com",
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
ExtraScopes: []string{},
|
||||
LocalServerPort: 10000,
|
||||
Client: httpClient,
|
||||
}, gomock.Any()).
|
||||
Return(&adaptors.OIDCAuthenticateOut{
|
||||
VerifiedIDToken: &oidc.IDToken{Subject: "SUBJECT"},
|
||||
IDToken: "YOUR_ID_TOKEN",
|
||||
RefreshToken: "YOUR_REFRESH_TOKEN",
|
||||
}, nil)
|
||||
mockKubeConfig := mock_adaptors.NewMockKubeConfig(ctrl)
|
||||
mockKubeConfig.EXPECT().
|
||||
LoadByDefaultRules("").
|
||||
Return(f.mergedKubeConfig, nil)
|
||||
mockKubeConfig.EXPECT().
|
||||
LoadFromFile("/path/to/google").
|
||||
Return(f.googleKubeConfig, nil)
|
||||
mockKubeConfig.EXPECT().
|
||||
WriteToFile(f.googleKubeConfigWithToken, "/path/to/google")
|
||||
|
||||
oidcIn := adaptors.OIDCAuthenticateIn{
|
||||
Config: f.googleOIDCConfig,
|
||||
LocalServerPort: []int{10000},
|
||||
Client: httpClient,
|
||||
}
|
||||
|
||||
u := Login{
|
||||
KubeConfig: newMockKubeConfig(ctrl, inConfig, outConfig),
|
||||
HTTP: newMockHTTP(ctrl, httpClientConfig),
|
||||
OIDC: mockOIDC,
|
||||
KubeConfig: mockKubeConfig,
|
||||
HTTP: mockHTTP,
|
||||
OIDC: newMockOIDC(ctrl, ctx, oidcIn),
|
||||
Logger: mock_adaptors.NewLogger(t, ctrl),
|
||||
}
|
||||
if err := u.Do(ctx, usecases.LoginIn{
|
||||
KubeConfig: "/path/to/kubeconfig",
|
||||
ListenPort: 10000,
|
||||
ListenPort: []int{10000},
|
||||
SkipTLSVerify: true,
|
||||
}); err != nil {
|
||||
t.Errorf("Do returned error: %+v", err)
|
||||
@@ -159,43 +404,43 @@ func TestLogin_Do(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("SkipOpenBrowser", func(t *testing.T) {
|
||||
inConfig := newInConfig()
|
||||
outConfig := newOutConfig(inConfig)
|
||||
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
ctx := context.TODO()
|
||||
f := newLoginTestFixture()
|
||||
|
||||
httpClientConfig := mock_adaptors.NewMockHTTPClientConfig(ctrl)
|
||||
httpClientConfig.EXPECT().
|
||||
SetSkipTLSVerify(false)
|
||||
mockHTTP := mock_adaptors.NewMockHTTP(ctrl)
|
||||
mockHTTP.EXPECT().
|
||||
NewClient(adaptors.HTTPClientConfig{
|
||||
OIDCConfig: f.googleOIDCConfig,
|
||||
}).
|
||||
Return(httpClient, nil)
|
||||
|
||||
mockOIDC := mock_adaptors.NewMockOIDC(ctrl)
|
||||
mockOIDC.EXPECT().
|
||||
Authenticate(ctx, adaptors.OIDCAuthenticateIn{
|
||||
Issuer: "https://accounts.google.com",
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
ExtraScopes: []string{},
|
||||
LocalServerPort: 10000,
|
||||
Client: httpClient,
|
||||
SkipOpenBrowser: true,
|
||||
}, gomock.Any()).
|
||||
Return(&adaptors.OIDCAuthenticateOut{
|
||||
VerifiedIDToken: &oidc.IDToken{Subject: "SUBJECT"},
|
||||
IDToken: "YOUR_ID_TOKEN",
|
||||
RefreshToken: "YOUR_REFRESH_TOKEN",
|
||||
}, nil)
|
||||
mockKubeConfig := mock_adaptors.NewMockKubeConfig(ctrl)
|
||||
mockKubeConfig.EXPECT().
|
||||
LoadByDefaultRules("").
|
||||
Return(f.mergedKubeConfig, nil)
|
||||
mockKubeConfig.EXPECT().
|
||||
LoadFromFile("/path/to/google").
|
||||
Return(f.googleKubeConfig, nil)
|
||||
mockKubeConfig.EXPECT().
|
||||
WriteToFile(f.googleKubeConfigWithToken, "/path/to/google")
|
||||
|
||||
oidcIn := adaptors.OIDCAuthenticateIn{
|
||||
Config: f.googleOIDCConfig,
|
||||
LocalServerPort: []int{10000},
|
||||
Client: httpClient,
|
||||
SkipOpenBrowser: true,
|
||||
}
|
||||
|
||||
u := Login{
|
||||
KubeConfig: newMockKubeConfig(ctrl, inConfig, outConfig),
|
||||
HTTP: newMockHTTP(ctrl, httpClientConfig),
|
||||
OIDC: mockOIDC,
|
||||
KubeConfig: mockKubeConfig,
|
||||
HTTP: mockHTTP,
|
||||
OIDC: newMockOIDC(ctrl, ctx, oidcIn),
|
||||
Logger: mock_adaptors.NewLogger(t, ctrl),
|
||||
}
|
||||
if err := u.Do(ctx, usecases.LoginIn{
|
||||
KubeConfig: "/path/to/kubeconfig",
|
||||
ListenPort: 10000,
|
||||
ListenPort: []int{10000},
|
||||
SkipOpenBrowser: true,
|
||||
}); err != nil {
|
||||
t.Errorf("Do returned error: %+v", err)
|
||||
@@ -203,317 +448,137 @@ func TestLogin_Do(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("KubeConfig/ValidToken", func(t *testing.T) {
|
||||
inConfig := newInConfig()
|
||||
inConfig.AuthInfos["google"].AuthProvider.Config["id-token"] = "VALID"
|
||||
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
ctx := context.TODO()
|
||||
f := newLoginTestFixture()
|
||||
f.googleOIDCConfig.SetIDToken("VALID_TOKEN")
|
||||
|
||||
kubeConfig := mock_adaptors.NewMockKubeConfig(ctrl)
|
||||
kubeConfig.EXPECT().
|
||||
LoadFromFile("/path/to/kubeconfig").
|
||||
Return(inConfig, nil)
|
||||
mockHTTP := mock_adaptors.NewMockHTTP(ctrl)
|
||||
mockHTTP.EXPECT().
|
||||
NewClient(adaptors.HTTPClientConfig{
|
||||
OIDCConfig: f.googleOIDCConfig,
|
||||
}).
|
||||
Return(httpClient, nil)
|
||||
|
||||
httpClientConfig := mock_adaptors.NewMockHTTPClientConfig(ctrl)
|
||||
httpClientConfig.EXPECT().
|
||||
SetSkipTLSVerify(false)
|
||||
mockKubeConfig := mock_adaptors.NewMockKubeConfig(ctrl)
|
||||
mockKubeConfig.EXPECT().
|
||||
LoadByDefaultRules("").
|
||||
Return(f.mergedKubeConfig, nil)
|
||||
|
||||
mockOIDC := mock_adaptors.NewMockOIDC(ctrl)
|
||||
mockOIDC.EXPECT().
|
||||
VerifyIDToken(ctx, adaptors.OIDCVerifyTokenIn{
|
||||
IDToken: "VALID",
|
||||
Issuer: "https://accounts.google.com",
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
Client: httpClient,
|
||||
Verify(ctx, adaptors.OIDCVerifyIn{
|
||||
Config: f.googleOIDCConfig,
|
||||
Client: httpClient,
|
||||
}).
|
||||
Return(&oidc.IDToken{}, nil)
|
||||
|
||||
u := Login{
|
||||
KubeConfig: kubeConfig,
|
||||
HTTP: newMockHTTP(ctrl, httpClientConfig),
|
||||
KubeConfig: mockKubeConfig,
|
||||
HTTP: mockHTTP,
|
||||
OIDC: mockOIDC,
|
||||
Logger: mock_adaptors.NewLogger(t, ctrl),
|
||||
}
|
||||
if err := u.Do(ctx, usecases.LoginIn{
|
||||
KubeConfig: "/path/to/kubeconfig",
|
||||
ListenPort: 10000,
|
||||
ListenPort: []int{10000},
|
||||
}); err != nil {
|
||||
t.Errorf("Do returned error: %+v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("KubeConfig/InvalidToken", func(t *testing.T) {
|
||||
inConfig := newInConfig()
|
||||
inConfig.AuthInfos["google"].AuthProvider.Config["id-token"] = "EXPIRED"
|
||||
outConfig := newOutConfig(inConfig)
|
||||
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
ctx := context.TODO()
|
||||
f := newLoginTestFixture()
|
||||
f.googleOIDCConfig.SetIDToken("EXPIRED_TOKEN")
|
||||
|
||||
httpClientConfig := mock_adaptors.NewMockHTTPClientConfig(ctrl)
|
||||
httpClientConfig.EXPECT().
|
||||
SetSkipTLSVerify(false)
|
||||
mockHTTP := mock_adaptors.NewMockHTTP(ctrl)
|
||||
mockHTTP.EXPECT().
|
||||
NewClient(adaptors.HTTPClientConfig{
|
||||
OIDCConfig: f.googleOIDCConfig,
|
||||
}).
|
||||
Return(httpClient, nil)
|
||||
|
||||
mockOIDC := mock_adaptors.NewMockOIDC(ctrl)
|
||||
mockKubeConfig := mock_adaptors.NewMockKubeConfig(ctrl)
|
||||
mockKubeConfig.EXPECT().
|
||||
LoadByDefaultRules("").
|
||||
Return(f.mergedKubeConfig, nil)
|
||||
mockKubeConfig.EXPECT().
|
||||
LoadFromFile("/path/to/google").
|
||||
Return(f.googleKubeConfig, nil)
|
||||
mockKubeConfig.EXPECT().
|
||||
WriteToFile(f.googleKubeConfigWithToken, "/path/to/google")
|
||||
|
||||
mockOIDC := newMockOIDC(ctrl, ctx, adaptors.OIDCAuthenticateIn{
|
||||
Config: f.googleOIDCConfig,
|
||||
LocalServerPort: []int{10000},
|
||||
Client: httpClient,
|
||||
})
|
||||
mockOIDC.EXPECT().
|
||||
VerifyIDToken(ctx, adaptors.OIDCVerifyTokenIn{
|
||||
IDToken: "EXPIRED",
|
||||
Issuer: "https://accounts.google.com",
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
Client: httpClient,
|
||||
Verify(ctx, adaptors.OIDCVerifyIn{
|
||||
Config: f.googleOIDCConfig,
|
||||
Client: httpClient,
|
||||
}).
|
||||
Return(nil, errors.New("token is expired"))
|
||||
mockOIDC.EXPECT().
|
||||
Authenticate(ctx, adaptors.OIDCAuthenticateIn{
|
||||
Issuer: "https://accounts.google.com",
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
ExtraScopes: []string{},
|
||||
LocalServerPort: 10000,
|
||||
Client: httpClient,
|
||||
}, gomock.Any()).
|
||||
Return(&adaptors.OIDCAuthenticateOut{
|
||||
VerifiedIDToken: &oidc.IDToken{Subject: "SUBJECT"},
|
||||
IDToken: "YOUR_ID_TOKEN",
|
||||
RefreshToken: "YOUR_REFRESH_TOKEN",
|
||||
}, nil)
|
||||
|
||||
u := Login{
|
||||
KubeConfig: newMockKubeConfig(ctrl, inConfig, outConfig),
|
||||
HTTP: newMockHTTP(ctrl, httpClientConfig),
|
||||
KubeConfig: mockKubeConfig,
|
||||
HTTP: mockHTTP,
|
||||
OIDC: mockOIDC,
|
||||
Logger: mock_adaptors.NewLogger(t, ctrl),
|
||||
}
|
||||
if err := u.Do(ctx, usecases.LoginIn{
|
||||
KubeConfig: "/path/to/kubeconfig",
|
||||
ListenPort: 10000,
|
||||
ListenPort: []int{10000},
|
||||
}); err != nil {
|
||||
t.Errorf("Do returned error: %+v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("KubeConfig/extra-scopes", func(t *testing.T) {
|
||||
inConfig := newInConfig()
|
||||
inConfig.AuthInfos["google"].AuthProvider.Config["extra-scopes"] = "email,profile"
|
||||
outConfig := newOutConfig(inConfig)
|
||||
|
||||
t.Run("Certificates", func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
ctx := context.TODO()
|
||||
f := newLoginTestFixture()
|
||||
f.googleOIDCConfig["idp-certificate-authority"] = "/path/to/cert2"
|
||||
f.googleOIDCConfig["idp-certificate-authority-data"] = "base64encoded"
|
||||
f.googleOIDCConfigWithToken["idp-certificate-authority"] = "/path/to/cert2"
|
||||
f.googleOIDCConfigWithToken["idp-certificate-authority-data"] = "base64encoded"
|
||||
|
||||
httpClientConfig := mock_adaptors.NewMockHTTPClientConfig(ctrl)
|
||||
httpClientConfig.EXPECT().
|
||||
SetSkipTLSVerify(false)
|
||||
mockHTTP := mock_adaptors.NewMockHTTP(ctrl)
|
||||
mockHTTP.EXPECT().
|
||||
NewClient(adaptors.HTTPClientConfig{
|
||||
OIDCConfig: f.googleOIDCConfig,
|
||||
CertificateAuthorityFilename: "/path/to/cert1",
|
||||
}).
|
||||
Return(httpClient, nil)
|
||||
|
||||
mockOIDC := mock_adaptors.NewMockOIDC(ctrl)
|
||||
mockOIDC.EXPECT().
|
||||
Authenticate(ctx, adaptors.OIDCAuthenticateIn{
|
||||
Issuer: "https://accounts.google.com",
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
ExtraScopes: []string{"email", "profile"},
|
||||
LocalServerPort: 10000,
|
||||
Client: httpClient,
|
||||
}, gomock.Any()).
|
||||
Return(&adaptors.OIDCAuthenticateOut{
|
||||
VerifiedIDToken: &oidc.IDToken{Subject: "SUBJECT"},
|
||||
IDToken: "YOUR_ID_TOKEN",
|
||||
RefreshToken: "YOUR_REFRESH_TOKEN",
|
||||
}, nil)
|
||||
mockKubeConfig := mock_adaptors.NewMockKubeConfig(ctrl)
|
||||
mockKubeConfig.EXPECT().
|
||||
LoadByDefaultRules("").
|
||||
Return(f.mergedKubeConfig, nil)
|
||||
mockKubeConfig.EXPECT().
|
||||
LoadFromFile("/path/to/google").
|
||||
Return(f.googleKubeConfig, nil)
|
||||
mockKubeConfig.EXPECT().
|
||||
WriteToFile(f.googleKubeConfigWithToken, "/path/to/google")
|
||||
|
||||
oidcIn := adaptors.OIDCAuthenticateIn{
|
||||
Config: f.googleOIDCConfig,
|
||||
LocalServerPort: []int{10000},
|
||||
Client: httpClient,
|
||||
}
|
||||
|
||||
u := Login{
|
||||
KubeConfig: newMockKubeConfig(ctrl, inConfig, outConfig),
|
||||
HTTP: newMockHTTP(ctrl, httpClientConfig),
|
||||
OIDC: mockOIDC,
|
||||
KubeConfig: mockKubeConfig,
|
||||
HTTP: mockHTTP,
|
||||
OIDC: newMockOIDC(ctrl, ctx, oidcIn),
|
||||
Logger: mock_adaptors.NewLogger(t, ctrl),
|
||||
}
|
||||
if err := u.Do(ctx, usecases.LoginIn{
|
||||
KubeConfig: "/path/to/kubeconfig",
|
||||
ListenPort: 10000,
|
||||
}); err != nil {
|
||||
t.Errorf("Do returned error: %+v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("KubeConfig/idp-certificate-authority", func(t *testing.T) {
|
||||
inConfig := newInConfig()
|
||||
inConfig.AuthInfos["google"].AuthProvider.Config["idp-certificate-authority"] = "/path/to/cert"
|
||||
outConfig := newOutConfig(inConfig)
|
||||
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
ctx := context.TODO()
|
||||
|
||||
httpClientConfig := mock_adaptors.NewMockHTTPClientConfig(ctrl)
|
||||
httpClientConfig.EXPECT().
|
||||
SetSkipTLSVerify(false)
|
||||
httpClientConfig.EXPECT().
|
||||
AddCertificateFromFile("/path/to/cert")
|
||||
|
||||
mockOIDC := mock_adaptors.NewMockOIDC(ctrl)
|
||||
mockOIDC.EXPECT().
|
||||
Authenticate(ctx, adaptors.OIDCAuthenticateIn{
|
||||
Issuer: "https://accounts.google.com",
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
ExtraScopes: []string{},
|
||||
LocalServerPort: 10000,
|
||||
Client: httpClient,
|
||||
}, gomock.Any()).
|
||||
Return(&adaptors.OIDCAuthenticateOut{
|
||||
VerifiedIDToken: &oidc.IDToken{Subject: "SUBJECT"},
|
||||
IDToken: "YOUR_ID_TOKEN",
|
||||
RefreshToken: "YOUR_REFRESH_TOKEN",
|
||||
}, nil)
|
||||
|
||||
u := Login{
|
||||
KubeConfig: newMockKubeConfig(ctrl, inConfig, outConfig),
|
||||
HTTP: newMockHTTP(ctrl, httpClientConfig),
|
||||
OIDC: mockOIDC,
|
||||
Logger: mock_adaptors.NewLogger(t, ctrl),
|
||||
}
|
||||
if err := u.Do(ctx, usecases.LoginIn{
|
||||
KubeConfig: "/path/to/kubeconfig",
|
||||
ListenPort: 10000,
|
||||
}); err != nil {
|
||||
t.Errorf("Do returned error: %+v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("KubeConfig/idp-certificate-authority/error", func(t *testing.T) {
|
||||
inConfig := newInConfig()
|
||||
inConfig.AuthInfos["google"].AuthProvider.Config["idp-certificate-authority"] = "/path/to/cert"
|
||||
outConfig := newOutConfig(inConfig)
|
||||
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
ctx := context.TODO()
|
||||
|
||||
httpClientConfig := mock_adaptors.NewMockHTTPClientConfig(ctrl)
|
||||
httpClientConfig.EXPECT().
|
||||
SetSkipTLSVerify(false)
|
||||
httpClientConfig.EXPECT().
|
||||
AddCertificateFromFile("/path/to/cert").
|
||||
Return(errors.New("not found"))
|
||||
|
||||
mockOIDC := mock_adaptors.NewMockOIDC(ctrl)
|
||||
mockOIDC.EXPECT().
|
||||
Authenticate(ctx, adaptors.OIDCAuthenticateIn{
|
||||
Issuer: "https://accounts.google.com",
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
ExtraScopes: []string{},
|
||||
LocalServerPort: 10000,
|
||||
Client: httpClient,
|
||||
}, gomock.Any()).
|
||||
Return(&adaptors.OIDCAuthenticateOut{
|
||||
VerifiedIDToken: &oidc.IDToken{Subject: "SUBJECT"},
|
||||
IDToken: "YOUR_ID_TOKEN",
|
||||
RefreshToken: "YOUR_REFRESH_TOKEN",
|
||||
}, nil)
|
||||
|
||||
u := Login{
|
||||
KubeConfig: newMockKubeConfig(ctrl, inConfig, outConfig),
|
||||
HTTP: newMockHTTP(ctrl, httpClientConfig),
|
||||
OIDC: mockOIDC,
|
||||
Logger: mock_adaptors.NewLogger(t, ctrl),
|
||||
}
|
||||
if err := u.Do(ctx, usecases.LoginIn{
|
||||
KubeConfig: "/path/to/kubeconfig",
|
||||
ListenPort: 10000,
|
||||
}); err != nil {
|
||||
t.Errorf("Do returned error: %+v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("KubeConfig/idp-certificate-authority-data", func(t *testing.T) {
|
||||
inConfig := newInConfig()
|
||||
inConfig.AuthInfos["google"].AuthProvider.Config["idp-certificate-authority-data"] = "base64encoded"
|
||||
outConfig := newOutConfig(inConfig)
|
||||
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
ctx := context.TODO()
|
||||
|
||||
httpClientConfig := mock_adaptors.NewMockHTTPClientConfig(ctrl)
|
||||
httpClientConfig.EXPECT().
|
||||
SetSkipTLSVerify(false)
|
||||
httpClientConfig.EXPECT().
|
||||
AddEncodedCertificate("base64encoded")
|
||||
|
||||
mockOIDC := mock_adaptors.NewMockOIDC(ctrl)
|
||||
mockOIDC.EXPECT().
|
||||
Authenticate(ctx, adaptors.OIDCAuthenticateIn{
|
||||
Issuer: "https://accounts.google.com",
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
ExtraScopes: []string{},
|
||||
LocalServerPort: 10000,
|
||||
Client: httpClient,
|
||||
}, gomock.Any()).
|
||||
Return(&adaptors.OIDCAuthenticateOut{
|
||||
VerifiedIDToken: &oidc.IDToken{Subject: "SUBJECT"},
|
||||
IDToken: "YOUR_ID_TOKEN",
|
||||
RefreshToken: "YOUR_REFRESH_TOKEN",
|
||||
}, nil)
|
||||
|
||||
u := Login{
|
||||
KubeConfig: newMockKubeConfig(ctrl, inConfig, outConfig),
|
||||
HTTP: newMockHTTP(ctrl, httpClientConfig),
|
||||
OIDC: mockOIDC,
|
||||
Logger: mock_adaptors.NewLogger(t, ctrl),
|
||||
}
|
||||
if err := u.Do(ctx, usecases.LoginIn{
|
||||
KubeConfig: "/path/to/kubeconfig",
|
||||
ListenPort: 10000,
|
||||
}); err != nil {
|
||||
t.Errorf("Do returned error: %+v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("KubeConfig/idp-certificate-authority-data/error", func(t *testing.T) {
|
||||
inConfig := newInConfig()
|
||||
inConfig.AuthInfos["google"].AuthProvider.Config["idp-certificate-authority-data"] = "base64encoded"
|
||||
outConfig := newOutConfig(inConfig)
|
||||
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
ctx := context.TODO()
|
||||
|
||||
httpClientConfig := mock_adaptors.NewMockHTTPClientConfig(ctrl)
|
||||
httpClientConfig.EXPECT().
|
||||
SetSkipTLSVerify(false)
|
||||
httpClientConfig.EXPECT().
|
||||
AddEncodedCertificate("base64encoded").
|
||||
Return(errors.New("invalid"))
|
||||
|
||||
mockOIDC := mock_adaptors.NewMockOIDC(ctrl)
|
||||
mockOIDC.EXPECT().
|
||||
Authenticate(ctx, adaptors.OIDCAuthenticateIn{
|
||||
Issuer: "https://accounts.google.com",
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
ExtraScopes: []string{},
|
||||
LocalServerPort: 10000,
|
||||
Client: httpClient,
|
||||
}, gomock.Any()).
|
||||
Return(&adaptors.OIDCAuthenticateOut{
|
||||
VerifiedIDToken: &oidc.IDToken{Subject: "SUBJECT"},
|
||||
IDToken: "YOUR_ID_TOKEN",
|
||||
RefreshToken: "YOUR_REFRESH_TOKEN",
|
||||
}, nil)
|
||||
|
||||
u := Login{
|
||||
KubeConfig: newMockKubeConfig(ctrl, inConfig, outConfig),
|
||||
HTTP: newMockHTTP(ctrl, httpClientConfig),
|
||||
OIDC: mockOIDC,
|
||||
Logger: mock_adaptors.NewLogger(t, ctrl),
|
||||
}
|
||||
if err := u.Do(ctx, usecases.LoginIn{
|
||||
KubeConfig: "/path/to/kubeconfig",
|
||||
ListenPort: 10000,
|
||||
ListenPort: []int{10000},
|
||||
CertificateAuthorityFilename: "/path/to/cert1",
|
||||
}); err != nil {
|
||||
t.Errorf("Do returned error: %+v", err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user