mirror of
https://github.com/int128/kubelogin.git
synced 2026-03-02 17:00:20 +00:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
000711f52e | ||
|
|
e465c4852b | ||
|
|
003badb0bc | ||
|
|
0873a193a5 | ||
|
|
5e80b1858e | ||
|
|
c816281657 | ||
|
|
0db49860f9 | ||
|
|
d70c9db036 | ||
|
|
675b5e5fff | ||
|
|
e3bfc321a2 | ||
|
|
b34d0fb32f | ||
|
|
4c61a71ed4 | ||
|
|
8a02ed0fb0 | ||
|
|
3485c5408e | ||
|
|
fb99977e98 |
2
Makefile
2
Makefile
@@ -10,7 +10,7 @@ all: $(TARGET)
|
||||
check:
|
||||
golint
|
||||
go vet
|
||||
$(MAKE) -C cli_test/authserver/testdata
|
||||
$(MAKE) -C adaptors_test/authserver/testdata
|
||||
go test -v ./...
|
||||
|
||||
$(TARGET): $(wildcard *.go)
|
||||
|
||||
22
README.md
22
README.md
@@ -4,7 +4,7 @@ This is a kubectl plugin for [Kubernetes OpenID Connect (OIDC) authentication](h
|
||||
It gets a token from the OIDC provider and writes it to the kubeconfig.
|
||||
|
||||
|
||||
## TL;DR
|
||||
## Getting Started
|
||||
|
||||
You need to setup the following components:
|
||||
|
||||
@@ -13,11 +13,21 @@ You need to setup the following components:
|
||||
- Role for your group or user
|
||||
- kubectl authentication
|
||||
|
||||
You can install this by brew tap or from the [releases](https://github.com/int128/kubelogin/releases).
|
||||
You can install [the latest release](https://github.com/int128/kubelogin/releases) by the following ways:
|
||||
|
||||
```sh
|
||||
# Homebrew
|
||||
brew tap int128/kubelogin
|
||||
brew install kubelogin
|
||||
|
||||
# Krew (experimental)
|
||||
curl -LO https://github.com/int128/kubelogin/releases/download/v1.9.0/oidc-login.yaml
|
||||
kubectl krew install --manifest oidc-login.yaml
|
||||
|
||||
# GitHub Releases
|
||||
curl -LO https://github.com/int128/kubelogin/releases/download/v1.9.0/kubelogin_linux_amd64.zip
|
||||
unzip kubelogin_linux_amd64.zip
|
||||
ln -s kubelogin kubectl-oidc_login
|
||||
```
|
||||
|
||||
After initial setup or when the token has been expired, just run:
|
||||
@@ -31,7 +41,7 @@ After initial setup or when the token has been expired, just run:
|
||||
2018/08/27 15:03:09 Updated /home/user/.kube/config
|
||||
```
|
||||
|
||||
or run as a plugin:
|
||||
or run as a kubectl plugin:
|
||||
|
||||
```
|
||||
% kubectl oidc-login
|
||||
@@ -118,6 +128,12 @@ kubectl config set-credentials keycloak \
|
||||
If kubelogin could not parse the certificate, it shows a warning and skips it.
|
||||
|
||||
|
||||
### HTTP Proxy
|
||||
|
||||
You can set the following environment variables if you are behind a proxy: `HTTP_PROXY`, `HTTPS_PROXY` and `NO_PROXY`.
|
||||
See also [net/http#ProxyFromEnvironment](https://golang.org/pkg/net/http/#ProxyFromEnvironment).
|
||||
|
||||
|
||||
## Contributions
|
||||
|
||||
This is an open source software licensed under Apache License 2.0.
|
||||
|
||||
73
adaptors/cmd.go
Normal file
73
adaptors/cmd.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package adaptors
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/int128/kubelogin/adaptors/interfaces"
|
||||
"github.com/int128/kubelogin/usecases/interfaces"
|
||||
"github.com/jessevdk/go-flags"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/pkg/errors"
|
||||
"go.uber.org/dig"
|
||||
)
|
||||
|
||||
func NewCmd(i Cmd) adaptors.Cmd {
|
||||
return &i
|
||||
}
|
||||
|
||||
type Cmd struct {
|
||||
dig.In
|
||||
Login usecases.Login
|
||||
Logger adaptors.Logger
|
||||
}
|
||||
|
||||
func (cmd *Cmd) Run(ctx context.Context, args []string, version string) int {
|
||||
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.Logf("Error: %s", err)
|
||||
return 1
|
||||
}
|
||||
if len(args) > 0 {
|
||||
cmd.Logger.Logf("Error: too many arguments")
|
||||
return 1
|
||||
}
|
||||
kubeConfig, err := o.ExpandKubeConfig()
|
||||
if err != nil {
|
||||
cmd.Logger.Logf("Error: invalid option: %s", err)
|
||||
return 1
|
||||
}
|
||||
|
||||
in := usecases.LoginIn{
|
||||
KubeConfig: kubeConfig,
|
||||
ListenPort: o.ListenPort,
|
||||
SkipTLSVerify: o.SkipTLSVerify,
|
||||
SkipOpenBrowser: o.SkipOpenBrowser,
|
||||
}
|
||||
if err := cmd.Login.Do(ctx, in); err != nil {
|
||||
cmd.Logger.Logf("Error: %s", err)
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
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."`
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
87
adaptors/cmd_test.go
Normal file
87
adaptors/cmd_test.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package adaptors
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/int128/kubelogin/usecases/interfaces"
|
||||
"github.com/int128/kubelogin/usecases/mock_usecases"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
)
|
||||
|
||||
func TestCmd_Run(t *testing.T) {
|
||||
const executable = "kubelogin"
|
||||
const version = "HEAD"
|
||||
|
||||
t.Run("Defaults", func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
ctx := context.TODO()
|
||||
|
||||
login := mock_usecases.NewMockLogin(ctrl)
|
||||
login.EXPECT().
|
||||
Do(ctx, usecases.LoginIn{
|
||||
KubeConfig: expand(t, "~/.kube/config"),
|
||||
ListenPort: 8000,
|
||||
})
|
||||
|
||||
cmd := Cmd{
|
||||
Login: login,
|
||||
Logger: t,
|
||||
}
|
||||
exitCode := cmd.Run(ctx, []string{executable}, version)
|
||||
if exitCode != 0 {
|
||||
t.Errorf("exitCode wants 0 but %d", exitCode)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("FullOptions", func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
ctx := context.TODO()
|
||||
|
||||
login := mock_usecases.NewMockLogin(ctrl)
|
||||
login.EXPECT().
|
||||
Do(ctx, usecases.LoginIn{
|
||||
KubeConfig: expand(t, "~/.kube/config"),
|
||||
ListenPort: 10080,
|
||||
SkipTLSVerify: true,
|
||||
SkipOpenBrowser: true,
|
||||
})
|
||||
|
||||
cmd := Cmd{
|
||||
Login: login,
|
||||
Logger: t,
|
||||
}
|
||||
exitCode := cmd.Run(ctx, []string{executable,
|
||||
"--listen-port", "10080",
|
||||
"--insecure-skip-tls-verify",
|
||||
"--skip-open-browser",
|
||||
}, version)
|
||||
if exitCode != 0 {
|
||||
t.Errorf("exitCode wants 0 but %d", exitCode)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("TooManyArgs", func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
cmd := Cmd{
|
||||
Login: mock_usecases.NewMockLogin(ctrl),
|
||||
Logger: t,
|
||||
}
|
||||
exitCode := cmd.Run(context.TODO(), []string{executable, "some"}, version)
|
||||
if exitCode != 1 {
|
||||
t.Errorf("exitCode wants 1 but %d", exitCode)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
74
adaptors/http.go
Normal file
74
adaptors/http.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package adaptors
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/int128/kubelogin/adaptors/interfaces"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func NewHTTP() adaptors.HTTP {
|
||||
return &HTTP{}
|
||||
}
|
||||
|
||||
type HTTP struct{}
|
||||
|
||||
func (*HTTP) NewClientConfig() adaptors.HTTPClientConfig {
|
||||
return &httpClientConfig{
|
||||
certPool: x509.NewCertPool(),
|
||||
}
|
||||
}
|
||||
|
||||
func (*HTTP) NewClient(config adaptors.HTTPClientConfig) (*http.Client, error) {
|
||||
return &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: config.TLSConfig(),
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
type httpClientConfig struct {
|
||||
certPool *x509.CertPool
|
||||
skipTLSVerify bool
|
||||
}
|
||||
|
||||
func (c *httpClientConfig) AddCertificateFromFile(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 {
|
||||
return errors.Errorf("could not append certificate from %s", filename)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *httpClientConfig) AddEncodedCertificate(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 {
|
||||
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
|
||||
}
|
||||
58
adaptors/interfaces/adaptors.go
Normal file
58
adaptors/interfaces/adaptors.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package adaptors
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net/http"
|
||||
|
||||
"github.com/coreos/go-oidc"
|
||||
"k8s.io/client-go/tools/clientcmd/api"
|
||||
)
|
||||
|
||||
//go:generate mockgen -package mock_adaptors -destination ../mock_adaptors/mock_adaptors.go github.com/int128/kubelogin/adaptors/interfaces KubeConfig,HTTP,HTTPClientConfig,OIDC
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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 OIDC interface {
|
||||
Authenticate(ctx context.Context, in OIDCAuthenticateIn) (*OIDCAuthenticateOut, error)
|
||||
}
|
||||
|
||||
type OIDCAuthenticateIn struct {
|
||||
Issuer string
|
||||
ClientID string
|
||||
ClientSecret string
|
||||
ExtraScopes []string // Additional scopes
|
||||
Client *http.Client // HTTP client for oidc and oauth2
|
||||
LocalServerPort int // HTTP server port
|
||||
SkipOpenBrowser bool // skip opening browser if true
|
||||
}
|
||||
|
||||
type OIDCAuthenticateOut struct {
|
||||
VerifiedIDToken *oidc.IDToken
|
||||
IDToken string
|
||||
RefreshToken string
|
||||
}
|
||||
|
||||
type Logger interface {
|
||||
Logf(format string, v ...interface{})
|
||||
}
|
||||
30
adaptors/kubeconfig.go
Normal file
30
adaptors/kubeconfig.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package adaptors
|
||||
|
||||
import (
|
||||
"github.com/int128/kubelogin/adaptors/interfaces"
|
||||
"github.com/pkg/errors"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
"k8s.io/client-go/tools/clientcmd/api"
|
||||
)
|
||||
|
||||
func NewKubeConfig() adaptors.KubeConfig {
|
||||
return &KubeConfig{}
|
||||
}
|
||||
|
||||
type KubeConfig struct{}
|
||||
|
||||
func (*KubeConfig) LoadFromFile(filename string) (*api.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
|
||||
}
|
||||
|
||||
func (*KubeConfig) WriteToFile(config *api.Config, filename string) error {
|
||||
err := clientcmd.WriteToFile(*config, filename)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not write the kubeconfig to %s", filename)
|
||||
}
|
||||
return err
|
||||
}
|
||||
17
adaptors/logger.go
Normal file
17
adaptors/logger.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package adaptors
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/int128/kubelogin/adaptors/interfaces"
|
||||
)
|
||||
|
||||
func NewLogger() adaptors.Logger {
|
||||
return &Logger{}
|
||||
}
|
||||
|
||||
type Logger struct{}
|
||||
|
||||
func (*Logger) Logf(format string, v ...interface{}) {
|
||||
log.Printf(format, v...)
|
||||
}
|
||||
216
adaptors/mock_adaptors/mock_adaptors.go
Normal file
216
adaptors/mock_adaptors/mock_adaptors.go
Normal file
@@ -0,0 +1,216 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/int128/kubelogin/adaptors/interfaces (interfaces: KubeConfig,HTTP,HTTPClientConfig,OIDC)
|
||||
|
||||
// Package mock_adaptors is a generated GoMock package.
|
||||
package mock_adaptors
|
||||
|
||||
import (
|
||||
context "context"
|
||||
tls "crypto/tls"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
interfaces "github.com/int128/kubelogin/adaptors/interfaces"
|
||||
api "k8s.io/client-go/tools/clientcmd/api"
|
||||
http "net/http"
|
||||
reflect "reflect"
|
||||
)
|
||||
|
||||
// MockKubeConfig is a mock of KubeConfig interface
|
||||
type MockKubeConfig struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockKubeConfigMockRecorder
|
||||
}
|
||||
|
||||
// MockKubeConfigMockRecorder is the mock recorder for MockKubeConfig
|
||||
type MockKubeConfigMockRecorder struct {
|
||||
mock *MockKubeConfig
|
||||
}
|
||||
|
||||
// NewMockKubeConfig creates a new mock instance
|
||||
func NewMockKubeConfig(ctrl *gomock.Controller) *MockKubeConfig {
|
||||
mock := &MockKubeConfig{ctrl: ctrl}
|
||||
mock.recorder = &MockKubeConfigMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockKubeConfig) EXPECT() *MockKubeConfigMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// LoadFromFile mocks base method
|
||||
func (m *MockKubeConfig) LoadFromFile(arg0 string) (*api.Config, error) {
|
||||
ret := m.ctrl.Call(m, "LoadFromFile", arg0)
|
||||
ret0, _ := ret[0].(*api.Config)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// LoadFromFile indicates an expected call of LoadFromFile
|
||||
func (mr *MockKubeConfigMockRecorder) LoadFromFile(arg0 interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadFromFile", reflect.TypeOf((*MockKubeConfig)(nil).LoadFromFile), arg0)
|
||||
}
|
||||
|
||||
// WriteToFile mocks base method
|
||||
func (m *MockKubeConfig) WriteToFile(arg0 *api.Config, arg1 string) error {
|
||||
ret := m.ctrl.Call(m, "WriteToFile", arg0, arg1)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// WriteToFile indicates an expected call of WriteToFile
|
||||
func (mr *MockKubeConfigMockRecorder) WriteToFile(arg0, arg1 interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WriteToFile", reflect.TypeOf((*MockKubeConfig)(nil).WriteToFile), arg0, arg1)
|
||||
}
|
||||
|
||||
// MockHTTP is a mock of HTTP interface
|
||||
type MockHTTP struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockHTTPMockRecorder
|
||||
}
|
||||
|
||||
// MockHTTPMockRecorder is the mock recorder for MockHTTP
|
||||
type MockHTTPMockRecorder struct {
|
||||
mock *MockHTTP
|
||||
}
|
||||
|
||||
// NewMockHTTP creates a new mock instance
|
||||
func NewMockHTTP(ctrl *gomock.Controller) *MockHTTP {
|
||||
mock := &MockHTTP{ctrl: ctrl}
|
||||
mock.recorder = &MockHTTPMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockHTTP) EXPECT() *MockHTTPMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// NewClient mocks base method
|
||||
func (m *MockHTTP) NewClient(arg0 interfaces.HTTPClientConfig) (*http.Client, error) {
|
||||
ret := m.ctrl.Call(m, "NewClient", arg0)
|
||||
ret0, _ := ret[0].(*http.Client)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// NewClient indicates an expected call of NewClient
|
||||
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
|
||||
recorder *MockOIDCMockRecorder
|
||||
}
|
||||
|
||||
// MockOIDCMockRecorder is the mock recorder for MockOIDC
|
||||
type MockOIDCMockRecorder struct {
|
||||
mock *MockOIDC
|
||||
}
|
||||
|
||||
// NewMockOIDC creates a new mock instance
|
||||
func NewMockOIDC(ctrl *gomock.Controller) *MockOIDC {
|
||||
mock := &MockOIDC{ctrl: ctrl}
|
||||
mock.recorder = &MockOIDCMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockOIDC) EXPECT() *MockOIDCMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Authenticate mocks base method
|
||||
func (m *MockOIDC) Authenticate(arg0 context.Context, arg1 interfaces.OIDCAuthenticateIn) (*interfaces.OIDCAuthenticateOut, error) {
|
||||
ret := m.ctrl.Call(m, "Authenticate", arg0, arg1)
|
||||
ret0, _ := ret[0].(*interfaces.OIDCAuthenticateOut)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Authenticate indicates an expected call of Authenticate
|
||||
func (mr *MockOIDCMockRecorder) Authenticate(arg0, arg1 interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Authenticate", reflect.TypeOf((*MockOIDC)(nil).Authenticate), arg0, arg1)
|
||||
}
|
||||
56
adaptors/oidc.go
Normal file
56
adaptors/oidc.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package adaptors
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/coreos/go-oidc"
|
||||
"github.com/int128/kubelogin/adaptors/interfaces"
|
||||
"github.com/int128/oauth2cli"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
func NewOIDC() adaptors.OIDC {
|
||||
return &OIDC{}
|
||||
}
|
||||
|
||||
type OIDC struct{}
|
||||
|
||||
func (*OIDC) Authenticate(ctx context.Context, in adaptors.OIDCAuthenticateIn) (*adaptors.OIDCAuthenticateOut, error) {
|
||||
if in.Client != nil {
|
||||
ctx = context.WithValue(ctx, oauth2.HTTPClient, in.Client)
|
||||
}
|
||||
provider, err := oidc.NewProvider(ctx, in.Issuer)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not discovery the OIDC issuer")
|
||||
}
|
||||
flow := oauth2cli.AuthCodeFlow{
|
||||
Config: oauth2.Config{
|
||||
Endpoint: provider.Endpoint(),
|
||||
ClientID: in.ClientID,
|
||||
ClientSecret: in.ClientSecret,
|
||||
Scopes: append(in.ExtraScopes, oidc.ScopeOpenID),
|
||||
},
|
||||
LocalServerPort: in.LocalServerPort,
|
||||
SkipOpenBrowser: in.SkipOpenBrowser,
|
||||
AuthCodeOptions: []oauth2.AuthCodeOption{oauth2.AccessTypeOffline},
|
||||
}
|
||||
token, err := flow.GetToken(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not get a token")
|
||||
}
|
||||
idToken, ok := token.Extra("id_token").(string)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("id_token is missing in the token response: %s", token)
|
||||
}
|
||||
verifier := provider.Verifier(&oidc.Config{ClientID: in.ClientID})
|
||||
verifiedIDToken, err := verifier.Verify(ctx, idToken)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not verify the id_token")
|
||||
}
|
||||
return &adaptors.OIDCAuthenticateOut{
|
||||
VerifiedIDToken: verifiedIDToken,
|
||||
IDToken: idToken,
|
||||
RefreshToken: token.RefreshToken,
|
||||
}, nil
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"time"
|
||||
|
||||
jwt "github.com/dgrijalva/jwt-go"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type handler struct {
|
||||
@@ -89,7 +90,7 @@ func (h *handler) serveHTTP(w http.ResponseWriter, r *http.Request) error {
|
||||
// http://openid.net/specs/openid-connect-core-1_0.html#AuthResponse
|
||||
q := r.URL.Query()
|
||||
if h.Scope != q.Get("scope") {
|
||||
return fmt.Errorf("scope wants %s but %s", h.Scope, q.Get("scope"))
|
||||
return errors.Errorf("scope wants %s but %s", h.Scope, q.Get("scope"))
|
||||
}
|
||||
to := fmt.Sprintf("%s?state=%s&code=%s", q.Get("redirect_uri"), q.Get("state"), h.authCode)
|
||||
http.Redirect(w, r, to, 302)
|
||||
@@ -100,7 +101,7 @@ func (h *handler) serveHTTP(w http.ResponseWriter, r *http.Request) error {
|
||||
return err
|
||||
}
|
||||
if h.authCode != r.Form.Get("code") {
|
||||
return fmt.Errorf("code wants %s but %s", h.authCode, r.Form.Get("code"))
|
||||
return errors.Errorf("code wants %s but %s", h.authCode, r.Form.Get("code"))
|
||||
}
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
if err := h.token.Execute(w, h); err != nil {
|
||||
@@ -1,20 +1,21 @@
|
||||
package cli_test
|
||||
package adaptors_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/int128/kubelogin/cli"
|
||||
"github.com/int128/kubelogin/cli_test/authserver"
|
||||
"github.com/int128/kubelogin/cli_test/kubeconfig"
|
||||
"github.com/int128/kubelogin/adaptors/interfaces"
|
||||
"github.com/int128/kubelogin/adaptors_test/authserver"
|
||||
"github.com/int128/kubelogin/adaptors_test/kubeconfig"
|
||||
"github.com/int128/kubelogin/di"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
@@ -25,16 +26,17 @@ import (
|
||||
// 3. Open a request for port 8000.
|
||||
// 4. Wait for the CLI.
|
||||
// 5. Shutdown the auth server.
|
||||
func TestE2E(t *testing.T) {
|
||||
//
|
||||
func TestCmd_Run(t *testing.T) {
|
||||
data := map[string]struct {
|
||||
kubeconfigValues kubeconfig.Values
|
||||
cli cli.CLI
|
||||
args []string
|
||||
serverConfig authserver.Config
|
||||
clientTLS *tls.Config
|
||||
}{
|
||||
"NoTLS": {
|
||||
kubeconfig.Values{Issuer: "http://localhost:9000"},
|
||||
cli.CLI{},
|
||||
[]string{"kubelogin"},
|
||||
authserver.Config{Issuer: "http://localhost:9000"},
|
||||
&tls.Config{},
|
||||
},
|
||||
@@ -43,7 +45,7 @@ func TestE2E(t *testing.T) {
|
||||
Issuer: "http://localhost:9000",
|
||||
ExtraScopes: "profile groups",
|
||||
},
|
||||
cli.CLI{},
|
||||
[]string{"kubelogin"},
|
||||
authserver.Config{
|
||||
Issuer: "http://localhost:9000",
|
||||
Scope: "profile groups openid",
|
||||
@@ -52,7 +54,7 @@ func TestE2E(t *testing.T) {
|
||||
},
|
||||
"SkipTLSVerify": {
|
||||
kubeconfig.Values{Issuer: "https://localhost:9000"},
|
||||
cli.CLI{SkipTLSVerify: true},
|
||||
[]string{"kubelogin", "--insecure-skip-tls-verify"},
|
||||
authserver.Config{
|
||||
Issuer: "https://localhost:9000",
|
||||
Cert: authserver.ServerCert,
|
||||
@@ -65,7 +67,7 @@ func TestE2E(t *testing.T) {
|
||||
Issuer: "https://localhost:9000",
|
||||
IDPCertificateAuthority: authserver.CACert,
|
||||
},
|
||||
cli.CLI{},
|
||||
[]string{"kubelogin"},
|
||||
authserver.Config{
|
||||
Issuer: "https://localhost:9000",
|
||||
Cert: authserver.ServerCert,
|
||||
@@ -75,10 +77,10 @@ func TestE2E(t *testing.T) {
|
||||
},
|
||||
"CACertData": {
|
||||
kubeconfig.Values{
|
||||
Issuer: "https://localhost:9000",
|
||||
Issuer: "https://localhost:9000",
|
||||
IDPCertificateAuthorityData: base64.StdEncoding.EncodeToString(read(t, authserver.CACert)),
|
||||
},
|
||||
cli.CLI{},
|
||||
[]string{"kubelogin"},
|
||||
authserver.Config{
|
||||
Issuer: "https://localhost:9000",
|
||||
Cert: authserver.ServerCert,
|
||||
@@ -88,19 +90,19 @@ func TestE2E(t *testing.T) {
|
||||
},
|
||||
"InvalidCACertShouldBeSkipped": {
|
||||
kubeconfig.Values{
|
||||
Issuer: "http://localhost:9000",
|
||||
IDPCertificateAuthority: "e2e_test.go",
|
||||
Issuer: "http://localhost:9000",
|
||||
IDPCertificateAuthority: "cmd_test.go",
|
||||
},
|
||||
cli.CLI{},
|
||||
[]string{"kubelogin"},
|
||||
authserver.Config{Issuer: "http://localhost:9000"},
|
||||
&tls.Config{},
|
||||
},
|
||||
"InvalidCACertDataShouldBeSkipped": {
|
||||
kubeconfig.Values{
|
||||
Issuer: "http://localhost:9000",
|
||||
Issuer: "http://localhost:9000",
|
||||
IDPCertificateAuthorityData: base64.StdEncoding.EncodeToString([]byte("foo")),
|
||||
},
|
||||
cli.CLI{},
|
||||
[]string{"kubelogin"},
|
||||
authserver.Config{Issuer: "http://localhost:9000"},
|
||||
&tls.Config{},
|
||||
},
|
||||
@@ -114,13 +116,16 @@ func TestE2E(t *testing.T) {
|
||||
defer server.Shutdown(ctx)
|
||||
kcfg := kubeconfig.Create(t, &c.kubeconfigValues)
|
||||
defer os.Remove(kcfg)
|
||||
c.cli.KubeConfig = kcfg
|
||||
c.cli.SkipOpenBrowser = true
|
||||
c.cli.ListenPort = 8000
|
||||
|
||||
args := append(c.args, "--kubeconfig", kcfg, "--skip-open-browser")
|
||||
var eg errgroup.Group
|
||||
eg.Go(func() error {
|
||||
return c.cli.Run(ctx)
|
||||
return di.Invoke(func(cmd adaptors.Cmd) {
|
||||
exitCode := cmd.Run(ctx, args, "HEAD")
|
||||
if exitCode != 0 {
|
||||
t.Errorf("exit status wants 0 but %d", exitCode)
|
||||
}
|
||||
})
|
||||
})
|
||||
if err := openBrowserRequest(c.clientTLS); err != nil {
|
||||
cancel()
|
||||
@@ -139,10 +144,10 @@ func openBrowserRequest(tlsConfig *tls.Config) error {
|
||||
client := http.Client{Transport: &http.Transport{TLSClientConfig: tlsConfig}}
|
||||
res, err := client.Get("http://localhost:8000/")
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not send a request: %s", err)
|
||||
return errors.Wrapf(err, "could not send a request")
|
||||
}
|
||||
if res.StatusCode != 200 {
|
||||
return fmt.Errorf("StatusCode wants 200 but %d", res.StatusCode)
|
||||
return errors.Errorf("StatusCode wants 200 but %d", res.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
87
auth/oidc.go
87
auth/oidc.go
@@ -1,87 +0,0 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
oidc "github.com/coreos/go-oidc"
|
||||
"github.com/int128/oauth2cli"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
// TokenSet is a set of tokens and claims.
|
||||
type TokenSet struct {
|
||||
IDToken string
|
||||
RefreshToken string
|
||||
}
|
||||
|
||||
// Config represents OIDC configuration.
|
||||
type Config struct {
|
||||
Issuer string
|
||||
ClientID string
|
||||
ClientSecret string
|
||||
ExtraScopes []string // Additional scopes
|
||||
Client *http.Client // HTTP client for oidc and oauth2
|
||||
LocalServerPort int // HTTP server port
|
||||
SkipOpenBrowser bool // skip opening browser if true
|
||||
}
|
||||
|
||||
// GetTokenSet retrives a token from the OIDC provider and returns a TokenSet.
|
||||
func (c *Config) GetTokenSet(ctx context.Context) (*TokenSet, error) {
|
||||
if c.Client != nil {
|
||||
// https://github.com/int128/kubelogin/issues/31
|
||||
val, ok := os.LookupEnv("HTTPS_PROXY")
|
||||
if ok {
|
||||
proxyURL, err := url.Parse(val)
|
||||
if err != nil {
|
||||
log.Printf("HTTPS_PROXY %s cannot be parsed into a URL\n", val)
|
||||
} else {
|
||||
transport := &http.Transport{
|
||||
Proxy: http.ProxyURL(proxyURL),
|
||||
}
|
||||
c.Client = &http.Client{
|
||||
Transport: transport,
|
||||
}
|
||||
}
|
||||
}
|
||||
//
|
||||
ctx = context.WithValue(ctx, oauth2.HTTPClient, c.Client)
|
||||
}
|
||||
provider, err := oidc.NewProvider(ctx, c.Issuer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Could not discovery the OIDC issuer: %s", err)
|
||||
}
|
||||
flow := oauth2cli.AuthCodeFlow{
|
||||
Config: oauth2.Config{
|
||||
Endpoint: provider.Endpoint(),
|
||||
ClientID: c.ClientID,
|
||||
ClientSecret: c.ClientSecret,
|
||||
Scopes: append(c.ExtraScopes, oidc.ScopeOpenID),
|
||||
},
|
||||
LocalServerPort: c.LocalServerPort,
|
||||
SkipOpenBrowser: c.SkipOpenBrowser,
|
||||
AuthCodeOptions: []oauth2.AuthCodeOption{oauth2.AccessTypeOffline},
|
||||
}
|
||||
token, err := flow.GetToken(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Could not get a token: %s", err)
|
||||
}
|
||||
idToken, ok := token.Extra("id_token").(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("id_token is missing in the token response: %s", token)
|
||||
}
|
||||
verifier := provider.Verifier(&oidc.Config{ClientID: c.ClientID})
|
||||
verifiedIDToken, err := verifier.Verify(ctx, idToken)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Could not verify the id_token: %s", err)
|
||||
}
|
||||
log.Printf("Got token for subject=%s", verifiedIDToken.Subject)
|
||||
return &TokenSet{
|
||||
IDToken: idToken,
|
||||
RefreshToken: token.RefreshToken,
|
||||
}, nil
|
||||
}
|
||||
92
cli/cli.go
92
cli/cli.go
@@ -1,92 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/int128/kubelogin/auth"
|
||||
"github.com/int128/kubelogin/kubeconfig"
|
||||
flags "github.com/jessevdk/go-flags"
|
||||
homedir "github.com/mitchellh/go-homedir"
|
||||
)
|
||||
|
||||
// Parse parses command line arguments and returns a CLI instance.
|
||||
func Parse(osArgs []string, version string) (*CLI, error) {
|
||||
var cli CLI
|
||||
parser := flags.NewParser(&cli, flags.HelpFlag)
|
||||
parser.LongDescription = fmt.Sprintf(`Version %s
|
||||
This updates the kubeconfig for Kubernetes OpenID Connect (OIDC) authentication.`,
|
||||
version)
|
||||
args, err := parser.ParseArgs(osArgs[1:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(args) > 0 {
|
||||
return nil, fmt.Errorf("Too many argument")
|
||||
}
|
||||
return &cli, nil
|
||||
}
|
||||
|
||||
// CLI represents an interface of this command.
|
||||
type CLI 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."`
|
||||
}
|
||||
|
||||
// ExpandKubeConfig returns an expanded KubeConfig path.
|
||||
func (c *CLI) ExpandKubeConfig() (string, error) {
|
||||
d, err := homedir.Expand(c.KubeConfig)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Could not expand %s: %s", c.KubeConfig, err)
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// Run performs this command.
|
||||
func (c *CLI) Run(ctx context.Context) error {
|
||||
log.Printf("Reading %s", c.KubeConfig)
|
||||
path, err := c.ExpandKubeConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cfg, err := kubeconfig.Read(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not read kubeconfig: %s", err)
|
||||
}
|
||||
log.Printf("Using current-context: %s", cfg.CurrentContext)
|
||||
authProvider, err := kubeconfig.FindOIDCAuthProvider(cfg)
|
||||
if err != nil {
|
||||
return fmt.Errorf(`Could not find OIDC configuration in kubeconfig: %s
|
||||
Did you setup kubectl for OIDC authentication?
|
||||
kubectl config set-credentials %s \
|
||||
--auth-provider oidc \
|
||||
--auth-provider-arg idp-issuer-url=https://issuer.example.com \
|
||||
--auth-provider-arg client-id=YOUR_CLIENT_ID \
|
||||
--auth-provider-arg client-secret=YOUR_CLIENT_SECRET`,
|
||||
err, cfg.CurrentContext)
|
||||
}
|
||||
tlsConfig := c.tlsConfig(authProvider)
|
||||
authConfig := &auth.Config{
|
||||
Issuer: authProvider.IDPIssuerURL(),
|
||||
ClientID: authProvider.ClientID(),
|
||||
ClientSecret: authProvider.ClientSecret(),
|
||||
ExtraScopes: authProvider.ExtraScopes(),
|
||||
Client: &http.Client{Transport: &http.Transport{TLSClientConfig: tlsConfig}},
|
||||
LocalServerPort: c.ListenPort,
|
||||
SkipOpenBrowser: c.SkipOpenBrowser,
|
||||
}
|
||||
token, err := authConfig.GetTokenSet(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not get token from OIDC provider: %s", err)
|
||||
}
|
||||
|
||||
authProvider.SetIDToken(token.IDToken)
|
||||
authProvider.SetRefreshToken(token.RefreshToken)
|
||||
kubeconfig.Write(cfg, path)
|
||||
log.Printf("Updated %s", c.KubeConfig)
|
||||
return nil
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
c, err := Parse([]string{"kubelogin"}, "version")
|
||||
if err != nil {
|
||||
t.Errorf("Parse returned error: %s", err)
|
||||
}
|
||||
if c == nil {
|
||||
t.Errorf("Parse should return CLI but nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParse_TooManyArgs(t *testing.T) {
|
||||
c, err := Parse([]string{"kubelogin", "some"}, "version")
|
||||
if err == nil {
|
||||
t.Errorf("Parse should return error but nil")
|
||||
}
|
||||
if c != nil {
|
||||
t.Errorf("Parse should return nil but %+v", c)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParse_Help(t *testing.T) {
|
||||
c, err := Parse([]string{"kubelogin", "--help"}, "version")
|
||||
if err == nil {
|
||||
t.Errorf("Parse should return error but nil")
|
||||
}
|
||||
if c != nil {
|
||||
t.Errorf("Parse should return nil but %+v", c)
|
||||
}
|
||||
}
|
||||
57
cli/tls.go
57
cli/tls.go
@@ -1,57 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
|
||||
"github.com/int128/kubelogin/kubeconfig"
|
||||
)
|
||||
|
||||
func (c *CLI) tlsConfig(authProvider *kubeconfig.OIDCAuthProvider) *tls.Config {
|
||||
p := x509.NewCertPool()
|
||||
if ca := authProvider.IDPCertificateAuthority(); ca != "" {
|
||||
if err := appendCertFile(p, ca); err != nil {
|
||||
log.Printf("Skip CA certificate of idp-certificate-authority: %s", err)
|
||||
} else {
|
||||
log.Printf("Using CA certificate: %s", ca)
|
||||
}
|
||||
}
|
||||
if ca := authProvider.IDPCertificateAuthorityData(); ca != "" {
|
||||
if err := appendCertData(p, ca); err != nil {
|
||||
log.Printf("Skip CA certificate of idp-certificate-authority-data: %s", err)
|
||||
} else {
|
||||
log.Printf("Using CA certificate of idp-certificate-authority-data")
|
||||
}
|
||||
}
|
||||
cfg := &tls.Config{InsecureSkipVerify: c.SkipTLSVerify}
|
||||
if len(p.Subjects()) > 0 {
|
||||
cfg.RootCAs = p
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
func appendCertFile(p *x509.CertPool, name string) error {
|
||||
b, err := ioutil.ReadFile(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not read %s: %s", name, err)
|
||||
}
|
||||
if p.AppendCertsFromPEM(b) != true {
|
||||
return fmt.Errorf("Could not append certificate from %s", name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func appendCertData(p *x509.CertPool, data string) error {
|
||||
b, err := base64.StdEncoding.DecodeString(data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not decode base64: %s", err)
|
||||
}
|
||||
if p.AppendCertsFromPEM(b) != true {
|
||||
return fmt.Errorf("Could not append certificate")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
34
di/di.go
Normal file
34
di/di.go
Normal file
@@ -0,0 +1,34 @@
|
||||
// Package di provides dependency injection.
|
||||
package di
|
||||
|
||||
import (
|
||||
"github.com/int128/kubelogin/adaptors"
|
||||
adaptorsInterfaces "github.com/int128/kubelogin/adaptors/interfaces"
|
||||
"github.com/int128/kubelogin/usecases"
|
||||
"github.com/pkg/errors"
|
||||
"go.uber.org/dig"
|
||||
)
|
||||
|
||||
var constructors = []interface{}{
|
||||
usecases.NewLogin,
|
||||
|
||||
adaptors.NewCmd,
|
||||
adaptors.NewKubeConfig,
|
||||
adaptors.NewOIDC,
|
||||
adaptors.NewHTTP,
|
||||
adaptors.NewLogger,
|
||||
}
|
||||
|
||||
// Invoke runs the function with an adaptors.Cmd instance.
|
||||
func Invoke(f func(cmd adaptorsInterfaces.Cmd)) error {
|
||||
c := dig.New()
|
||||
for _, constructor := range constructors {
|
||||
if err := c.Provide(constructor); err != nil {
|
||||
return errors.Wrapf(err, "could not provide the constructor")
|
||||
}
|
||||
}
|
||||
if err := c.Invoke(f); err != nil {
|
||||
return errors.Wrapf(err, "could not invoke")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
18
di/di_test.go
Normal file
18
di/di_test.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package di_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
adaptors "github.com/int128/kubelogin/adaptors/interfaces"
|
||||
"github.com/int128/kubelogin/di"
|
||||
)
|
||||
|
||||
func TestInvoke(t *testing.T) {
|
||||
if err := di.Invoke(func(cmd adaptors.Cmd) {
|
||||
if cmd == nil {
|
||||
t.Errorf("cmd wants non-nil but nil")
|
||||
}
|
||||
}); err != nil {
|
||||
t.Fatalf("Invoke returned error: %+v", err)
|
||||
}
|
||||
}
|
||||
3
go.mod
3
go.mod
@@ -5,6 +5,7 @@ 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/google/gofuzz v0.0.0-20170612174753-24818f796faf // indirect
|
||||
github.com/imdario/mergo v0.3.7 // indirect
|
||||
github.com/int128/oauth2cli v1.1.0
|
||||
@@ -13,9 +14,11 @@ require (
|
||||
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/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
|
||||
|
||||
4
go.sum
4
go.sum
@@ -8,6 +8,8 @@ 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/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=
|
||||
@@ -41,6 +43,8 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
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/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=
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package kubeconfig
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"k8s.io/client-go/tools/clientcmd/api"
|
||||
)
|
||||
|
||||
@@ -13,17 +13,17 @@ import (
|
||||
func FindOIDCAuthProvider(config *api.Config) (*OIDCAuthProvider, error) {
|
||||
context := config.Contexts[config.CurrentContext]
|
||||
if context == nil {
|
||||
return nil, fmt.Errorf("context %s does not exist", config.CurrentContext)
|
||||
return nil, errors.Errorf("context %s does not exist", config.CurrentContext)
|
||||
}
|
||||
authInfo := config.AuthInfos[context.AuthInfo]
|
||||
if authInfo == nil {
|
||||
return nil, fmt.Errorf("auth-info %s does not exist", context.AuthInfo)
|
||||
return nil, errors.Errorf("auth-info %s does not exist", context.AuthInfo)
|
||||
}
|
||||
if authInfo.AuthProvider == nil {
|
||||
return nil, fmt.Errorf("auth-provider is not set")
|
||||
return nil, errors.Errorf("auth-provider is not set")
|
||||
}
|
||||
if authInfo.AuthProvider.Name != "oidc" {
|
||||
return nil, fmt.Errorf("auth-provider name is %s but must be oidc", authInfo.AuthProvider.Name)
|
||||
return nil, errors.Errorf("auth-provider name is %s but must be oidc", authInfo.AuthProvider.Name)
|
||||
}
|
||||
return (*OIDCAuthProvider)(authInfo.AuthProvider), nil
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
package kubeconfig
|
||||
|
||||
import (
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
"k8s.io/client-go/tools/clientcmd/api"
|
||||
)
|
||||
|
||||
// Read parses the file and returns the Config.
|
||||
func Read(path string) (*api.Config, error) {
|
||||
return clientcmd.LoadFromFile(path)
|
||||
}
|
||||
|
||||
// Write writes the config to the file.
|
||||
func Write(config *api.Config, path string) error {
|
||||
return clientcmd.WriteToFile(*config, path)
|
||||
}
|
||||
12
main.go
12
main.go
@@ -5,18 +5,16 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/int128/kubelogin/cli"
|
||||
"github.com/int128/kubelogin/adaptors/interfaces"
|
||||
"github.com/int128/kubelogin/di"
|
||||
)
|
||||
|
||||
var version = "HEAD"
|
||||
|
||||
func main() {
|
||||
c, err := cli.Parse(os.Args, version)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
ctx := context.Background()
|
||||
if err := c.Run(ctx); err != nil {
|
||||
if err := di.Invoke(func(cmd adaptors.Cmd) {
|
||||
os.Exit(cmd.Run(context.Background(), os.Args, version))
|
||||
}); err != nil {
|
||||
log.Fatalf("Error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
16
usecases/interfaces/usecases.go
Normal file
16
usecases/interfaces/usecases.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package usecases
|
||||
|
||||
import "context"
|
||||
|
||||
//go:generate mockgen -package mock_usecases -destination ../mock_usecases/mock_usecases.go github.com/int128/kubelogin/usecases/interfaces Login
|
||||
|
||||
type Login interface {
|
||||
Do(ctx context.Context, in LoginIn) error
|
||||
}
|
||||
|
||||
type LoginIn struct {
|
||||
KubeConfig string
|
||||
SkipTLSVerify bool
|
||||
SkipOpenBrowser bool
|
||||
ListenPort int
|
||||
}
|
||||
87
usecases/login.go
Normal file
87
usecases/login.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package usecases
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/int128/kubelogin/adaptors/interfaces"
|
||||
"github.com/int128/kubelogin/kubeconfig"
|
||||
"github.com/int128/kubelogin/usecases/interfaces"
|
||||
"github.com/pkg/errors"
|
||||
"go.uber.org/dig"
|
||||
)
|
||||
|
||||
const oidcConfigErrorMessage = `No OIDC configuration found. Did you setup kubectl for OIDC authentication?
|
||||
kubectl config set-credentials %[1]s \
|
||||
--auth-provider oidc \
|
||||
--auth-provider-arg idp-issuer-url=https://issuer.example.com \
|
||||
--auth-provider-arg client-id=YOUR_CLIENT_ID \
|
||||
--auth-provider-arg client-secret=YOUR_CLIENT_SECRET`
|
||||
|
||||
func NewLogin(i Login) usecases.Login {
|
||||
return &i
|
||||
}
|
||||
|
||||
type Login struct {
|
||||
dig.In
|
||||
KubeConfig adaptors.KubeConfig
|
||||
HTTP adaptors.HTTP
|
||||
OIDC adaptors.OIDC
|
||||
Logger adaptors.Logger
|
||||
}
|
||||
|
||||
func (u *Login) Do(ctx context.Context, in usecases.LoginIn) error {
|
||||
cfg, err := u.KubeConfig.LoadFromFile(in.KubeConfig)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not read the kubeconfig")
|
||||
}
|
||||
|
||||
u.Logger.Logf("Using current-context: %s", cfg.CurrentContext)
|
||||
authProvider, err := kubeconfig.FindOIDCAuthProvider(cfg)
|
||||
if err != nil {
|
||||
u.Logger.Logf(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.Logf("Using the certificate %s", filename)
|
||||
if err := clientConfig.AddCertificateFromFile(filename); err != nil {
|
||||
u.Logger.Logf("Skip the certificate %s: %s", filename, err)
|
||||
}
|
||||
}
|
||||
if authProvider.IDPCertificateAuthorityData() != "" {
|
||||
encoded := authProvider.IDPCertificateAuthorityData()
|
||||
u.Logger.Logf("Using certificate of idp-certificate-authority-data")
|
||||
if err := clientConfig.AddEncodedCertificate(encoded); err != nil {
|
||||
u.Logger.Logf("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")
|
||||
}
|
||||
|
||||
out, err := u.OIDC.Authenticate(ctx, adaptors.OIDCAuthenticateIn{
|
||||
Issuer: authProvider.IDPIssuerURL(),
|
||||
ClientID: authProvider.ClientID(),
|
||||
ClientSecret: authProvider.ClientSecret(),
|
||||
ExtraScopes: authProvider.ExtraScopes(),
|
||||
Client: hc,
|
||||
LocalServerPort: in.ListenPort,
|
||||
SkipOpenBrowser: in.SkipOpenBrowser,
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not get token from OIDC provider")
|
||||
}
|
||||
|
||||
u.Logger.Logf("Got a token for subject=%s", out.VerifiedIDToken.Subject)
|
||||
authProvider.SetIDToken(out.IDToken)
|
||||
authProvider.SetRefreshToken(out.RefreshToken)
|
||||
if err := u.KubeConfig.WriteToFile(cfg, in.KubeConfig); err != nil {
|
||||
return errors.Wrapf(err, "could not update the kubeconfig")
|
||||
}
|
||||
u.Logger.Logf("Updated %s", in.KubeConfig)
|
||||
return nil
|
||||
}
|
||||
426
usecases/login_test.go
Normal file
426
usecases/login_test.go
Normal file
@@ -0,0 +1,426 @@
|
||||
package usecases
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/coreos/go-oidc"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/int128/kubelogin/adaptors/interfaces"
|
||||
"github.com/int128/kubelogin/adaptors/mock_adaptors"
|
||||
"github.com/int128/kubelogin/usecases/interfaces"
|
||||
"github.com/pkg/errors"
|
||||
"k8s.io/client-go/tools/clientcmd/api"
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
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,
|
||||
}).
|
||||
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: t,
|
||||
}
|
||||
if err := u.Do(ctx, usecases.LoginIn{
|
||||
KubeConfig: "/path/to/kubeconfig",
|
||||
ListenPort: 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()
|
||||
|
||||
httpClientConfig := mock_adaptors.NewMockHTTPClientConfig(ctrl)
|
||||
httpClientConfig.EXPECT().
|
||||
SetSkipTLSVerify(true)
|
||||
|
||||
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,
|
||||
}).
|
||||
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: t,
|
||||
}
|
||||
if err := u.Do(ctx, usecases.LoginIn{
|
||||
KubeConfig: "/path/to/kubeconfig",
|
||||
ListenPort: 10000,
|
||||
SkipTLSVerify: true,
|
||||
}); err != nil {
|
||||
t.Errorf("Do returned error: %+v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("SkipOpenBrowser", 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)
|
||||
|
||||
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,
|
||||
}).
|
||||
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: t,
|
||||
}
|
||||
if err := u.Do(ctx, usecases.LoginIn{
|
||||
KubeConfig: "/path/to/kubeconfig",
|
||||
ListenPort: 10000,
|
||||
SkipOpenBrowser: true,
|
||||
}); 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)
|
||||
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
ctx := context.TODO()
|
||||
|
||||
httpClientConfig := mock_adaptors.NewMockHTTPClientConfig(ctrl)
|
||||
httpClientConfig.EXPECT().
|
||||
SetSkipTLSVerify(false)
|
||||
|
||||
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,
|
||||
}).
|
||||
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: t,
|
||||
}
|
||||
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,
|
||||
}).
|
||||
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: t,
|
||||
}
|
||||
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,
|
||||
}).
|
||||
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: t,
|
||||
}
|
||||
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,
|
||||
}).
|
||||
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: t,
|
||||
}
|
||||
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,
|
||||
}).
|
||||
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: t,
|
||||
}
|
||||
if err := u.Do(ctx, usecases.LoginIn{
|
||||
KubeConfig: "/path/to/kubeconfig",
|
||||
ListenPort: 10000,
|
||||
}); err != nil {
|
||||
t.Errorf("Do returned error: %+v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
47
usecases/mock_usecases/mock_usecases.go
Normal file
47
usecases/mock_usecases/mock_usecases.go
Normal file
@@ -0,0 +1,47 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/int128/kubelogin/usecases/interfaces (interfaces: Login)
|
||||
|
||||
// Package mock_usecases is a generated GoMock package.
|
||||
package mock_usecases
|
||||
|
||||
import (
|
||||
context "context"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
interfaces "github.com/int128/kubelogin/usecases/interfaces"
|
||||
reflect "reflect"
|
||||
)
|
||||
|
||||
// MockLogin is a mock of Login interface
|
||||
type MockLogin struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockLoginMockRecorder
|
||||
}
|
||||
|
||||
// MockLoginMockRecorder is the mock recorder for MockLogin
|
||||
type MockLoginMockRecorder struct {
|
||||
mock *MockLogin
|
||||
}
|
||||
|
||||
// NewMockLogin creates a new mock instance
|
||||
func NewMockLogin(ctrl *gomock.Controller) *MockLogin {
|
||||
mock := &MockLogin{ctrl: ctrl}
|
||||
mock.recorder = &MockLoginMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockLogin) EXPECT() *MockLoginMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Do mocks base method
|
||||
func (m *MockLogin) Do(arg0 context.Context, arg1 interfaces.LoginIn) error {
|
||||
ret := m.ctrl.Call(m, "Do", arg0, arg1)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Do indicates an expected call of Do
|
||||
func (mr *MockLoginMockRecorder) Do(arg0, arg1 interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Do", reflect.TypeOf((*MockLogin)(nil).Do), arg0, arg1)
|
||||
}
|
||||
Reference in New Issue
Block a user