Compare commits

...

6 Commits

Author SHA1 Message Date
Hidetake Iwata
6f96ccae62 Make commit for PR for krew-index on release 2019-07-26 19:35:45 +09:00
Hidetake Iwata
dc88948d88 Run as a client-go credential plugin (#118) 2019-07-25 17:01:34 +09:00
Hidetake Iwata
31609a3ed3 Move to k8s.io/client-go@v12.0.0 (#119)
See https://github.com/kubernetes/client-go/blob/master/INSTALL.md
2019-07-25 16:43:48 +09:00
Hidetake Iwata
9685507d7d Refactor: use OIDCConfig in input of authentication use-case (#117) 2019-07-25 13:49:09 +09:00
dependabot-preview[bot]
1c66099496 Bump github.com/go-test/deep from 1.0.1 to 1.0.2 (#113)
Bumps [github.com/go-test/deep](https://github.com/go-test/deep) from 1.0.1 to 1.0.2.
- [Release notes](https://github.com/go-test/deep/releases)
- [Changelog](https://github.com/go-test/deep/blob/master/CHANGES.md)
- [Commits](https://github.com/go-test/deep/compare/v1.0.1...v1.0.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-07-16 13:20:42 +09:00
Hidetake Iwata
681faa07ca Update README.md 2019-07-11 11:06:58 +09:00
30 changed files with 1295 additions and 338 deletions

View File

@@ -11,15 +11,13 @@ 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: |
curl -L -o ~/bin/ghcp https://github.com/int128/ghcp/releases/download/v1.3.0/ghcp_linux_amd64
curl -L -o ~/bin/ghcp https://github.com/int128/ghcp/releases/download/v1.5.0/ghcp_linux_amd64
chmod +x ~/bin/ghcp
- run: |
curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b ~/bin v1.16.0
- 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
- run: make check
- run: bash <(curl -s https://codecov.io/bash)
- run: make run

View File

@@ -29,10 +29,13 @@ diagram: docs/authn.png
dist:
VERSION=$(CIRCLE_TAG) goxzst -d dist/gh/ -o "$(TARGET)" -t "kubelogin.rb oidc-login.yaml" -- -ldflags "$(LDFLAGS)"
mv dist/gh/kubelogin.rb dist/
mkdir -p dist/plugins
cp dist/gh/oidc-login.yaml dist/plugins/oidc-login.yaml
release: dist
ghr -u "$(CIRCLE_PROJECT_USERNAME)" -r "$(CIRCLE_PROJECT_REPONAME)" "$(CIRCLE_TAG)" dist/gh/
ghcp -u "$(CIRCLE_PROJECT_USERNAME)" -r "homebrew-$(CIRCLE_PROJECT_REPONAME)" -m "$(CIRCLE_TAG)" -C dist/ kubelogin.rb
ghcp commit -u "$(CIRCLE_PROJECT_USERNAME)" -r "homebrew-$(CIRCLE_PROJECT_REPONAME)" -m "$(CIRCLE_TAG)" -C dist/ kubelogin.rb
ghcp fork-commit -u kubernetes-sigs -r krew-index -b "oidc-login-$(CIRCLE_TAG)" -m "Bump oidc-login to $(CIRCLE_TAG)" -C dist/ plugins/oidc-login.yaml
clean:
-rm $(TARGET)

256
README.md
View File

@@ -3,7 +3,7 @@
This is a kubectl plugin for [Kubernetes OpenID Connect (OIDC) authentication](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#openid-connect-tokens), also known as `kubectl oidc-login`.
In Kubernetes OIDC authentication, kubectl does not provide actual authentication and we need to manually set an ID token and refresh token to the kubeconfig.
Kubelogin provides browser based authentication and writes an ID token and refresh token to the kubeconfig.
Kubelogin integrates browser based authentication with kubectl.
## Getting Started
@@ -19,22 +19,97 @@ brew install kubelogin
kubectl krew install oidc-login
# GitHub Releases
curl -LO https://github.com/int128/kubelogin/releases/download/v1.12.0/kubelogin_linux_amd64.zip
curl -LO https://github.com/int128/kubelogin/releases/download/v1.13.0/kubelogin_linux_amd64.zip
unzip kubelogin_linux_amd64.zip
ln -s kubelogin kubectl-oidc_login
```
You need to configure the OIDC provider, Kubernetes API server, kubectl authentication and role binding.
You need to configure the OIDC provider, Kubernetes API server, kubeconfig and role binding.
See the following documents for more:
- [Getting Started with Keycloak](docs/keycloak.md)
- [Getting Started with Google Identity Platform](docs/google.md)
- [Team Operation](docs/team_ops.md)
You can run kubelogin as the following methods:
### Login by the command
- Run as a credential plugin
- Run as a standalone command
- Wrap kubectl (deprecated)
Just run the command:
### Run as a credential plugin
Status: beta since kubelogin v1.14.0.
You can run kubelogin as a [client-go credential plugin](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#client-go-credential-plugins).
This provides transparent login without manually running `kubelogin` command.
Configure the kubeconfig like:
```yaml
users:
- name: keycloak
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
command: kubelogin
args:
- get-token
- --oidc-issuer-url=https://issuer.example.com
- --oidc-client-id=YOUR_CLIENT_ID
- --oidc-client-secret=YOUR_CLIENT_SECRET
```
Run kubectl.
```sh
kubectl get pods
```
Kubectl executes kubelogin before calling the Kubernetes APIs.
Kubelogin automatically opens the browser and you can log in to the provider.
<img src="docs/keycloak-login.png" alt="keycloak-login" width="455" height="329">
After authentication, kubelogin returns the credentials to kubectl and finally kubectl calls the Kubernetes APIs with the credential.
```
% kubectl get pods
Open http://localhost:8000 for authentication
You got a valid token until 2019-05-18 10:28:51 +0900 JST
NAME READY STATUS RESTARTS AGE
echoserver-86c78fdccd-nzmd5 1/1 Running 0 26d
```
Kubelogin writes the ID token and refresh token to the cache file.
If the cached ID token is valid, kubelogin just returns it.
If the cached ID token has expired, kubelogin will refresh the token using the refresh token.
If the refresh token has expired, kubelogin will proceed the authentication.
### Run as a standalone command
Status: stable.
You can run kubelogin as a standalone command.
In this method, you need to manually run the command before running kubectl.
Configure the kubeconfig like:
```yaml
- name: keycloak
user:
auth-provider:
config:
client-id: YOUR_CLIENT_ID
client-secret: YOUR_CLIENT_SECRET
idp-issuer-url: https://issuer.example.com
name: oidc
```
Run kubelogin:
```sh
kubelogin
@@ -47,7 +122,7 @@ It automatically opens the browser and you can log in to the provider.
<img src="docs/keycloak-login.png" alt="keycloak-login" width="455" height="329">
After authentication, it writes an ID token and refresh token to the kubeconfig.
After authentication, kubelogin writes the ID token and refresh token to the kubeconfig.
```
% kubelogin
@@ -75,7 +150,9 @@ If the ID token has expired, kubelogin will refresh the token using the refresh
If the refresh token has expired, kubelogin will proceed the authentication.
### Wrap kubectl and login transparently
### Wrap kubectl
Status: DEPRECATED and will be removed in kubelogin v1.15.0.
You can wrap kubectl to transparently login to the provider.
@@ -86,7 +163,7 @@ alias kubectl='kubelogin exec -- kubectl'
alias kubectl='kubectl oidc-login exec -- kubectl'
```
If the token expired, it updates the kubeconfig and executes kubectl.
If the token expired, kubelogin updates the kubeconfig and executes kubectl.
```
% kubectl get pods
@@ -96,7 +173,7 @@ NAME READY STATUS RESTARTS AGE
echoserver-86c78fdccd-nzmd5 1/1 Running 0 26d
```
If the ID token is valid, it just executes kubectl.
If the ID token is valid, kubelogin just executes kubectl.
```
% kubectl get pods
@@ -111,7 +188,7 @@ Kubelogin respects kubectl options passed to the extra arguments.
For example, if you run `kubectl --kubeconfig .kubeconfig`,
it will update `.kubeconfig` and execute kubectl.
If the current auth provider is not `oidc`, it just executes kubectl.
If the current auth provider is not `oidc`, kubelogin just executes kubectl.
## Configuration
@@ -119,25 +196,77 @@ If the current auth provider is not `oidc`, it just executes kubectl.
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).
### Run as a credential plugin
Kubelogin supports the following options:
```
% kubelogin get-token -h
Run as a kubectl credential plugin
Usage:
kubelogin get-token [flags]
Flags:
--listen-port ints Port to bind to the local server. If multiple ports are given, it will try the ports in order (default [8000,18000])
--skip-open-browser If true, it does not open the browser on authentication
--username string If set, perform the resource owner password credentials grant
--password string If set, use the password instead of asking it
--oidc-issuer-url string Issuer URL of the provider (mandatory)
--oidc-client-id string Client ID of the provider (mandatory)
--oidc-client-secret string Client secret of the provider
--oidc-extra-scope strings Scopes to request to the provider
--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
--token-cache string Path to a file for caching the token (default "~/.kube/oidc-login.token-cache")
-h, --help help for get-token
```
#### Extra scopes
You can set the extra scopes to request to the provider by `--oidc-extra-scope`.
```yaml
- --oidc-extra-scope=email
- --oidc-extra-scope=profile
```
#### CA Certificates
You can use your self-signed certificates for the provider.
```yaml
- --certificate-authority=/home/user/.kube/keycloak-ca.pem
```
### Run as a standalone command
Kubelogin supports the following options:
```
% kubelogin -h
Login to the OpenID Connect provider and update the kubeconfig
Usage:
kubelogin [flags]
kubelogin [command]
Examples:
# Login to the provider using authorization code grant.
# Login to the provider using the authorization code flow.
kubelogin
# Login to the provider using resource owner password credentials grant.
# Login to the provider using the resource owner password credentials flow.
kubelogin --username USERNAME --password PASSWORD
# Wrap kubectl and login transparently
alias kubectl='kubelogin exec -- kubectl'
# Run as a credential plugin.
kubelogin get-token --oidc-issuer-url=https://issuer.example.com
Available Commands:
exec Login transparently and execute the kubectl command
exec Login transparently and execute the kubectl command (deprecated)
get-token Run as a kubectl credential plugin
help Help about any command
version Print the version information
@@ -155,22 +284,7 @@ Flags:
-h, --help help for kubelogin
```
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
----|-----------|------
`idp-issuer-url` | Read (Mandatory) | Issuer URL of the provider.
`client-id` | Read (Mandatory) | Client ID of the provider.
`client-secret` | Read (Mandatory) | Client Secret of the provider.
`idp-certificate-authority` | Read | CA certificate path of the provider.
`idp-certificate-authority-data` | Read | Base64 encoded CA certificate of the provider.
`extra-scopes` | Read | Scopes to request to the provider (comma separated).
`id-token` | Write | ID token got from the provider.
`refresh-token` | Write | Refresh token got from the provider.
### Kubeconfig
#### Kubeconfig
You can set path to the kubeconfig file by the option or the environment variable just like kubectl.
It defaults to `~/.kube/config`.
@@ -185,6 +299,50 @@ 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.
Kubelogin 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
----|-----------|------
`idp-issuer-url` | Read (Mandatory) | Issuer URL of the provider.
`client-id` | Read (Mandatory) | Client ID of the provider.
`client-secret` | Read (Mandatory) | Client Secret of the provider.
`idp-certificate-authority` | Read | CA certificate path of the provider.
`idp-certificate-authority-data` | Read | Base64 encoded CA certificate of the provider.
`extra-scopes` | Read | Scopes to request to the provider (comma separated).
`id-token` | Write | ID token got from the provider.
`refresh-token` | Write | Refresh token got from the provider.
#### Extra scopes
You can set the extra scopes to request to the provider by `extra-scopes` in the kubeconfig.
```sh
kubectl config set-credentials keycloak --auth-provider-arg extra-scopes=email
```
Currently kubectl does not accept multiple scopes, so you need to edit the kubeconfig as like:
```sh
kubectl config set-credentials keycloak --auth-provider-arg extra-scopes=SCOPES
sed -i '' -e s/SCOPES/email,profile/ $KUBECONFIG
```
#### CA Certificates
You can use your self-signed certificates for the provider.
```sh
kubectl config set-credentials keycloak \
--auth-provider-arg idp-certificate-authority=$HOME/.kube/keycloak-ca.pem
```
### 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).
### Authentication flows
@@ -201,7 +359,11 @@ You need to register the following redirect URIs to the provider:
You can change the ports by the option:
```sh
# run as a standalone command
kubelogin --listen-port 12345 --listen-port 23456
# run as a credential plugin
kubelogin get-token --listen-port 12345 --listen-port 23456
```
@@ -225,38 +387,6 @@ Password:
```
### Extra scopes
You can set the extra scopes to request to the provider by `extra-scopes` in the kubeconfig.
```sh
kubectl config set-credentials keycloak --auth-provider-arg extra-scopes=email
```
Currently kubectl does not accept multiple scopes, so you need to edit the kubeconfig as like:
```sh
kubectl config set-credentials keycloak --auth-provider-arg extra-scopes=SCOPES
sed -i '' -e s/SCOPES/email,profile/ $KUBECONFIG
```
### CA Certificates
You can use your self-signed certificates for the provider.
```sh
kubectl config set-credentials keycloak \
--auth-provider-arg idp-certificate-authority=$HOME/.kube/keycloak-ca.pem
```
### 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.

View File

@@ -12,6 +12,7 @@ import (
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"golang.org/x/xerrors"
"k8s.io/client-go/util/homedir"
)
// Set provides an implementation and interface for Cmd.
@@ -20,20 +21,22 @@ var Set = wire.NewSet(
wire.Bind(new(adaptors.Cmd), new(*Cmd)),
)
const examples = ` # Login to the provider using authorization code grant.
const examples = ` # Login to the provider using the authorization code flow.
%[1]s
# Login to the provider using resource owner password credentials grant.
# Login to the provider using the resource owner password credentials flow.
%[1]s --username USERNAME --password PASSWORD
# Wrap kubectl and login transparently
alias kubectl='%[1]s exec -- kubectl'`
# Run as a credential plugin.
%[1]s get-token --oidc-issuer-url=https://issuer.example.com`
var defaultListenPort = []int{8000, 18000}
var defaultTokenCache = homedir.HomeDir() + "/.kube/oidc-login.token-cache"
// Cmd provides interaction with command line interface (CLI).
type Cmd struct {
Login usecases.Login
GetToken usecases.GetToken
LoginAndExec usecases.LoginAndExec
Logger adaptors.Logger
}
@@ -75,9 +78,10 @@ func (cmd *Cmd) Run(ctx context.Context, args []string, version string) int {
o.kubectlOptions.register(rootCmd.Flags())
o.kubeloginOptions.register(rootCmd.Flags())
//TODO: deprecated
execCmd := cobra.Command{
Use: "exec [flags] -- kubectl [args]",
Short: "Login transparently and execute the kubectl command",
Short: "Login transparently and execute the kubectl command (deprecated)",
Args: func(execCmd *cobra.Command, args []string) error {
if execCmd.ArgsLenAtDash() == -1 {
return xerrors.Errorf("double dash is missing, please run as %s exec -- kubectl", executable)
@@ -125,6 +129,9 @@ func (cmd *Cmd) Run(ctx context.Context, args []string, version string) int {
o.kubeloginOptions.register(execCmd.Flags())
rootCmd.AddCommand(&execCmd)
getTokenCmd := newGetTokenCmd(ctx, cmd)
rootCmd.AddCommand(getTokenCmd)
versionCmd := cobra.Command{
Use: "version",
Short: "Print the version information",
@@ -178,3 +185,72 @@ func (o *kubeloginOptions) register(f *pflag.FlagSet) {
f.StringVar(&o.Username, "username", "", "If set, perform the resource owner password credentials grant")
f.StringVar(&o.Password, "password", "", "If set, use the password instead of asking it")
}
// getTokenOptions represents the options for get-token command.
type getTokenOptions struct {
kubeloginOptions
IssuerURL string
ClientID string
ClientSecret string
ExtraScopes []string
CertificateAuthority string
SkipTLSVerify bool
Verbose int
TokenCacheFilename string
}
func (o *getTokenOptions) register(f *pflag.FlagSet) {
f.SortFlags = false
o.kubeloginOptions.register(f)
f.StringVar(&o.IssuerURL, "oidc-issuer-url", "", "Issuer URL of the provider (mandatory)")
f.StringVar(&o.ClientID, "oidc-client-id", "", "Client ID of the provider (mandatory)")
f.StringVar(&o.ClientSecret, "oidc-client-secret", "", "Client secret of the provider")
f.StringSliceVar(&o.ExtraScopes, "oidc-extra-scope", nil, "Scopes to request to the provider")
f.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")
f.StringVar(&o.TokenCacheFilename, "token-cache", defaultTokenCache, "Path to a file for caching the token")
}
func newGetTokenCmd(ctx context.Context, cmd *Cmd) *cobra.Command {
var o getTokenOptions
c := &cobra.Command{
Use: "get-token [flags]",
Short: "Run as a kubectl credential plugin",
Args: func(c *cobra.Command, args []string) error {
if err := cobra.NoArgs(c, args); err != nil {
return err
}
if o.IssuerURL == "" {
return xerrors.New("--oidc-issuer-url is missing")
}
if o.ClientID == "" {
return xerrors.New("--oidc-client-id is missing")
}
return nil
},
RunE: func(*cobra.Command, []string) error {
cmd.Logger.SetLevel(adaptors.LogLevel(o.Verbose))
in := usecases.GetTokenIn{
IssuerURL: o.IssuerURL,
ClientID: o.ClientID,
ClientSecret: o.ClientSecret,
ExtraScopes: o.ExtraScopes,
CACertFilename: o.CertificateAuthority,
SkipTLSVerify: o.SkipTLSVerify,
ListenPort: o.ListenPort,
SkipOpenBrowser: o.SkipOpenBrowser,
Username: o.Username,
Password: o.Password,
TokenCacheFilename: o.TokenCacheFilename,
}
if err := cmd.GetToken.Do(ctx, in); err != nil {
return xerrors.Errorf("error: %w", err)
}
return nil
},
}
c.SilenceUsage = true
o.register(c.Flags())
return c
}

View File

@@ -265,4 +265,112 @@ func TestCmd_Run(t *testing.T) {
t.Errorf("exitCode wants 0 but %d", exitCode)
}
})
t.Run("get-token/Defaults", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.TODO()
getToken := mock_usecases.NewMockGetToken(ctrl)
getToken.EXPECT().
Do(ctx, usecases.GetTokenIn{
ListenPort: defaultListenPort,
TokenCacheFilename: defaultTokenCache,
IssuerURL: "https://issuer.example.com",
ClientID: "YOUR_CLIENT_ID",
})
logger := mock_adaptors.NewLogger(t, ctrl)
logger.EXPECT().SetLevel(adaptors.LogLevel(0))
cmd := Cmd{
GetToken: getToken,
Logger: logger,
}
exitCode := cmd.Run(ctx, []string{executable,
"get-token",
"--oidc-issuer-url", "https://issuer.example.com",
"--oidc-client-id", "YOUR_CLIENT_ID",
}, version)
if exitCode != 0 {
t.Errorf("exitCode wants 0 but %d", exitCode)
}
})
t.Run("get-token/FullOptions", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.TODO()
getToken := mock_usecases.NewMockGetToken(ctrl)
getToken.EXPECT().
Do(ctx, usecases.GetTokenIn{
TokenCacheFilename: defaultTokenCache,
IssuerURL: "https://issuer.example.com",
ClientID: "YOUR_CLIENT_ID",
ClientSecret: "YOUR_CLIENT_SECRET",
ExtraScopes: []string{"email", "profile"},
CACertFilename: "/path/to/cacert",
SkipTLSVerify: true,
ListenPort: []int{10080, 20080},
SkipOpenBrowser: true,
Username: "USER",
Password: "PASS",
})
logger := mock_adaptors.NewLogger(t, ctrl)
logger.EXPECT().SetLevel(adaptors.LogLevel(1))
cmd := Cmd{
GetToken: getToken,
Logger: logger,
}
exitCode := cmd.Run(ctx, []string{executable,
"get-token",
"--oidc-issuer-url", "https://issuer.example.com",
"--oidc-client-id", "YOUR_CLIENT_ID",
"--oidc-client-secret", "YOUR_CLIENT_SECRET",
"--oidc-extra-scope", "email",
"--oidc-extra-scope", "profile",
"--certificate-authority", "/path/to/cacert",
"--insecure-skip-tls-verify",
"-v1",
"--listen-port", "10080",
"--listen-port", "20080",
"--skip-open-browser",
"--username", "USER",
"--password", "PASS",
}, version)
if exitCode != 0 {
t.Errorf("exitCode wants 0 but %d", exitCode)
}
})
t.Run("get-token/MissingMandatoryOptions", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.TODO()
cmd := Cmd{
GetToken: mock_usecases.NewMockGetToken(ctrl),
Logger: mock_adaptors.NewLogger(t, ctrl),
}
exitCode := cmd.Run(ctx, []string{executable, "get-token"}, version)
if exitCode != 1 {
t.Errorf("exitCode wants 1 but %d", exitCode)
}
})
t.Run("get-token/TooManyArgs", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.TODO()
cmd := Cmd{
GetToken: mock_usecases.NewMockGetToken(ctrl),
Logger: mock_adaptors.NewLogger(t, ctrl),
}
exitCode := cmd.Run(ctx, []string{executable, "get-token", "foo"}, version)
if exitCode != 1 {
t.Errorf("exitCode wants 1 but %d", exitCode)
}
})
}

View File

@@ -0,0 +1,40 @@
// Package credentialplugin provides interaction with kubectl for a credential plugin.
package credentialplugin
import (
"encoding/json"
"os"
"github.com/google/wire"
"github.com/int128/kubelogin/adaptors"
"github.com/int128/kubelogin/models/credentialplugin"
"golang.org/x/xerrors"
"k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/pkg/apis/clientauthentication/v1beta1"
)
var Set = wire.NewSet(
wire.Struct(new(Interaction), "*"),
wire.Bind(new(adaptors.CredentialPluginInteraction), new(*Interaction)),
)
type Interaction struct{}
// Write writes the ExecCredential to standard output for kubectl.
func (*Interaction) Write(out credentialplugin.Output) error {
ec := &v1beta1.ExecCredential{
TypeMeta: v1.TypeMeta{
APIVersion: "client.authentication.k8s.io/v1beta1",
Kind: "ExecCredential",
},
Status: &v1beta1.ExecCredentialStatus{
Token: out.Token,
ExpirationTimestamp: &v1.Time{Time: out.Expiry},
},
}
e := json.NewEncoder(os.Stdout)
if err := e.Encode(ec); err != nil {
return xerrors.Errorf("could not write the ExecCredential: %w", err)
}
return nil
}

View File

@@ -4,10 +4,11 @@ import (
"context"
"time"
"github.com/int128/kubelogin/models/credentialplugin"
"github.com/int128/kubelogin/models/kubeconfig"
)
//go:generate mockgen -destination mock_adaptors/mock_adaptors.go github.com/int128/kubelogin/adaptors Kubeconfig,OIDC,OIDCClient,Env,Logger
//go:generate mockgen -destination mock_adaptors/mock_adaptors.go github.com/int128/kubelogin/adaptors Kubeconfig,TokenCacheRepository,CredentialPluginInteraction,OIDC,OIDCClient,Env,Logger
type Cmd interface {
Run(ctx context.Context, args []string, version string) int
@@ -18,6 +19,15 @@ type Kubeconfig interface {
UpdateAuthProvider(auth *kubeconfig.AuthProvider) error
}
type TokenCacheRepository interface {
Read(filename string) (*credentialplugin.TokenCache, error)
Write(filename string, tc credentialplugin.TokenCache) error
}
type CredentialPluginInteraction interface {
Write(out credentialplugin.Output) error
}
type OIDC interface {
New(ctx context.Context, config OIDCClientConfig) (OIDCClient, error)
}

View File

@@ -1,5 +1,5 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/int128/kubelogin/adaptors (interfaces: Kubeconfig,OIDC,OIDCClient,Env,Logger)
// Source: github.com/int128/kubelogin/adaptors (interfaces: Kubeconfig,TokenCacheRepository,CredentialPluginInteraction,OIDC,OIDCClient,Env,Logger)
// Package mock_adaptors is a generated GoMock package.
package mock_adaptors
@@ -8,6 +8,7 @@ import (
context "context"
gomock "github.com/golang/mock/gomock"
adaptors "github.com/int128/kubelogin/adaptors"
credentialplugin "github.com/int128/kubelogin/models/credentialplugin"
kubeconfig "github.com/int128/kubelogin/models/kubeconfig"
reflect "reflect"
)
@@ -60,6 +61,89 @@ func (mr *MockKubeconfigMockRecorder) UpdateAuthProvider(arg0 interface{}) *gomo
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateAuthProvider", reflect.TypeOf((*MockKubeconfig)(nil).UpdateAuthProvider), arg0)
}
// MockTokenCacheRepository is a mock of TokenCacheRepository interface
type MockTokenCacheRepository struct {
ctrl *gomock.Controller
recorder *MockTokenCacheRepositoryMockRecorder
}
// MockTokenCacheRepositoryMockRecorder is the mock recorder for MockTokenCacheRepository
type MockTokenCacheRepositoryMockRecorder struct {
mock *MockTokenCacheRepository
}
// NewMockTokenCacheRepository creates a new mock instance
func NewMockTokenCacheRepository(ctrl *gomock.Controller) *MockTokenCacheRepository {
mock := &MockTokenCacheRepository{ctrl: ctrl}
mock.recorder = &MockTokenCacheRepositoryMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockTokenCacheRepository) EXPECT() *MockTokenCacheRepositoryMockRecorder {
return m.recorder
}
// Read mocks base method
func (m *MockTokenCacheRepository) Read(arg0 string) (*credentialplugin.TokenCache, error) {
ret := m.ctrl.Call(m, "Read", arg0)
ret0, _ := ret[0].(*credentialplugin.TokenCache)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Read indicates an expected call of Read
func (mr *MockTokenCacheRepositoryMockRecorder) Read(arg0 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Read", reflect.TypeOf((*MockTokenCacheRepository)(nil).Read), arg0)
}
// Write mocks base method
func (m *MockTokenCacheRepository) Write(arg0 string, arg1 credentialplugin.TokenCache) error {
ret := m.ctrl.Call(m, "Write", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// Write indicates an expected call of Write
func (mr *MockTokenCacheRepositoryMockRecorder) Write(arg0, arg1 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Write", reflect.TypeOf((*MockTokenCacheRepository)(nil).Write), arg0, arg1)
}
// MockCredentialPluginInteraction is a mock of CredentialPluginInteraction interface
type MockCredentialPluginInteraction struct {
ctrl *gomock.Controller
recorder *MockCredentialPluginInteractionMockRecorder
}
// MockCredentialPluginInteractionMockRecorder is the mock recorder for MockCredentialPluginInteraction
type MockCredentialPluginInteractionMockRecorder struct {
mock *MockCredentialPluginInteraction
}
// NewMockCredentialPluginInteraction creates a new mock instance
func NewMockCredentialPluginInteraction(ctrl *gomock.Controller) *MockCredentialPluginInteraction {
mock := &MockCredentialPluginInteraction{ctrl: ctrl}
mock.recorder = &MockCredentialPluginInteractionMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockCredentialPluginInteraction) EXPECT() *MockCredentialPluginInteractionMockRecorder {
return m.recorder
}
// Write mocks base method
func (m *MockCredentialPluginInteraction) Write(arg0 credentialplugin.Output) error {
ret := m.ctrl.Call(m, "Write", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// Write indicates an expected call of Write
func (mr *MockCredentialPluginInteractionMockRecorder) Write(arg0 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Write", reflect.TypeOf((*MockCredentialPluginInteraction)(nil).Write), arg0)
}
// MockOIDC is a mock of OIDC interface
type MockOIDC struct {
ctrl *gomock.Controller

View File

@@ -0,0 +1,46 @@
package tokencache
import (
"encoding/json"
"os"
"github.com/google/wire"
"github.com/int128/kubelogin/adaptors"
"github.com/int128/kubelogin/models/credentialplugin"
"golang.org/x/xerrors"
)
// Set provides an implementation and interface for Kubeconfig.
var Set = wire.NewSet(
wire.Struct(new(Repository), "*"),
wire.Bind(new(adaptors.TokenCacheRepository), new(*Repository)),
)
type Repository struct{}
func (*Repository) Read(filename string) (*credentialplugin.TokenCache, error) {
f, err := os.Open(filename)
if err != nil {
return nil, xerrors.Errorf("could not open file %s: %w", filename, err)
}
defer f.Close()
d := json.NewDecoder(f)
var c credentialplugin.TokenCache
if err := d.Decode(&c); err != nil {
return nil, xerrors.Errorf("could not decode json file %s: %w", filename, err)
}
return &c, nil
}
func (*Repository) Write(filename string, tc credentialplugin.TokenCache) error {
f, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return xerrors.Errorf("could not create file %s: %w", filename, err)
}
defer f.Close()
e := json.NewEncoder(f)
if err := e.Encode(&tc); err != nil {
return xerrors.Errorf("could not encode json to file %s: %w", filename, err)
}
return nil
}

View File

@@ -0,0 +1,73 @@
package tokencache
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/go-test/deep"
"github.com/int128/kubelogin/models/credentialplugin"
)
func TestRepository_Read(t *testing.T) {
var r Repository
t.Run("Success", func(t *testing.T) {
dir, err := ioutil.TempDir("", "kube")
if err != nil {
t.Fatalf("could not create a temp dir: %s", err)
}
defer func() {
if err := os.RemoveAll(dir); err != nil {
t.Errorf("could not clean up the temp dir: %s", err)
}
}()
json := `{"id_token":"YOUR_ID_TOKEN","refresh_token":"YOUR_REFRESH_TOKEN"}`
filename := filepath.Join(dir, "token-cache")
if err := ioutil.WriteFile(filename, []byte(json), 0600); err != nil {
t.Fatalf("could not write to the temp file: %s", err)
}
tokenCache, err := r.Read(filename)
if err != nil {
t.Errorf("err wants nil but %+v", err)
}
want := &credentialplugin.TokenCache{IDToken: "YOUR_ID_TOKEN", RefreshToken: "YOUR_REFRESH_TOKEN"}
if diff := deep.Equal(tokenCache, want); diff != nil {
t.Error(diff)
}
})
}
func TestRepository_Write(t *testing.T) {
var r Repository
t.Run("Success", func(t *testing.T) {
dir, err := ioutil.TempDir("", "kube")
if err != nil {
t.Fatalf("could not create a temp dir: %s", err)
}
defer func() {
if err := os.RemoveAll(dir); err != nil {
t.Errorf("could not clean up the temp dir: %s", err)
}
}()
filename := filepath.Join(dir, "token-cache")
tokenCache := credentialplugin.TokenCache{IDToken: "YOUR_ID_TOKEN", RefreshToken: "YOUR_REFRESH_TOKEN"}
if err := r.Write(filename, tokenCache); err != nil {
t.Errorf("err wants nil but %+v", err)
}
b, err := ioutil.ReadFile(filename)
if err != nil {
t.Fatalf("could not read the token cache file: %s", err)
}
want := `{"id_token":"YOUR_ID_TOKEN","refresh_token":"YOUR_REFRESH_TOKEN"}
`
if diff := deep.Equal(string(b), want); diff != nil {
t.Error(diff)
}
})
}

View File

@@ -7,12 +7,15 @@ import (
"github.com/google/wire"
"github.com/int128/kubelogin/adaptors"
"github.com/int128/kubelogin/adaptors/cmd"
credentialPluginAdaptor "github.com/int128/kubelogin/adaptors/credentialplugin"
"github.com/int128/kubelogin/adaptors/env"
"github.com/int128/kubelogin/adaptors/kubeconfig"
"github.com/int128/kubelogin/adaptors/logger"
"github.com/int128/kubelogin/adaptors/oidc"
"github.com/int128/kubelogin/adaptors/tokencache"
"github.com/int128/kubelogin/usecases"
"github.com/int128/kubelogin/usecases/auth"
credentialPluginUseCase "github.com/int128/kubelogin/usecases/credentialplugin"
"github.com/int128/kubelogin/usecases/login"
)
@@ -22,24 +25,32 @@ func NewCmd() adaptors.Cmd {
auth.Set,
auth.ExtraSet,
login.Set,
credentialPluginUseCase.Set,
cmd.Set,
env.Set,
kubeconfig.Set,
tokencache.Set,
credentialPluginAdaptor.Set,
oidc.Set,
logger.Set,
)
return nil
}
// NewCmdWith returns an instance of adaptors.Cmd with given dependencies.
// This is only for e2e tests.
func NewCmdWith(adaptors.Logger, usecases.LoginShowLocalServerURL) adaptors.Cmd {
// NewCmdForHeadless returns an instance of adaptors.Cmd for headless testing.
func NewCmdForHeadless(
adaptors.Logger,
usecases.LoginShowLocalServerURL,
adaptors.CredentialPluginInteraction,
) adaptors.Cmd {
wire.Build(
auth.Set,
login.Set,
credentialPluginUseCase.Set,
cmd.Set,
env.Set,
kubeconfig.Set,
tokencache.Set,
oidc.Set,
)
return nil

View File

@@ -8,12 +8,15 @@ package di
import (
"github.com/int128/kubelogin/adaptors"
"github.com/int128/kubelogin/adaptors/cmd"
"github.com/int128/kubelogin/adaptors/credentialplugin"
"github.com/int128/kubelogin/adaptors/env"
"github.com/int128/kubelogin/adaptors/kubeconfig"
"github.com/int128/kubelogin/adaptors/logger"
"github.com/int128/kubelogin/adaptors/oidc"
"github.com/int128/kubelogin/adaptors/tokencache"
"github.com/int128/kubelogin/usecases"
"github.com/int128/kubelogin/usecases/auth"
credentialplugin2 "github.com/int128/kubelogin/usecases/credentialplugin"
"github.com/int128/kubelogin/usecases/login"
)
@@ -40,6 +43,14 @@ func NewCmd() adaptors.Cmd {
Kubeconfig: kubeconfigKubeconfig,
Logger: adaptorsLogger,
}
repository := &tokencache.Repository{}
interaction := &credentialplugin.Interaction{}
getToken := &credentialplugin2.GetToken{
Authentication: authentication,
TokenCacheRepository: repository,
Interaction: interaction,
Logger: adaptorsLogger,
}
exec := &login.Exec{
Authentication: authentication,
Kubeconfig: kubeconfigKubeconfig,
@@ -48,13 +59,14 @@ func NewCmd() adaptors.Cmd {
}
cmdCmd := &cmd.Cmd{
Login: loginLogin,
GetToken: getToken,
LoginAndExec: exec,
Logger: adaptorsLogger,
}
return cmdCmd
}
func NewCmdWith(adaptorsLogger adaptors.Logger, loginShowLocalServerURL usecases.LoginShowLocalServerURL) adaptors.Cmd {
func NewCmdForHeadless(adaptorsLogger adaptors.Logger, loginShowLocalServerURL usecases.LoginShowLocalServerURL, credentialPluginInteraction adaptors.CredentialPluginInteraction) adaptors.Cmd {
factory := &oidc.Factory{
Logger: adaptorsLogger,
}
@@ -71,6 +83,13 @@ func NewCmdWith(adaptorsLogger adaptors.Logger, loginShowLocalServerURL usecases
Kubeconfig: kubeconfigKubeconfig,
Logger: adaptorsLogger,
}
repository := &tokencache.Repository{}
getToken := &credentialplugin2.GetToken{
Authentication: authentication,
TokenCacheRepository: repository,
Interaction: credentialPluginInteraction,
Logger: adaptorsLogger,
}
exec := &login.Exec{
Authentication: authentication,
Kubeconfig: kubeconfigKubeconfig,
@@ -79,6 +98,7 @@ func NewCmdWith(adaptorsLogger adaptors.Logger, loginShowLocalServerURL usecases
}
cmdCmd := &cmd.Cmd{
Login: loginLogin,
GetToken: getToken,
LoginAndExec: exec,
Logger: adaptorsLogger,
}

View File

@@ -17,6 +17,11 @@ Open [Google APIs Console](https://console.developers.google.com/apis/credential
Configure your Kubernetes API Server accepts [OpenID Connect Tokens](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#openid-connect-tokens).
```
--oidc-issuer-url=https://accounts.google.com
--oidc-client-id=YOUR_CLIENT_ID.apps.googleusercontent.com
```
If you are using [kops](https://github.com/kubernetes/kops), run `kops edit cluster` and append the following settings:
```yaml
@@ -46,49 +51,33 @@ subjects:
You can create a custom role and assign it as well.
## 4. Setup kubectl
## 4. Setup kubeconfig
Configure `kubectl` for the OIDC authentication.
```sh
kubectl config set-credentials KUBECONTEXT \
--auth-provider oidc \
--auth-provider-arg idp-issuer-url=https://accounts.google.com \
--auth-provider-arg client-id=YOUR_CLIENT_ID.apps.googleusercontent.com \
--auth-provider-arg client-secret=YOUR_CLIENT_SECRET
```
## 5. Run kubelogin
Run `kubelogin`.
```
% kubelogin
Open http://localhost:8000 for authentication
You got a valid token until 2019-05-16 22:03:13 +0900 JST
Updated ~/.kubeconfig
```
Now your `~/.kube/config` should be like:
Configure the kubeconfig like:
```yaml
users:
- name: hello.k8s.local
- name: google
user:
auth-provider:
config:
idp-issuer-url: https://accounts.google.com
client-id: YOUR_CLIENT_ID.apps.googleusercontent.com
client-secret: YOUR_SECRET
id-token: ey... # kubelogin will update ID token here
refresh-token: ey... # kubelogin will update refresh token here
name: oidc
exec:
apiVersion: client.authentication.k8s.io/v1beta1
command: kubelogin
args:
- get-token
- --oidc-issuer-url=https://accounts.google.com
- --oidc-client-id=YOUR_CLIENT_ID.apps.googleusercontent.com
- --oidc-client-secret=YOUR_CLIENT_SECRET
```
## 5. Run kubectl
Make sure you can access to the Kubernetes cluster.
```
% kubectl get nodes
Open http://localhost:8000 for authentication
You got a valid token until 2019-05-16 22:03:13 +0900 JST
Updated ~/.kubeconfig
NAME STATUS ROLES AGE VERSION
ip-1-2-3-4.us-west-2.compute.internal Ready node 21d v1.9.6
ip-1-2-3-5.us-west-2.compute.internal Ready node 20d v1.9.6

View File

@@ -32,6 +32,12 @@ For example, if you have the `admin` role of the client, you will get a JWT with
Configure your Kubernetes API server accepts [OpenID Connect Tokens](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#openid-connect-tokens).
```
--oidc-issuer-url=https://keycloak.example.com/auth/realms/YOUR_REALM
--oidc-client-id=kubernetes
--oidc-groups-claim=groups
```
If you are using [kops](https://github.com/kubernetes/kops), run `kops edit cluster` and add the following spec:
```yaml
@@ -62,49 +68,33 @@ subjects:
You can create a custom role and assign it as well.
## 4. Setup kubectl
## 4. Setup kubeconfig
Configure `kubectl` for the OIDC authentication.
```sh
kubectl config set-credentials KUBECONTEXT \
--auth-provider oidc \
--auth-provider-arg idp-issuer-url=https://keycloak.example.com/auth/realms/YOUR_REALM \
--auth-provider-arg client-id=kubernetes \
--auth-provider-arg client-secret=YOUR_CLIENT_SECRET
```
## 5. Run kubelogin
Run `kubelogin`.
```
% kubelogin
Open http://localhost:8000 for authentication
You got a valid token until 2019-05-16 22:03:13 +0900 JST
Updated ~/.kubeconfig
```
Now your `~/.kube/config` should be like:
Configure the kubeconfig like:
```yaml
users:
- name: hello.k8s.local
- name: keycloak
user:
auth-provider:
config:
idp-issuer-url: https://keycloak.example.com/auth/realms/YOUR_REALM
client-id: kubernetes
client-secret: YOUR_SECRET
id-token: ey... # kubelogin will update ID token here
refresh-token: ey... # kubelogin will update refresh token here
name: oidc
exec:
apiVersion: client.authentication.k8s.io/v1beta1
command: kubelogin
args:
- get-token
- --oidc-issuer-url=https://keycloak.example.com/auth/realms/YOUR_REALM
- --oidc-client-id=kubernetes
- --oidc-client-secret=YOUR_CLIENT_SECRET
```
## 5. Run kubectl
Make sure you can access to the Kubernetes cluster.
```
% kubectl get nodes
Open http://localhost:8000 for authentication
You got a valid token until 2019-05-16 22:03:13 +0900 JST
Updated ~/.kubeconfig
NAME STATUS ROLES AGE VERSION
ip-1-2-3-4.us-west-2.compute.internal Ready node 21d v1.9.6
ip-1-2-3-5.us-west-2.compute.internal Ready node 20d v1.9.6

View File

@@ -1,4 +1,4 @@
# Team Operation
# Team on-boarding
## kops
@@ -29,12 +29,14 @@ preferences: {}
users:
- name: hello.k8s.local
user:
auth-provider:
name: oidc
config:
client-id: YOUR_CLIEND_ID
client-secret: YOUR_CLIENT_SECRET
idp-issuer-url: YOUR_ISSUER
exec:
apiVersion: client.authentication.k8s.io/v1beta1
command: kubelogin
args:
- get-token
- --oidc-issuer-url=https://keycloak.example.com/auth/realms/YOUR_REALM
- --oidc-client-id=YOUR_CLIENT_ID
- --oidc-client-secret=YOUR_CLIENT_SECRET
```
You can share the kubeconfig to your team members for easy onboarding.
You can share the kubeconfig to your team members for on-boarding.

View File

@@ -0,0 +1,74 @@
package e2e_test
import (
"context"
"testing"
"time"
"github.com/golang/mock/gomock"
"github.com/int128/kubelogin/adaptors"
"github.com/int128/kubelogin/adaptors/mock_adaptors"
"github.com/int128/kubelogin/di"
"github.com/int128/kubelogin/e2e_test/idp"
"github.com/int128/kubelogin/e2e_test/idp/mock_idp"
"github.com/int128/kubelogin/e2e_test/localserver"
"github.com/int128/kubelogin/e2e_test/logger"
"github.com/int128/kubelogin/models/credentialplugin"
"github.com/int128/kubelogin/usecases"
)
// Run the integration tests of the credential plugin use-case.
//
// 1. Start the auth server.
// 2. Run the Cmd.
// 3. Open a request for the local server.
// 4. Verify the output.
//
func TestCmd_Run_CredentialPlugin(t *testing.T) {
timeout := 1 * time.Second
t.Run("Defaults", func(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
ctrl := gomock.NewController(t)
defer ctrl.Finish()
service := mock_idp.NewMockService(ctrl)
serverURL, server := localserver.Start(t, idp.NewHandler(t, service))
defer server.Shutdown(t, ctx)
var idToken string
setupMockIDPForCodeFlow(t, service, serverURL, "openid", &idToken)
credentialPluginInteraction := mock_adaptors.NewMockCredentialPluginInteraction(ctrl)
credentialPluginInteraction.EXPECT().
Write(gomock.Any()).
Do(func(out credentialplugin.Output) {
if out.Token != idToken {
t.Errorf("Token wants %s but %s", idToken, out.Token)
}
if out.Expiry != tokenExpiryFuture {
t.Errorf("Expiry wants %v but %v", tokenExpiryFuture, out.Expiry)
}
})
req := startBrowserRequest(t, ctx, nil)
runGetTokenCmd(t, ctx, req, credentialPluginInteraction,
"--skip-open-browser",
"--listen-port", "0",
"--token-cache", "/dev/null",
"--oidc-issuer-url", serverURL,
"--oidc-client-id", "kubernetes",
)
req.wait()
})
}
func runGetTokenCmd(t *testing.T, ctx context.Context, s usecases.LoginShowLocalServerURL, interaction adaptors.CredentialPluginInteraction, args ...string) {
t.Helper()
cmd := di.NewCmdForHeadless(logger.New(t), s, interaction)
exitCode := cmd.Run(ctx, append([]string{"kubelogin", "get-token", "--v=1"}, args...), "HEAD")
if exitCode != 0 {
t.Errorf("exit status wants 0 but %d", exitCode)
}
}

View File

@@ -21,14 +21,19 @@ import (
"github.com/int128/kubelogin/usecases"
)
// Run the integration tests.
var (
tokenExpiryFuture = time.Now().Add(time.Hour).Round(time.Second)
tokenExpiryPast = time.Now().Add(-time.Hour).Round(time.Second)
)
// Run the integration tests of the Login use-case.
//
// 1. Start the auth server.
// 2. Run the Cmd.
// 3. Open a request for the local server.
// 4. Verify the kuneconfig.
// 4. Verify the kubeconfig.
//
func TestCmd_Run(t *testing.T) {
func TestCmd_Run_Login(t *testing.T) {
timeout := 1 * time.Second
t.Run("Defaults", func(t *testing.T) {
@@ -66,7 +71,7 @@ func TestCmd_Run(t *testing.T) {
service := mock_idp.NewMockService(ctrl)
serverURL, server := localserver.Start(t, idp.NewHandler(t, service))
defer server.Shutdown(t, ctx)
idToken := newIDToken(t, serverURL, "", time.Hour)
idToken := newIDToken(t, serverURL, "", tokenExpiryFuture)
service.EXPECT().Discovery().Return(idp.NewDiscoveryResponse(serverURL))
service.EXPECT().GetCertificates().Return(idp.NewCertificatesResponse(keys.JWSKeyPair))
service.EXPECT().AuthenticatePassword("USER", "PASS", "openid").
@@ -203,7 +208,7 @@ func TestCmd_Run(t *testing.T) {
service := mock_idp.NewMockService(ctrl)
serverURL, server := localserver.Start(t, idp.NewHandler(t, service))
defer server.Shutdown(t, ctx)
idToken := newIDToken(t, serverURL, "YOUR_NONCE", time.Hour)
idToken := newIDToken(t, serverURL, "YOUR_NONCE", tokenExpiryFuture)
service.EXPECT().Discovery().Return(idp.NewDiscoveryResponse(serverURL))
service.EXPECT().GetCertificates().Return(idp.NewCertificatesResponse(keys.JWSKeyPair))
@@ -231,7 +236,7 @@ func TestCmd_Run(t *testing.T) {
service := mock_idp.NewMockService(ctrl)
serverURL, server := localserver.Start(t, idp.NewHandler(t, service))
defer server.Shutdown(t, ctx)
idToken := newIDToken(t, serverURL, "YOUR_NONCE", time.Hour)
idToken := newIDToken(t, serverURL, "YOUR_NONCE", tokenExpiryFuture)
service.EXPECT().Discovery().Return(idp.NewDiscoveryResponse(serverURL))
service.EXPECT().GetCertificates().Return(idp.NewCertificatesResponse(keys.JWSKeyPair))
service.EXPECT().Refresh("VALID_REFRESH_TOKEN").
@@ -239,7 +244,7 @@ func TestCmd_Run(t *testing.T) {
kubeConfigFilename := kubeconfig.Create(t, &kubeconfig.Values{
Issuer: serverURL,
IDToken: newIDToken(t, serverURL, "YOUR_NONCE", -time.Hour), // expired
IDToken: newIDToken(t, serverURL, "YOUR_NONCE", tokenExpiryPast), // expired
RefreshToken: "VALID_REFRESH_TOKEN",
})
defer os.Remove(kubeConfigFilename)
@@ -269,7 +274,7 @@ func TestCmd_Run(t *testing.T) {
kubeConfigFilename := kubeconfig.Create(t, &kubeconfig.Values{
Issuer: serverURL,
IDToken: newIDToken(t, serverURL, "YOUR_NONCE", -time.Hour), // expired
IDToken: newIDToken(t, serverURL, "YOUR_NONCE", tokenExpiryPast), // expired
RefreshToken: "EXPIRED_REFRESH_TOKEN",
})
defer os.Remove(kubeConfigFilename)
@@ -283,7 +288,7 @@ func TestCmd_Run(t *testing.T) {
})
}
func newIDToken(t *testing.T, issuer, nonce string, expiration time.Duration) string {
func newIDToken(t *testing.T, issuer, nonce string, expiry time.Time) string {
t.Helper()
var claims struct {
jwt.StandardClaims
@@ -295,7 +300,7 @@ func newIDToken(t *testing.T, issuer, nonce string, expiration time.Duration) st
Audience: "kubernetes",
Subject: "SUBJECT",
IssuedAt: time.Now().Unix(),
ExpiresAt: time.Now().Add(expiration).Unix(),
ExpiresAt: expiry.Unix(),
}
claims.Nonce = nonce
claims.Groups = []string{"admin", "users"}
@@ -318,14 +323,14 @@ func setupMockIDPForCodeFlow(t *testing.T, service *mock_idp.MockService, server
})
service.EXPECT().Exchange("YOUR_AUTH_CODE").
DoAndReturn(func(string) (*idp.TokenResponse, error) {
*idToken = newIDToken(t, serverURL, nonce, time.Hour)
*idToken = newIDToken(t, serverURL, nonce, tokenExpiryFuture)
return idp.NewTokenResponse(*idToken, "YOUR_REFRESH_TOKEN"), nil
})
}
func runCmd(t *testing.T, ctx context.Context, s usecases.LoginShowLocalServerURL, args ...string) {
t.Helper()
cmd := di.NewCmdWith(logger.New(t), s)
cmd := di.NewCmdForHeadless(logger.New(t), s, nil)
exitCode := cmd.Run(ctx, append([]string{"kubelogin", "--v=1"}, args...), "HEAD")
if exitCode != 0 {
t.Errorf("exit status wants 0 but %d", exitCode)

30
go.mod
View File

@@ -1,32 +1,22 @@
module github.com/int128/kubelogin
go 1.12
require (
github.com/coreos/go-oidc v2.0.0+incompatible
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/go-test/deep v1.0.1
github.com/gogo/protobuf v1.2.1 // indirect
github.com/dgrijalva/jwt-go v0.0.0-20160705203006-01aeca54ebda
github.com/go-test/deep v1.0.2
github.com/golang/mock v1.3.1
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf // indirect
github.com/google/wire v0.3.0
github.com/imdario/mergo v0.3.7 // indirect
github.com/int128/oauth2cli v1.4.1
github.com/json-iterator/go v1.1.6 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
github.com/spf13/cobra v0.0.5
github.com/spf13/pflag v1.0.3
github.com/stretchr/testify v1.3.0 // indirect
golang.org/x/crypto v0.0.0-20190422183909-d864b10871cd
golang.org/x/oauth2 v0.0.0-20190319182350-c85d3e98c914
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/square/go-jose.v2 v2.3.0 // indirect
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7
gopkg.in/square/go-jose.v2 v2.3.1 // indirect
gopkg.in/yaml.v2 v2.2.2
k8s.io/api v0.0.0-20190222213804-5cb15d344471 // indirect
k8s.io/apimachinery v0.0.0-20190221213512-86fb29eff628 // indirect
k8s.io/client-go v10.0.0+incompatible
k8s.io/klog v0.2.0 // indirect
sigs.k8s.io/yaml v1.1.0 // indirect
k8s.io/apimachinery v0.0.0-20190612205821-1799e75a0719
k8s.io/client-go v0.0.0-20190620085101-78d2af792bab
)

124
go.sum
View File

@@ -1,4 +1,5 @@
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/Azure/go-autorest v11.1.2+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
@@ -7,127 +8,138 @@ github.com/coreos/go-oidc v2.0.0+incompatible h1:+RStIopZ8wooMx+Vs5Bt8zMXxV1ABl5
github.com/coreos/go-oidc v2.0.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgrijalva/jwt-go v0.0.0-20160705203006-01aeca54ebda h1:NyywMz59neOoVRFDz+ccfKWxn784fiHMDnZSy6T+JXY=
github.com/dgrijalva/jwt-go v0.0.0-20160705203006-01aeca54ebda/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
github.com/evanphx/json-patch v0.0.0-20190203023257-5858425f7550/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/go-test/deep v1.0.1 h1:UQhStjbkDClarlmv0am7OXXO4/GaPdCGiUiMTvi28sg=
github.com/go-test/deep v1.0.1/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
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/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw=
github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/gogo/protobuf v0.0.0-20171007142547-342cbe0a0415 h1:WSBJMqJbLxsn+bTCPyPYZfqHdJmc8MK4wrBjMft6BAM=
github.com/gogo/protobuf v0.0.0-20171007142547-342cbe0a0415/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/btree v0.0.0-20160524151835-7d79101e329e/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeqr2z77+8R2RKyh8PG66dcu1V0ck=
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/wire v0.3.0 h1:imGQZGEVEHpje5056+K+cgdO72p0LQv2xIIFXNGUf60=
github.com/google/wire v0.3.0/go.mod h1:i1DMg/Lu8Sz5yYl25iOdmc5CT5qusaa+zmRWs16741s=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/gophercloud/gophercloud v0.0.0-20190126172459-c818fa66e4c8/go.mod h1:3WdhXV3rUYy9p6AUW8d94kr+HS62Y4VL9mBnFxsD8q4=
github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
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/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/imdario/mergo v0.3.5 h1:JboBksRwiiAJWvIYJVo46AfV+IAIKZpfrSzVKj42R4Q=
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/int128/oauth2cli v1.4.1 h1:IsaYMafEDS1jyArxYdmksw+nMsNxiYCQzdkPj3QF9BY=
github.com/int128/oauth2cli v1.4.1/go.mod h1:CMJjyUSgKiobye1M/9byFACOjtB2LRo2mo7boklEKlI=
github.com/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/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
github.com/json-iterator/go v0.0.0-20180701071628-ab8a2e0c74be h1:AHimNtVIpiBjPUhEF5KNCkrUyqTSA5zWUl8sQ2bfGBE=
github.com/json-iterator/go v0.0.0-20180701071628-ab8a2e0c74be/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
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/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
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=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v0.0.0-20190113212917-5533ce8a0da3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4 h1:49lOXmGaUpV9Fz3gd7TFZY106KVlPVa5jcYD1gaQf98=
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU=
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.1 h1:aCvUg6QPl3ibpQUxyLkrEkCHtPqYJL4x9AuhqVqFis4=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
golang.org/x/crypto v0.0.0-20181025213731-e84da0312774 h1:a4tQYYYuK9QdeO/+kEvNYyuR21S+7ve5EANok6hABhI=
golang.org/x/crypto v0.0.0-20181025213731-e84da0312774/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
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-20190422183909-d864b10871cd h1:sMHc2rZHuzQmrbVoSpt9HgerkXPyIeCSO6k0zUMGfFk=
golang.org/x/crypto v0.0.0-20190422183909-d864b10871cd/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/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-20190206173232-65e2d4e15006 h1:bfLnR+k0tq5Lqt6dflRLcZiz6UaXCMt3vhYJ1l4FQ80=
golang.org/x/net v0.0.0-20190206173232-65e2d4e15006/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/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/oauth2 v0.0.0-20190402181905-9f3314589c9a h1:tImsplftrFpALCYumobsd0K86vlAs/eXGFms2txfJfA=
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/sys v0.0.0-20190312061237-fead79001313 h1:pczuHS43Cp2ktBEEmLwScxgjWsBSzdaQiKzUyf3DTTc=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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/text v0.3.1-0.20181227161524-e6919f6577db h1:6/JqlYfC1CCaLnGceQTI+sDGhC9UBSPAsBqI0Gun6kU=
golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20161028155119-f51c12702a4d h1:TnM+PKb3ylGmZvyPXmo9m/wktg7Jn/a/fNmr33HSj8g=
golang.org/x/time v0.0.0-20161028155119-f51c12702a4d/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190422233926-fe54fb35175b/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522 h1:bhOzK9QyoD0ogCnFro1m2mz41+Ib0oOhfJnBp5MR4K4=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/square/go-jose.v2 v2.3.0 h1:nLzhkFyl5bkblqYBoiWJUt5JkWOzmiaBtCxdJAqJd3U=
gopkg.in/square/go-jose.v2 v2.3.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/inf.v0 v0.9.0 h1:3zYtXIO92bvsdS3ggAdA8Gb4Azj0YU+TVY1uGYNFA8o=
gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4=
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
k8s.io/api v0.0.0-20190222213804-5cb15d344471 h1:MzQGt8qWQCR+39kbYRd0uQqsvSidpYqJLFeWiJ9l4OE=
k8s.io/api v0.0.0-20190222213804-5cb15d344471/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA=
k8s.io/apimachinery v0.0.0-20190221213512-86fb29eff628 h1:UYfHH+KEF88OTg+GojQUwFTNxbxwmoktLwutUzR0GPg=
k8s.io/apimachinery v0.0.0-20190221213512-86fb29eff628/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0=
k8s.io/client-go v10.0.0+incompatible h1:+xQQxwjrcIPWDMJBAS+1G2FNk1McoPnb53xkvcDiDqE=
k8s.io/client-go v10.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s=
k8s.io/klog v0.2.0 h1:0ElL0OHzF3N+OhoJTL0uca20SxtYt4X4+bzHeqrB83c=
k8s.io/klog v0.2.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/api v0.0.0-20190620084959-7cf5895f2711/go.mod h1:TBhBqb1AWbBQbW3XRusr7n7E4v2+5ZY8r8sAMnyFC5A=
k8s.io/apimachinery v0.0.0-20190612205821-1799e75a0719 h1:uV4S5IB5g4Nvi+TBVNf3e9L4wrirlwYJ6w88jUQxTUw=
k8s.io/apimachinery v0.0.0-20190612205821-1799e75a0719/go.mod h1:I4A+glKBHiTgiEjQiCCQfCAIcIMFGt291SmsvcrFzJA=
k8s.io/client-go v0.0.0-20190620085101-78d2af792bab h1:E8Fecph0qbNsAbijJJQryKu4Oi9QTp5cVpjTE+nqg6g=
k8s.io/client-go v0.0.0-20190620085101-78d2af792bab/go.mod h1:E95RaSlHr79aHaX0aGSwcPNfygDiPKOVXdmivCIZT0k=
k8s.io/client-go v11.0.0+incompatible h1:LBbX2+lOwY9flffWlJM7f1Ct8V2SRNiMRDFeiwnJo9o=
k8s.io/klog v0.3.1 h1:RVgyDHY/kFKtLqh67NvEWIgkMneNoIrdkN0CxDSQc68=
k8s.io/klog v0.3.1/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc=
k8s.io/utils v0.0.0-20190221042446-c2654d5206da h1:ElyM7RPonbKnQqOcw7dG2IK5uvQQn3b/WPHqD5mBvP4=
k8s.io/utils v0.0.0-20190221042446-c2654d5206da/go.mod h1:8k8uAuAQ0rXslZKaEWd0c3oVhZz7sSzSiPnVZayjIX0=
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=

View File

@@ -0,0 +1,16 @@
// Package credentialplugin provides models for the credential plugin.
package credentialplugin
import "time"
// TokenCache represents a token object cached.
type TokenCache struct {
IDToken string `json:"id_token,omitempty"`
RefreshToken string `json:"refresh_token,omitempty"`
}
// Output represents an output object of the credential plugin.
type Output struct {
Token string
Expiry time.Time
}

View File

@@ -46,7 +46,7 @@ type Authentication struct {
func (u *Authentication) Do(ctx context.Context, in usecases.AuthenticationIn) (*usecases.AuthenticationOut, error) {
client, err := u.OIDC.New(ctx, adaptors.OIDCClientConfig{
Config: in.CurrentAuthProvider.OIDCConfig,
Config: in.OIDCConfig,
CACertFilename: in.CACertFilename,
SkipTLSVerify: in.SkipTLSVerify,
})
@@ -54,18 +54,18 @@ func (u *Authentication) Do(ctx context.Context, in usecases.AuthenticationIn) (
return nil, xerrors.Errorf("could not create an OIDC client: %w", err)
}
if in.CurrentAuthProvider.OIDCConfig.IDToken != "" {
u.Logger.Debugf(1, "Verifying the token in the kubeconfig")
out, err := client.Verify(ctx, adaptors.OIDCVerifyIn{IDToken: in.CurrentAuthProvider.OIDCConfig.IDToken})
if in.OIDCConfig.IDToken != "" {
u.Logger.Debugf(1, "Verifying the existing token")
out, err := client.Verify(ctx, adaptors.OIDCVerifyIn{IDToken: in.OIDCConfig.IDToken})
if err != nil {
return nil, xerrors.Errorf("invalid ID token in the kubeconfig, you need to remove it manually: %w", err)
return nil, xerrors.Errorf("you need to remove the existing token manually: %w", err)
}
if out.IDTokenExpiry.After(time.Now()) { //TODO: inject time service
u.Logger.Debugf(1, "You already have a valid token in the kubeconfig")
u.Logger.Debugf(1, "You already have a valid token")
return &usecases.AuthenticationOut{
AlreadyHasValidIDToken: true,
IDToken: in.CurrentAuthProvider.OIDCConfig.IDToken,
RefreshToken: in.CurrentAuthProvider.OIDCConfig.RefreshToken,
IDToken: in.OIDCConfig.IDToken,
RefreshToken: in.OIDCConfig.RefreshToken,
IDTokenExpiry: out.IDTokenExpiry,
IDTokenClaims: out.IDTokenClaims,
}, nil
@@ -73,10 +73,10 @@ func (u *Authentication) Do(ctx context.Context, in usecases.AuthenticationIn) (
u.Logger.Debugf(1, "You have an expired token at %s", out.IDTokenExpiry)
}
if in.CurrentAuthProvider.OIDCConfig.RefreshToken != "" {
if in.OIDCConfig.RefreshToken != "" {
u.Logger.Debugf(1, "Refreshing the token")
out, err := client.Refresh(ctx, adaptors.OIDCRefreshIn{
RefreshToken: in.CurrentAuthProvider.OIDCConfig.RefreshToken,
RefreshToken: in.OIDCConfig.RefreshToken,
})
if err == nil {
return &usecases.AuthenticationOut{

View File

@@ -28,11 +28,9 @@ func TestAuthentication_Do(t *testing.T) {
SkipOpenBrowser: true,
CACertFilename: "/path/to/cert",
SkipTLSVerify: true,
CurrentAuthProvider: &kubeconfig.AuthProvider{
OIDCConfig: kubeconfig.OIDCConfig{
ClientID: "YOUR_CLIENT_ID",
ClientSecret: "YOUR_CLIENT_SECRET",
},
OIDCConfig: kubeconfig.OIDCConfig{
ClientID: "YOUR_CLIENT_ID",
ClientSecret: "YOUR_CLIENT_SECRET",
},
}
mockOIDCClient := mock_adaptors.NewMockOIDCClient(ctrl)
@@ -50,7 +48,7 @@ func TestAuthentication_Do(t *testing.T) {
mockOIDC := mock_adaptors.NewMockOIDC(ctrl)
mockOIDC.EXPECT().
New(ctx, adaptors.OIDCClientConfig{
Config: in.CurrentAuthProvider.OIDCConfig,
Config: in.OIDCConfig,
CACertFilename: "/path/to/cert",
SkipTLSVerify: true,
}).
@@ -83,11 +81,9 @@ func TestAuthentication_Do(t *testing.T) {
Password: "PASS",
CACertFilename: "/path/to/cert",
SkipTLSVerify: true,
CurrentAuthProvider: &kubeconfig.AuthProvider{
OIDCConfig: kubeconfig.OIDCConfig{
ClientID: "YOUR_CLIENT_ID",
ClientSecret: "YOUR_CLIENT_SECRET",
},
OIDCConfig: kubeconfig.OIDCConfig{
ClientID: "YOUR_CLIENT_ID",
ClientSecret: "YOUR_CLIENT_SECRET",
},
}
mockOIDCClient := mock_adaptors.NewMockOIDCClient(ctrl)
@@ -105,7 +101,7 @@ func TestAuthentication_Do(t *testing.T) {
mockOIDC := mock_adaptors.NewMockOIDC(ctrl)
mockOIDC.EXPECT().
New(ctx, adaptors.OIDCClientConfig{
Config: in.CurrentAuthProvider.OIDCConfig,
Config: in.OIDCConfig,
CACertFilename: "/path/to/cert",
SkipTLSVerify: true,
}).
@@ -135,11 +131,9 @@ func TestAuthentication_Do(t *testing.T) {
ctx := context.TODO()
in := usecases.AuthenticationIn{
Username: "USER",
CurrentAuthProvider: &kubeconfig.AuthProvider{
OIDCConfig: kubeconfig.OIDCConfig{
ClientID: "YOUR_CLIENT_ID",
ClientSecret: "YOUR_CLIENT_SECRET",
},
OIDCConfig: kubeconfig.OIDCConfig{
ClientID: "YOUR_CLIENT_ID",
ClientSecret: "YOUR_CLIENT_SECRET",
},
}
mockOIDCClient := mock_adaptors.NewMockOIDCClient(ctrl)
@@ -157,7 +151,7 @@ func TestAuthentication_Do(t *testing.T) {
mockOIDC := mock_adaptors.NewMockOIDC(ctrl)
mockOIDC.EXPECT().
New(ctx, adaptors.OIDCClientConfig{
Config: in.CurrentAuthProvider.OIDCConfig,
Config: in.OIDCConfig,
}).
Return(mockOIDCClient, nil)
mockEnv := mock_adaptors.NewMockEnv(ctrl)
@@ -188,17 +182,15 @@ func TestAuthentication_Do(t *testing.T) {
ctx := context.TODO()
in := usecases.AuthenticationIn{
Username: "USER",
CurrentAuthProvider: &kubeconfig.AuthProvider{
OIDCConfig: kubeconfig.OIDCConfig{
ClientID: "YOUR_CLIENT_ID",
ClientSecret: "YOUR_CLIENT_SECRET",
},
OIDCConfig: kubeconfig.OIDCConfig{
ClientID: "YOUR_CLIENT_ID",
ClientSecret: "YOUR_CLIENT_SECRET",
},
}
mockOIDC := mock_adaptors.NewMockOIDC(ctrl)
mockOIDC.EXPECT().
New(ctx, adaptors.OIDCClientConfig{
Config: in.CurrentAuthProvider.OIDCConfig,
Config: in.OIDCConfig,
}).
Return(mock_adaptors.NewMockOIDCClient(ctrl), nil)
mockEnv := mock_adaptors.NewMockEnv(ctrl)
@@ -222,12 +214,10 @@ func TestAuthentication_Do(t *testing.T) {
defer ctrl.Finish()
ctx := context.TODO()
in := usecases.AuthenticationIn{
CurrentAuthProvider: &kubeconfig.AuthProvider{
OIDCConfig: kubeconfig.OIDCConfig{
ClientID: "YOUR_CLIENT_ID",
ClientSecret: "YOUR_CLIENT_SECRET",
IDToken: "VALID_ID_TOKEN",
},
OIDCConfig: kubeconfig.OIDCConfig{
ClientID: "YOUR_CLIENT_ID",
ClientSecret: "YOUR_CLIENT_SECRET",
IDToken: "VALID_ID_TOKEN",
},
}
mockOIDCClient := mock_adaptors.NewMockOIDCClient(ctrl)
@@ -240,7 +230,7 @@ func TestAuthentication_Do(t *testing.T) {
mockOIDC := mock_adaptors.NewMockOIDC(ctrl)
mockOIDC.EXPECT().
New(ctx, adaptors.OIDCClientConfig{
Config: in.CurrentAuthProvider.OIDCConfig,
Config: in.OIDCConfig,
}).
Return(mockOIDCClient, nil)
u := Authentication{
@@ -267,13 +257,11 @@ func TestAuthentication_Do(t *testing.T) {
defer ctrl.Finish()
ctx := context.TODO()
in := usecases.AuthenticationIn{
CurrentAuthProvider: &kubeconfig.AuthProvider{
OIDCConfig: kubeconfig.OIDCConfig{
ClientID: "YOUR_CLIENT_ID",
ClientSecret: "YOUR_CLIENT_SECRET",
IDToken: "EXPIRED_ID_TOKEN",
RefreshToken: "VALID_REFRESH_TOKEN",
},
OIDCConfig: kubeconfig.OIDCConfig{
ClientID: "YOUR_CLIENT_ID",
ClientSecret: "YOUR_CLIENT_SECRET",
IDToken: "EXPIRED_ID_TOKEN",
RefreshToken: "VALID_REFRESH_TOKEN",
},
}
mockOIDCClient := mock_adaptors.NewMockOIDCClient(ctrl)
@@ -296,7 +284,7 @@ func TestAuthentication_Do(t *testing.T) {
mockOIDC := mock_adaptors.NewMockOIDC(ctrl)
mockOIDC.EXPECT().
New(ctx, adaptors.OIDCClientConfig{
Config: in.CurrentAuthProvider.OIDCConfig,
Config: in.OIDCConfig,
}).
Return(mockOIDCClient, nil)
u := Authentication{
@@ -324,13 +312,11 @@ func TestAuthentication_Do(t *testing.T) {
ctx := context.TODO()
in := usecases.AuthenticationIn{
ListenPort: []int{10000},
CurrentAuthProvider: &kubeconfig.AuthProvider{
OIDCConfig: kubeconfig.OIDCConfig{
ClientID: "YOUR_CLIENT_ID",
ClientSecret: "YOUR_CLIENT_SECRET",
IDToken: "EXPIRED_ID_TOKEN",
RefreshToken: "EXPIRED_REFRESH_TOKEN",
},
OIDCConfig: kubeconfig.OIDCConfig{
ClientID: "YOUR_CLIENT_ID",
ClientSecret: "YOUR_CLIENT_SECRET",
IDToken: "EXPIRED_ID_TOKEN",
RefreshToken: "EXPIRED_REFRESH_TOKEN",
},
}
mockOIDCClient := mock_adaptors.NewMockOIDCClient(ctrl)
@@ -358,7 +344,7 @@ func TestAuthentication_Do(t *testing.T) {
mockOIDC := mock_adaptors.NewMockOIDC(ctrl)
mockOIDC.EXPECT().
New(ctx, adaptors.OIDCClientConfig{
Config: in.CurrentAuthProvider.OIDCConfig,
Config: in.OIDCConfig,
}).
Return(mockOIDCClient, nil)
u := Authentication{

View File

@@ -0,0 +1,73 @@
// Package credentialplugin provides the use-cases for running as a client-go credentials plugin.
//
// See https://kubernetes.io/docs/reference/access-authn-authz/authentication/#client-go-credential-plugins
package credentialplugin
import (
"context"
"github.com/google/wire"
"github.com/int128/kubelogin/adaptors"
"github.com/int128/kubelogin/models/credentialplugin"
"github.com/int128/kubelogin/models/kubeconfig"
"github.com/int128/kubelogin/usecases"
"golang.org/x/xerrors"
)
var Set = wire.NewSet(
wire.Struct(new(GetToken), "*"),
wire.Bind(new(usecases.GetToken), new(*GetToken)),
)
type GetToken struct {
Authentication usecases.Authentication
TokenCacheRepository adaptors.TokenCacheRepository
Interaction adaptors.CredentialPluginInteraction
Logger adaptors.Logger
}
func (u *GetToken) Do(ctx context.Context, in usecases.GetTokenIn) error {
u.Logger.Debugf(1, "WARNING: log may contain your secrets such as token or password")
tokenCache, err := u.TokenCacheRepository.Read(in.TokenCacheFilename)
if err != nil {
u.Logger.Debugf(1, "could not read the token cache file: %s", err)
tokenCache = &credentialplugin.TokenCache{}
}
out, err := u.Authentication.Do(ctx, usecases.AuthenticationIn{
OIDCConfig: kubeconfig.OIDCConfig{
IDPIssuerURL: in.IssuerURL,
ClientID: in.ClientID,
ClientSecret: in.ClientSecret,
ExtraScopes: in.ExtraScopes,
IDToken: tokenCache.IDToken,
RefreshToken: tokenCache.RefreshToken,
},
SkipOpenBrowser: in.SkipOpenBrowser,
ListenPort: in.ListenPort,
Username: in.Username,
Password: in.Password,
CACertFilename: in.CACertFilename,
SkipTLSVerify: in.SkipTLSVerify,
})
if err != nil {
return xerrors.Errorf("error while authentication: %w", err)
}
for k, v := range out.IDTokenClaims {
u.Logger.Debugf(1, "ID token has the claim: %s=%v", k, v)
}
if !out.AlreadyHasValidIDToken {
u.Logger.Printf("You got a valid token until %s", out.IDTokenExpiry)
if err := u.TokenCacheRepository.Write(in.TokenCacheFilename, credentialplugin.TokenCache{
IDToken: out.IDToken,
RefreshToken: out.RefreshToken,
}); err != nil {
return xerrors.Errorf("could not write the token cache: %w", err)
}
}
if err := u.Interaction.Write(credentialplugin.Output{Token: out.IDToken, Expiry: out.IDTokenExpiry}); err != nil {
return xerrors.Errorf("could not write a credential object: %w", err)
}
return nil
}

View File

@@ -0,0 +1,167 @@
package credentialplugin
import (
"context"
"testing"
"time"
"github.com/golang/mock/gomock"
"github.com/int128/kubelogin/adaptors/mock_adaptors"
"github.com/int128/kubelogin/models/credentialplugin"
"github.com/int128/kubelogin/models/kubeconfig"
"github.com/int128/kubelogin/usecases"
"github.com/int128/kubelogin/usecases/mock_usecases"
"golang.org/x/xerrors"
)
func TestGetToken_Do(t *testing.T) {
dummyTokenClaims := map[string]string{"sub": "YOUR_SUBJECT"}
futureTime := time.Now().Add(time.Hour) //TODO: inject time service
t.Run("FullOptions", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.TODO()
in := usecases.GetTokenIn{
IssuerURL: "https://accounts.google.com",
ClientID: "YOUR_CLIENT_ID",
ClientSecret: "YOUR_CLIENT_SECRET",
TokenCacheFilename: "/path/to/token-cache",
ListenPort: []int{10000},
SkipOpenBrowser: true,
Username: "USER",
Password: "PASS",
CACertFilename: "/path/to/cert",
SkipTLSVerify: true,
}
mockAuthentication := mock_usecases.NewMockAuthentication(ctrl)
mockAuthentication.EXPECT().
Do(ctx, usecases.AuthenticationIn{
OIDCConfig: kubeconfig.OIDCConfig{
IDPIssuerURL: "https://accounts.google.com",
ClientID: "YOUR_CLIENT_ID",
ClientSecret: "YOUR_CLIENT_SECRET",
},
ListenPort: []int{10000},
SkipOpenBrowser: true,
Username: "USER",
Password: "PASS",
CACertFilename: "/path/to/cert",
SkipTLSVerify: true,
}).
Return(&usecases.AuthenticationOut{
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
IDTokenExpiry: futureTime,
IDTokenClaims: dummyTokenClaims,
}, nil)
tokenCacheRepository := mock_adaptors.NewMockTokenCacheRepository(ctrl)
tokenCacheRepository.EXPECT().
Read("/path/to/token-cache").
Return(nil, xerrors.New("file not found"))
tokenCacheRepository.EXPECT().
Write("/path/to/token-cache", credentialplugin.TokenCache{
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
})
credentialPluginInteraction := mock_adaptors.NewMockCredentialPluginInteraction(ctrl)
credentialPluginInteraction.EXPECT().
Write(credentialplugin.Output{
Token: "YOUR_ID_TOKEN",
Expiry: futureTime,
})
u := GetToken{
Authentication: mockAuthentication,
TokenCacheRepository: tokenCacheRepository,
Interaction: credentialPluginInteraction,
Logger: mock_adaptors.NewLogger(t, ctrl),
}
if err := u.Do(ctx, in); err != nil {
t.Errorf("Do returned error: %+v", err)
}
})
t.Run("HasValidIDToken", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.TODO()
in := usecases.GetTokenIn{
IssuerURL: "https://accounts.google.com",
ClientID: "YOUR_CLIENT_ID",
ClientSecret: "YOUR_CLIENT_SECRET",
TokenCacheFilename: "/path/to/token-cache",
}
mockAuthentication := mock_usecases.NewMockAuthentication(ctrl)
mockAuthentication.EXPECT().
Do(ctx, usecases.AuthenticationIn{
OIDCConfig: kubeconfig.OIDCConfig{
IDPIssuerURL: "https://accounts.google.com",
ClientID: "YOUR_CLIENT_ID",
ClientSecret: "YOUR_CLIENT_SECRET",
IDToken: "VALID_ID_TOKEN",
},
}).
Return(&usecases.AuthenticationOut{
AlreadyHasValidIDToken: true,
IDToken: "VALID_ID_TOKEN",
IDTokenExpiry: futureTime,
IDTokenClaims: dummyTokenClaims,
}, nil)
tokenCacheRepository := mock_adaptors.NewMockTokenCacheRepository(ctrl)
tokenCacheRepository.EXPECT().
Read("/path/to/token-cache").
Return(&credentialplugin.TokenCache{
IDToken: "VALID_ID_TOKEN",
}, nil)
credentialPluginInteraction := mock_adaptors.NewMockCredentialPluginInteraction(ctrl)
credentialPluginInteraction.EXPECT().
Write(credentialplugin.Output{
Token: "VALID_ID_TOKEN",
Expiry: futureTime,
})
u := GetToken{
Authentication: mockAuthentication,
TokenCacheRepository: tokenCacheRepository,
Interaction: credentialPluginInteraction,
Logger: mock_adaptors.NewLogger(t, ctrl),
}
if err := u.Do(ctx, in); err != nil {
t.Errorf("Do returned error: %+v", err)
}
})
t.Run("AuthenticationError", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.TODO()
in := usecases.GetTokenIn{
IssuerURL: "https://accounts.google.com",
ClientID: "YOUR_CLIENT_ID",
ClientSecret: "YOUR_CLIENT_SECRET",
TokenCacheFilename: "/path/to/token-cache",
}
mockAuthentication := mock_usecases.NewMockAuthentication(ctrl)
mockAuthentication.EXPECT().
Do(ctx, usecases.AuthenticationIn{
OIDCConfig: kubeconfig.OIDCConfig{
IDPIssuerURL: "https://accounts.google.com",
ClientID: "YOUR_CLIENT_ID",
ClientSecret: "YOUR_CLIENT_SECRET",
},
}).
Return(nil, xerrors.New("authentication error"))
tokenCacheRepository := mock_adaptors.NewMockTokenCacheRepository(ctrl)
tokenCacheRepository.EXPECT().
Read("/path/to/token-cache").
Return(nil, xerrors.New("file not found"))
u := GetToken{
Authentication: mockAuthentication,
TokenCacheRepository: tokenCacheRepository,
Interaction: mock_adaptors.NewMockCredentialPluginInteraction(ctrl),
Logger: mock_adaptors.NewLogger(t, ctrl),
}
if err := u.Do(ctx, in); err == nil {
t.Errorf("err wants non-nil but nil")
}
})
}

View File

@@ -7,7 +7,7 @@ import (
"github.com/int128/kubelogin/models/kubeconfig"
)
//go:generate mockgen -destination mock_usecases/mock_usecases.go github.com/int128/kubelogin/usecases Login,LoginAndExec,Authentication
//go:generate mockgen -destination mock_usecases/mock_usecases.go github.com/int128/kubelogin/usecases Login,LoginAndExec,GetToken,Authentication
type Login interface {
Do(ctx context.Context, in LoginIn) error
@@ -32,6 +32,25 @@ type LoginShowLocalServerURL interface {
ShowLocalServerURL(url string)
}
type GetToken interface {
Do(ctx context.Context, in GetTokenIn) error
}
// GetTokenIn represents an input DTO of the GetToken use-case.
type GetTokenIn struct {
IssuerURL string
ClientID string
ClientSecret string
ExtraScopes []string // optional
SkipOpenBrowser bool
ListenPort []int
Username string // If set, perform the resource owner password credentials grant
Password string // If empty, read a password using Env.ReadPassword()
CACertFilename string // If set, use the CA cert
SkipTLSVerify bool
TokenCacheFilename string
}
type LoginAndExec interface {
Do(ctx context.Context, in LoginAndExecIn) (*LoginAndExecOut, error)
}
@@ -53,13 +72,13 @@ type Authentication interface {
// AuthenticationIn represents an input DTO of the Authentication use-case.
type AuthenticationIn struct {
CurrentAuthProvider *kubeconfig.AuthProvider
SkipOpenBrowser bool
ListenPort []int
Username string // If set, perform the resource owner password credentials grant
Password string // If empty, read a password using Env.ReadPassword()
CACertFilename string // If set, use the CA cert
SkipTLSVerify bool
OIDCConfig kubeconfig.OIDCConfig
SkipOpenBrowser bool
ListenPort []int
Username string // If set, perform the resource owner password credentials grant
Password string // If empty, read a password using Env.ReadPassword()
CACertFilename string // If set, use the CA cert
SkipTLSVerify bool
}
// AuthenticationIn represents an output DTO of the Authentication use-case.

View File

@@ -46,13 +46,13 @@ func (u *Exec) login(ctx context.Context, in usecases.LoginIn) error {
u.Logger.Debugf(1, "A token will be written to %s", authProvider.LocationOfOrigin)
out, err := u.Authentication.Do(ctx, usecases.AuthenticationIn{
CurrentAuthProvider: authProvider,
SkipOpenBrowser: in.SkipOpenBrowser,
ListenPort: in.ListenPort,
Username: in.Username,
Password: in.Password,
CACertFilename: in.CACertFilename,
SkipTLSVerify: in.SkipTLSVerify,
OIDCConfig: authProvider.OIDCConfig,
SkipOpenBrowser: in.SkipOpenBrowser,
ListenPort: in.ListenPort,
Username: in.Username,
Password: in.Password,
CACertFilename: in.CACertFilename,
SkipTLSVerify: in.SkipTLSVerify,
})
if err != nil {
return xerrors.Errorf("error while authentication: %w", err)

View File

@@ -69,13 +69,13 @@ func TestExec_Do(t *testing.T) {
mockAuthentication := mock_usecases.NewMockAuthentication(ctrl)
mockAuthentication.EXPECT().
Do(ctx, usecases.AuthenticationIn{
CurrentAuthProvider: currentAuthProvider,
ListenPort: []int{10000},
SkipOpenBrowser: true,
Username: "USER",
Password: "PASS",
CACertFilename: "/path/to/cert",
SkipTLSVerify: true,
OIDCConfig: currentAuthProvider.OIDCConfig,
ListenPort: []int{10000},
SkipOpenBrowser: true,
Username: "USER",
Password: "PASS",
CACertFilename: "/path/to/cert",
SkipTLSVerify: true,
}).
Return(&usecases.AuthenticationOut{
IDToken: "YOUR_ID_TOKEN",
@@ -128,7 +128,7 @@ func TestExec_Do(t *testing.T) {
Return(currentAuthProvider, nil)
mockAuthentication := mock_usecases.NewMockAuthentication(ctrl)
mockAuthentication.EXPECT().
Do(ctx, usecases.AuthenticationIn{CurrentAuthProvider: currentAuthProvider}).
Do(ctx, usecases.AuthenticationIn{OIDCConfig: currentAuthProvider.OIDCConfig}).
Return(&usecases.AuthenticationOut{
AlreadyHasValidIDToken: true,
IDToken: "VALID_ID_TOKEN",
@@ -209,7 +209,7 @@ func TestExec_Do(t *testing.T) {
Return(currentAuthProvider, nil)
mockAuthentication := mock_usecases.NewMockAuthentication(ctrl)
mockAuthentication.EXPECT().
Do(ctx, usecases.AuthenticationIn{CurrentAuthProvider: currentAuthProvider}).
Do(ctx, usecases.AuthenticationIn{OIDCConfig: currentAuthProvider.OIDCConfig}).
Return(nil, xerrors.New("authentication error"))
u := Exec{
Authentication: mockAuthentication,
@@ -259,7 +259,7 @@ func TestExec_Do(t *testing.T) {
Return(xerrors.New("I/O error"))
mockAuthentication := mock_usecases.NewMockAuthentication(ctrl)
mockAuthentication.EXPECT().
Do(ctx, usecases.AuthenticationIn{CurrentAuthProvider: currentAuthProvider}).
Do(ctx, usecases.AuthenticationIn{OIDCConfig: currentAuthProvider.OIDCConfig}).
Return(&usecases.AuthenticationOut{
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",

View File

@@ -48,13 +48,13 @@ func (u *Login) Do(ctx context.Context, in usecases.LoginIn) error {
u.Logger.Debugf(1, "A token will be written to %s", authProvider.LocationOfOrigin)
out, err := u.Authentication.Do(ctx, usecases.AuthenticationIn{
CurrentAuthProvider: authProvider,
SkipOpenBrowser: in.SkipOpenBrowser,
ListenPort: in.ListenPort,
Username: in.Username,
Password: in.Password,
CACertFilename: in.CACertFilename,
SkipTLSVerify: in.SkipTLSVerify,
OIDCConfig: authProvider.OIDCConfig,
SkipOpenBrowser: in.SkipOpenBrowser,
ListenPort: in.ListenPort,
Username: in.Username,
Password: in.Password,
CACertFilename: in.CACertFilename,
SkipTLSVerify: in.SkipTLSVerify,
})
if err != nil {
return xerrors.Errorf("error while authentication: %w", err)

View File

@@ -60,13 +60,13 @@ func TestLogin_Do(t *testing.T) {
mockAuthentication := mock_usecases.NewMockAuthentication(ctrl)
mockAuthentication.EXPECT().
Do(ctx, usecases.AuthenticationIn{
CurrentAuthProvider: currentAuthProvider,
ListenPort: []int{10000},
SkipOpenBrowser: true,
Username: "USER",
Password: "PASS",
CACertFilename: "/path/to/cert",
SkipTLSVerify: true,
OIDCConfig: currentAuthProvider.OIDCConfig,
ListenPort: []int{10000},
SkipOpenBrowser: true,
Username: "USER",
Password: "PASS",
CACertFilename: "/path/to/cert",
SkipTLSVerify: true,
}).
Return(&usecases.AuthenticationOut{
IDToken: "YOUR_ID_TOKEN",
@@ -104,7 +104,7 @@ func TestLogin_Do(t *testing.T) {
Return(currentAuthProvider, nil)
mockAuthentication := mock_usecases.NewMockAuthentication(ctrl)
mockAuthentication.EXPECT().
Do(ctx, usecases.AuthenticationIn{CurrentAuthProvider: currentAuthProvider}).
Do(ctx, usecases.AuthenticationIn{OIDCConfig: currentAuthProvider.OIDCConfig}).
Return(&usecases.AuthenticationOut{
AlreadyHasValidIDToken: true,
IDToken: "VALID_ID_TOKEN",
@@ -161,7 +161,7 @@ func TestLogin_Do(t *testing.T) {
Return(currentAuthProvider, nil)
mockAuthentication := mock_usecases.NewMockAuthentication(ctrl)
mockAuthentication.EXPECT().
Do(ctx, usecases.AuthenticationIn{CurrentAuthProvider: currentAuthProvider}).
Do(ctx, usecases.AuthenticationIn{OIDCConfig: currentAuthProvider.OIDCConfig}).
Return(nil, xerrors.New("authentication error"))
u := Login{
Authentication: mockAuthentication,
@@ -206,7 +206,7 @@ func TestLogin_Do(t *testing.T) {
Return(xerrors.New("I/O error"))
mockAuthentication := mock_usecases.NewMockAuthentication(ctrl)
mockAuthentication.EXPECT().
Do(ctx, usecases.AuthenticationIn{CurrentAuthProvider: currentAuthProvider}).
Do(ctx, usecases.AuthenticationIn{OIDCConfig: currentAuthProvider.OIDCConfig}).
Return(&usecases.AuthenticationOut{
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",

View File

@@ -1,5 +1,5 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/int128/kubelogin/usecases (interfaces: Login,LoginAndExec,Authentication)
// Source: github.com/int128/kubelogin/usecases (interfaces: Login,LoginAndExec,GetToken,Authentication)
// Package mock_usecases is a generated GoMock package.
package mock_usecases
@@ -82,6 +82,41 @@ func (mr *MockLoginAndExecMockRecorder) Do(arg0, arg1 interface{}) *gomock.Call
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Do", reflect.TypeOf((*MockLoginAndExec)(nil).Do), arg0, arg1)
}
// MockGetToken is a mock of GetToken interface
type MockGetToken struct {
ctrl *gomock.Controller
recorder *MockGetTokenMockRecorder
}
// MockGetTokenMockRecorder is the mock recorder for MockGetToken
type MockGetTokenMockRecorder struct {
mock *MockGetToken
}
// NewMockGetToken creates a new mock instance
func NewMockGetToken(ctrl *gomock.Controller) *MockGetToken {
mock := &MockGetToken{ctrl: ctrl}
mock.recorder = &MockGetTokenMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockGetToken) EXPECT() *MockGetTokenMockRecorder {
return m.recorder
}
// Do mocks base method
func (m *MockGetToken) Do(arg0 context.Context, arg1 usecases.GetTokenIn) error {
ret := m.ctrl.Call(m, "Do", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// Do indicates an expected call of Do
func (mr *MockGetTokenMockRecorder) Do(arg0, arg1 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Do", reflect.TypeOf((*MockGetToken)(nil).Do), arg0, arg1)
}
// MockAuthentication is a mock of Authentication interface
type MockAuthentication struct {
ctrl *gomock.Controller