mirror of
https://github.com/int128/kubelogin.git
synced 2026-02-14 16:39:51 +00:00
Refactor setup command and docs (#1253)
* Refactor setup command and docs * Fix slice flags * Fix
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/int128/kubelogin/mocks/github.com/int128/kubelogin/pkg/usecases/credentialplugin_mock"
|
||||
"github.com/int128/kubelogin/mocks/github.com/int128/kubelogin/pkg/usecases/setup_mock"
|
||||
"github.com/int128/kubelogin/mocks/github.com/int128/kubelogin/pkg/usecases/standalone_mock"
|
||||
"github.com/int128/kubelogin/pkg/oidc"
|
||||
"github.com/int128/kubelogin/pkg/testing/logger"
|
||||
@@ -16,6 +17,7 @@ import (
|
||||
"github.com/int128/kubelogin/pkg/usecases/authentication"
|
||||
"github.com/int128/kubelogin/pkg/usecases/authentication/authcode"
|
||||
"github.com/int128/kubelogin/pkg/usecases/credentialplugin"
|
||||
"github.com/int128/kubelogin/pkg/usecases/setup"
|
||||
"github.com/int128/kubelogin/pkg/usecases/standalone"
|
||||
)
|
||||
|
||||
@@ -23,6 +25,14 @@ func TestCmd_Run(t *testing.T) {
|
||||
const executable = "kubelogin"
|
||||
const version = "HEAD"
|
||||
|
||||
defaultGrantOptionSet := authentication.GrantOptionSet{
|
||||
AuthCodeBrowserOption: &authcode.BrowserOption{
|
||||
BindAddress: defaultListenAddress,
|
||||
AuthenticationTimeout: defaultAuthenticationTimeoutSec * time.Second,
|
||||
RedirectURLHostname: "localhost",
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("root", func(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
args []string
|
||||
@@ -31,13 +41,7 @@ func TestCmd_Run(t *testing.T) {
|
||||
"Defaults": {
|
||||
args: []string{executable},
|
||||
in: standalone.Input{
|
||||
GrantOptionSet: authentication.GrantOptionSet{
|
||||
AuthCodeBrowserOption: &authcode.BrowserOption{
|
||||
BindAddress: defaultListenAddress,
|
||||
AuthenticationTimeout: defaultAuthenticationTimeoutSec * time.Second,
|
||||
RedirectURLHostname: "localhost",
|
||||
},
|
||||
},
|
||||
GrantOptionSet: defaultGrantOptionSet,
|
||||
},
|
||||
},
|
||||
"FullOptions": {
|
||||
@@ -51,13 +55,7 @@ func TestCmd_Run(t *testing.T) {
|
||||
KubeconfigFilename: "/path/to/kubeconfig",
|
||||
KubeconfigContext: "hello.k8s.local",
|
||||
KubeconfigUser: "google",
|
||||
GrantOptionSet: authentication.GrantOptionSet{
|
||||
AuthCodeBrowserOption: &authcode.BrowserOption{
|
||||
BindAddress: defaultListenAddress,
|
||||
AuthenticationTimeout: defaultAuthenticationTimeoutSec * time.Second,
|
||||
RedirectURLHostname: "localhost",
|
||||
},
|
||||
},
|
||||
GrantOptionSet: defaultGrantOptionSet,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -122,13 +120,7 @@ func TestCmd_Run(t *testing.T) {
|
||||
Directory: filepath.Join(userHomeDir, ".kube/cache/oidc-login"),
|
||||
Storage: tokencache.StorageAuto,
|
||||
},
|
||||
GrantOptionSet: authentication.GrantOptionSet{
|
||||
AuthCodeBrowserOption: &authcode.BrowserOption{
|
||||
BindAddress: defaultListenAddress,
|
||||
AuthenticationTimeout: defaultAuthenticationTimeoutSec * time.Second,
|
||||
RedirectURLHostname: "localhost",
|
||||
},
|
||||
},
|
||||
GrantOptionSet: defaultGrantOptionSet,
|
||||
},
|
||||
},
|
||||
"FullOptions": {
|
||||
@@ -153,13 +145,7 @@ func TestCmd_Run(t *testing.T) {
|
||||
Directory: filepath.Join(userHomeDir, ".kube/cache/oidc-login"),
|
||||
Storage: tokencache.StorageDisk,
|
||||
},
|
||||
GrantOptionSet: authentication.GrantOptionSet{
|
||||
AuthCodeBrowserOption: &authcode.BrowserOption{
|
||||
BindAddress: defaultListenAddress,
|
||||
AuthenticationTimeout: defaultAuthenticationTimeoutSec * time.Second,
|
||||
RedirectURLHostname: "localhost",
|
||||
},
|
||||
},
|
||||
GrantOptionSet: defaultGrantOptionSet,
|
||||
},
|
||||
},
|
||||
"AccessToken": {
|
||||
@@ -179,13 +165,7 @@ func TestCmd_Run(t *testing.T) {
|
||||
Directory: filepath.Join(userHomeDir, ".kube/cache/oidc-login"),
|
||||
Storage: tokencache.StorageAuto,
|
||||
},
|
||||
GrantOptionSet: authentication.GrantOptionSet{
|
||||
AuthCodeBrowserOption: &authcode.BrowserOption{
|
||||
BindAddress: defaultListenAddress,
|
||||
AuthenticationTimeout: defaultAuthenticationTimeoutSec * time.Second,
|
||||
RedirectURLHostname: "localhost",
|
||||
},
|
||||
},
|
||||
GrantOptionSet: defaultGrantOptionSet,
|
||||
},
|
||||
},
|
||||
"HomedirExpansion": {
|
||||
@@ -282,4 +262,54 @@ func TestCmd_Run(t *testing.T) {
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("setup", func(t *testing.T) {
|
||||
t.Run("NoOption", func(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
cmd := Cmd{
|
||||
Logger: logger.New(t),
|
||||
Root: &Root{
|
||||
Logger: logger.New(t),
|
||||
},
|
||||
}
|
||||
exitCode := cmd.Run(ctx, []string{executable, "setup"}, version)
|
||||
if exitCode != 0 {
|
||||
t.Errorf("exitCode wants 0 but %d", exitCode)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("WithOptions", func(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
setupMock := setup_mock.NewMockInterface(t)
|
||||
setupMock.EXPECT().Do(ctx, setup.Input{
|
||||
IssuerURL: "https://issuer.example.com",
|
||||
ClientID: "YOUR_CLIENT",
|
||||
ExtraScopes: []string{"email", "profile"},
|
||||
GrantOptionSet: defaultGrantOptionSet,
|
||||
ChangedFlags: []string{
|
||||
"--oidc-issuer-url=https://issuer.example.com",
|
||||
"--oidc-client-id=YOUR_CLIENT",
|
||||
"--oidc-extra-scope=email",
|
||||
"--oidc-extra-scope=profile",
|
||||
},
|
||||
}).Return(nil)
|
||||
cmd := Cmd{
|
||||
Logger: logger.New(t),
|
||||
Root: &Root{
|
||||
Logger: logger.New(t),
|
||||
},
|
||||
Setup: &Setup{
|
||||
Setup: setupMock,
|
||||
},
|
||||
}
|
||||
exitCode := cmd.Run(ctx, []string{executable, "setup",
|
||||
"--oidc-issuer-url", "https://issuer.example.com",
|
||||
"--oidc-client-id", "YOUR_CLIENT",
|
||||
"--oidc-extra-scope", "email,profile",
|
||||
}, version)
|
||||
if exitCode != 0 {
|
||||
t.Errorf("exitCode wants 0 but %d", exitCode)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package cmd
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
_ "embed"
|
||||
|
||||
"github.com/int128/kubelogin/pkg/usecases/setup"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
@@ -35,13 +37,31 @@ type Setup struct {
|
||||
Setup setup.Interface
|
||||
}
|
||||
|
||||
//go:embed setup.md
|
||||
var setupLongDescription string
|
||||
|
||||
func (cmd *Setup) New() *cobra.Command {
|
||||
var o setupOptions
|
||||
c := &cobra.Command{
|
||||
Use: "setup",
|
||||
Short: "Show the setup instruction",
|
||||
Long: setupLongDescription,
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(c *cobra.Command, _ []string) error {
|
||||
var changedFlags []string
|
||||
c.Flags().VisitAll(func(f *pflag.Flag) {
|
||||
if !f.Changed {
|
||||
return
|
||||
}
|
||||
if sliceValue, ok := f.Value.(pflag.SliceValue); ok {
|
||||
for _, v := range sliceValue.GetSlice() {
|
||||
changedFlags = append(changedFlags, fmt.Sprintf("--%s=%s", f.Name, v))
|
||||
}
|
||||
return
|
||||
}
|
||||
changedFlags = append(changedFlags, fmt.Sprintf("--%s=%s", f.Name, f.Value))
|
||||
})
|
||||
|
||||
grantOptionSet, err := o.authenticationOptions.grantOptionSet()
|
||||
if err != nil {
|
||||
return fmt.Errorf("setup: %w", err)
|
||||
@@ -50,7 +70,7 @@ func (cmd *Setup) New() *cobra.Command {
|
||||
if err != nil {
|
||||
return fmt.Errorf("setup: %w", err)
|
||||
}
|
||||
in := setup.Stage2Input{
|
||||
in := setup.Input{
|
||||
IssuerURL: o.IssuerURL,
|
||||
ClientID: o.ClientID,
|
||||
ClientSecret: o.ClientSecret,
|
||||
@@ -59,18 +79,12 @@ func (cmd *Setup) New() *cobra.Command {
|
||||
PKCEMethod: pkceMethod,
|
||||
GrantOptionSet: grantOptionSet,
|
||||
TLSClientConfig: o.tlsOptions.tlsClientConfig(),
|
||||
}
|
||||
if c.Flags().Lookup("listen-address").Changed {
|
||||
in.ListenAddressArgs = o.authenticationOptions.ListenAddress
|
||||
}
|
||||
if c.Flags().Lookup("oidc-pkce-method").Changed {
|
||||
in.PKCEMethodArg = o.pkceOptions.PKCEMethod
|
||||
ChangedFlags: changedFlags,
|
||||
}
|
||||
if in.IssuerURL == "" || in.ClientID == "" {
|
||||
cmd.Setup.DoStage1()
|
||||
return nil
|
||||
return c.Help()
|
||||
}
|
||||
if err := cmd.Setup.DoStage2(c.Context(), in); err != nil {
|
||||
if err := cmd.Setup.Do(c.Context(), in); err != nil {
|
||||
return fmt.Errorf("setup: %w", err)
|
||||
}
|
||||
return nil
|
||||
|
||||
12
pkg/cmd/setup.md
Normal file
12
pkg/cmd/setup.md
Normal file
@@ -0,0 +1,12 @@
|
||||
This setup shows the instruction of Kubernetes OpenID Connect authentication.
|
||||
|
||||
You need to set up the OpenID Connect Provider.
|
||||
Run the following command to authenticate with the OpenID Connect Provider:
|
||||
|
||||
```
|
||||
kubectl oidc-login setup \
|
||||
--oidc-issuer-url=ISSUER_URL \
|
||||
--oidc-client-id=YOUR_CLIENT_ID
|
||||
```
|
||||
|
||||
See https://github.com/int128/kubelogin for the details.
|
||||
@@ -3,9 +3,17 @@ package setup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
_ "embed"
|
||||
|
||||
"github.com/google/wire"
|
||||
"github.com/int128/kubelogin/pkg/infrastructure/logger"
|
||||
"github.com/int128/kubelogin/pkg/oidc"
|
||||
"github.com/int128/kubelogin/pkg/tlsclientconfig"
|
||||
"github.com/int128/kubelogin/pkg/usecases/authentication"
|
||||
)
|
||||
|
||||
@@ -15,11 +23,62 @@ var Set = wire.NewSet(
|
||||
)
|
||||
|
||||
type Interface interface {
|
||||
DoStage1()
|
||||
DoStage2(ctx context.Context, in Stage2Input) error
|
||||
Do(ctx context.Context, in Input) error
|
||||
}
|
||||
|
||||
type Setup struct {
|
||||
Authentication authentication.Interface
|
||||
Logger logger.Interface
|
||||
}
|
||||
|
||||
//go:embed setup.md
|
||||
var setupMarkdown string
|
||||
|
||||
var setupTemplate = template.Must(template.New("setup.md").Funcs(template.FuncMap{
|
||||
"quote": strconv.Quote,
|
||||
}).Parse(setupMarkdown))
|
||||
|
||||
type Input struct {
|
||||
IssuerURL string
|
||||
ClientID string
|
||||
ClientSecret string
|
||||
ExtraScopes []string
|
||||
UseAccessToken bool
|
||||
PKCEMethod oidc.PKCEMethod
|
||||
GrantOptionSet authentication.GrantOptionSet
|
||||
TLSClientConfig tlsclientconfig.Config
|
||||
ChangedFlags []string
|
||||
}
|
||||
|
||||
func (u Setup) Do(ctx context.Context, in Input) error {
|
||||
u.Logger.Printf("Authentication in progress...")
|
||||
out, err := u.Authentication.Do(ctx, authentication.Input{
|
||||
Provider: oidc.Provider{
|
||||
IssuerURL: in.IssuerURL,
|
||||
ClientID: in.ClientID,
|
||||
ClientSecret: in.ClientSecret,
|
||||
ExtraScopes: in.ExtraScopes,
|
||||
PKCEMethod: in.PKCEMethod,
|
||||
UseAccessToken: in.UseAccessToken,
|
||||
},
|
||||
GrantOptionSet: in.GrantOptionSet,
|
||||
TLSClientConfig: in.TLSClientConfig,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("authentication error: %w", err)
|
||||
}
|
||||
idTokenClaims, err := out.TokenSet.DecodeWithoutVerify()
|
||||
if err != nil {
|
||||
return fmt.Errorf("you got an invalid token: %w", err)
|
||||
}
|
||||
|
||||
var b strings.Builder
|
||||
if err := setupTemplate.Execute(&b, map[string]any{
|
||||
"IDTokenPrettyJSON": idTokenClaims.Pretty,
|
||||
"Flags": in.ChangedFlags,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("render the template: %w", err)
|
||||
}
|
||||
u.Logger.Printf(b.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
24
pkg/usecases/setup/setup.md
Normal file
24
pkg/usecases/setup/setup.md
Normal file
@@ -0,0 +1,24 @@
|
||||
## Authenticated with the OpenID Connect Provider
|
||||
|
||||
You got the token with the following claims:
|
||||
|
||||
```
|
||||
{{ .IDTokenPrettyJSON }}
|
||||
```
|
||||
|
||||
## Set up the kubeconfig
|
||||
|
||||
You can run the following command to set up the kubeconfig:
|
||||
|
||||
```
|
||||
kubectl config set-credentials oidc \
|
||||
--exec-api-version=client.authentication.k8s.io/v1 \
|
||||
--exec-interactive-mode=Never \
|
||||
--exec-command=kubectl \
|
||||
--exec-arg=oidc-login \
|
||||
--exec-arg=get-token \
|
||||
{{- range $index, $flag := .Flags }}
|
||||
{{- if $index}} \{{end}}
|
||||
--exec-arg={{ $flag | quote }}
|
||||
{{- end }}
|
||||
```
|
||||
66
pkg/usecases/setup/setup_test.go
Normal file
66
pkg/usecases/setup/setup_test.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package setup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/int128/kubelogin/mocks/github.com/int128/kubelogin/pkg/usecases/authentication_mock"
|
||||
"github.com/int128/kubelogin/pkg/oidc"
|
||||
testingJWT "github.com/int128/kubelogin/pkg/testing/jwt"
|
||||
"github.com/int128/kubelogin/pkg/testing/logger"
|
||||
"github.com/int128/kubelogin/pkg/tlsclientconfig"
|
||||
"github.com/int128/kubelogin/pkg/usecases/authentication"
|
||||
)
|
||||
|
||||
func TestSetup_Do(t *testing.T) {
|
||||
issuedIDToken := testingJWT.EncodeF(t, func(claims *testingJWT.Claims) {
|
||||
claims.Issuer = "https://issuer.example.com"
|
||||
claims.Subject = "YOUR_SUBJECT"
|
||||
claims.ExpiresAt = jwt.NewNumericDate(time.Now().Add(1 * time.Hour))
|
||||
})
|
||||
dummyTLSClientConfig := tlsclientconfig.Config{
|
||||
CACertFilename: []string{"/path/to/cert"},
|
||||
}
|
||||
var grantOptionSet authentication.GrantOptionSet
|
||||
|
||||
ctx := context.Background()
|
||||
in := Input{
|
||||
IssuerURL: "https://accounts.google.com",
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
ExtraScopes: []string{"email"},
|
||||
GrantOptionSet: grantOptionSet,
|
||||
TLSClientConfig: dummyTLSClientConfig,
|
||||
ChangedFlags: []string{
|
||||
"--oidc-issuer-url=https://accounts.google.com",
|
||||
"--oidc-client-id=YOUR_CLIENT_ID",
|
||||
},
|
||||
}
|
||||
mockAuthentication := authentication_mock.NewMockInterface(t)
|
||||
mockAuthentication.EXPECT().
|
||||
Do(ctx, authentication.Input{
|
||||
Provider: oidc.Provider{
|
||||
IssuerURL: "https://accounts.google.com",
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
ExtraScopes: []string{"email"},
|
||||
},
|
||||
GrantOptionSet: grantOptionSet,
|
||||
TLSClientConfig: dummyTLSClientConfig,
|
||||
}).
|
||||
Return(&authentication.Output{
|
||||
TokenSet: oidc.TokenSet{
|
||||
IDToken: issuedIDToken,
|
||||
RefreshToken: "YOUR_REFRESH_TOKEN",
|
||||
},
|
||||
}, nil)
|
||||
u := Setup{
|
||||
Authentication: mockAuthentication,
|
||||
Logger: logger.New(t),
|
||||
}
|
||||
if err := u.Do(ctx, in); err != nil {
|
||||
t.Errorf("Do returned error: %+v", err)
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package setup
|
||||
|
||||
const stage1 = `This setup shows the instruction of Kubernetes OpenID Connect authentication.
|
||||
See also https://github.com/int128/kubelogin.
|
||||
|
||||
## 1. Set up the OpenID Connect Provider
|
||||
|
||||
Open the OpenID Connect Provider and create a client.
|
||||
|
||||
For example, Google Identity Platform:
|
||||
Open https://console.developers.google.com/apis/credentials and create an OAuth client of "Other" type.
|
||||
ISSUER is https://accounts.google.com
|
||||
|
||||
## 2. Verify authentication
|
||||
|
||||
Run the following command to proceed.
|
||||
|
||||
kubectl oidc-login setup \
|
||||
--oidc-issuer-url=ISSUER \
|
||||
--oidc-client-id=YOUR_CLIENT_ID \
|
||||
--oidc-client-secret=YOUR_CLIENT_SECRET
|
||||
|
||||
You can set your CA certificate. See also the options by --help.
|
||||
`
|
||||
|
||||
func (u *Setup) DoStage1() {
|
||||
u.Logger.Printf(stage1)
|
||||
}
|
||||
@@ -1,178 +0,0 @@
|
||||
package setup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/int128/kubelogin/pkg/oidc"
|
||||
"github.com/int128/kubelogin/pkg/tlsclientconfig"
|
||||
"github.com/int128/kubelogin/pkg/usecases/authentication"
|
||||
)
|
||||
|
||||
var stage2Tpl = template.Must(template.New("").Parse(`
|
||||
## 2. Verify authentication
|
||||
|
||||
You got a token with the following claims:
|
||||
|
||||
{{ .IDTokenPrettyJSON }}
|
||||
|
||||
## 3. Bind a cluster role
|
||||
|
||||
Run the following command:
|
||||
|
||||
kubectl create clusterrolebinding oidc-cluster-admin --clusterrole=cluster-admin --user='{{ .IssuerURL }}#{{ .Subject }}'
|
||||
|
||||
## 4. Set up the Kubernetes API server
|
||||
|
||||
Add the following options to the kube-apiserver:
|
||||
|
||||
--oidc-issuer-url={{ .IssuerURL }}
|
||||
--oidc-client-id={{ .ClientID }}
|
||||
|
||||
## 5. Set up the kubeconfig
|
||||
|
||||
Run the following command:
|
||||
|
||||
kubectl config set-credentials oidc \
|
||||
--exec-api-version=client.authentication.k8s.io/v1 \
|
||||
--exec-command=kubectl \
|
||||
--exec-arg=oidc-login \
|
||||
--exec-arg=get-token \
|
||||
{{- range $index, $arg := .Args }}
|
||||
{{- if $index}} \{{end}}
|
||||
--exec-arg={{ $arg }}
|
||||
{{- end }}
|
||||
|
||||
## 6. Verify cluster access
|
||||
|
||||
Make sure you can access the Kubernetes cluster.
|
||||
|
||||
kubectl --user=oidc get nodes
|
||||
|
||||
You can switch the default context to oidc.
|
||||
|
||||
kubectl config set-context --current --user=oidc
|
||||
|
||||
You can share the kubeconfig to your team members for on-boarding.
|
||||
`))
|
||||
|
||||
type stage2Vars struct {
|
||||
IDTokenPrettyJSON string
|
||||
IssuerURL string
|
||||
ClientID string
|
||||
Args []string
|
||||
Subject string
|
||||
}
|
||||
|
||||
// Stage2Input represents an input DTO of the stage2.
|
||||
type Stage2Input struct {
|
||||
IssuerURL string
|
||||
ClientID string
|
||||
ClientSecret string
|
||||
ExtraScopes []string // optional
|
||||
UseAccessToken bool // optional
|
||||
ListenAddressArgs []string // non-nil if set by the command arg
|
||||
PKCEMethod oidc.PKCEMethod
|
||||
PKCEMethodArg string
|
||||
GrantOptionSet authentication.GrantOptionSet
|
||||
TLSClientConfig tlsclientconfig.Config
|
||||
}
|
||||
|
||||
func (u *Setup) DoStage2(ctx context.Context, in Stage2Input) error {
|
||||
u.Logger.Printf("authentication in progress...")
|
||||
out, err := u.Authentication.Do(ctx, authentication.Input{
|
||||
Provider: oidc.Provider{
|
||||
IssuerURL: in.IssuerURL,
|
||||
ClientID: in.ClientID,
|
||||
ClientSecret: in.ClientSecret,
|
||||
ExtraScopes: in.ExtraScopes,
|
||||
PKCEMethod: in.PKCEMethod,
|
||||
UseAccessToken: in.UseAccessToken,
|
||||
},
|
||||
GrantOptionSet: in.GrantOptionSet,
|
||||
TLSClientConfig: in.TLSClientConfig,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("authentication error: %w", err)
|
||||
}
|
||||
idTokenClaims, err := out.TokenSet.DecodeWithoutVerify()
|
||||
if err != nil {
|
||||
return fmt.Errorf("you got an invalid token: %w", err)
|
||||
}
|
||||
|
||||
v := stage2Vars{
|
||||
IDTokenPrettyJSON: idTokenClaims.Pretty,
|
||||
IssuerURL: in.IssuerURL,
|
||||
ClientID: in.ClientID,
|
||||
Args: makeCredentialPluginArgs(in),
|
||||
Subject: idTokenClaims.Subject,
|
||||
}
|
||||
var b strings.Builder
|
||||
if err := stage2Tpl.Execute(&b, &v); err != nil {
|
||||
return fmt.Errorf("could not render the template: %w", err)
|
||||
}
|
||||
u.Logger.Printf(b.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeCredentialPluginArgs(in Stage2Input) []string {
|
||||
var args []string
|
||||
args = append(args, "--oidc-issuer-url="+in.IssuerURL)
|
||||
args = append(args, "--oidc-client-id="+in.ClientID)
|
||||
if in.ClientSecret != "" {
|
||||
args = append(args, "--oidc-client-secret="+in.ClientSecret)
|
||||
}
|
||||
for _, extraScope := range in.ExtraScopes {
|
||||
args = append(args, "--oidc-extra-scope="+extraScope)
|
||||
}
|
||||
if in.PKCEMethodArg != "" {
|
||||
args = append(args, "--oidc-pkce-method="+in.PKCEMethodArg)
|
||||
}
|
||||
if in.UseAccessToken {
|
||||
args = append(args, "--oidc-use-access-token")
|
||||
}
|
||||
for _, f := range in.TLSClientConfig.CACertFilename {
|
||||
args = append(args, "--certificate-authority="+f)
|
||||
}
|
||||
for _, d := range in.TLSClientConfig.CACertData {
|
||||
args = append(args, "--certificate-authority-data="+d)
|
||||
}
|
||||
if in.TLSClientConfig.SkipTLSVerify {
|
||||
args = append(args, "--insecure-skip-tls-verify")
|
||||
}
|
||||
|
||||
if in.GrantOptionSet.AuthCodeBrowserOption != nil {
|
||||
if in.GrantOptionSet.AuthCodeBrowserOption.SkipOpenBrowser {
|
||||
args = append(args, "--skip-open-browser")
|
||||
}
|
||||
if in.GrantOptionSet.AuthCodeBrowserOption.BrowserCommand != "" {
|
||||
args = append(args, "--browser-command="+in.GrantOptionSet.AuthCodeBrowserOption.BrowserCommand)
|
||||
}
|
||||
if in.GrantOptionSet.AuthCodeBrowserOption.LocalServerCertFile != "" {
|
||||
// Resolve the absolute path for the cert files so the user doesn't have to know
|
||||
// to use one when running setup.
|
||||
certpath, err := filepath.Abs(in.GrantOptionSet.AuthCodeBrowserOption.LocalServerCertFile)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
keypath, err := filepath.Abs(in.GrantOptionSet.AuthCodeBrowserOption.LocalServerKeyFile)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
args = append(args, "--local-server-cert="+certpath)
|
||||
args = append(args, "--local-server-key="+keypath)
|
||||
}
|
||||
}
|
||||
for _, l := range in.ListenAddressArgs {
|
||||
args = append(args, "--listen-address="+l)
|
||||
}
|
||||
if in.GrantOptionSet.ROPCOption != nil {
|
||||
if in.GrantOptionSet.ROPCOption.Username != "" {
|
||||
args = append(args, "--username="+in.GrantOptionSet.ROPCOption.Username)
|
||||
}
|
||||
}
|
||||
return args
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
package setup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/int128/kubelogin/mocks/github.com/int128/kubelogin/pkg/usecases/authentication_mock"
|
||||
"github.com/int128/kubelogin/pkg/oidc"
|
||||
testingJWT "github.com/int128/kubelogin/pkg/testing/jwt"
|
||||
"github.com/int128/kubelogin/pkg/testing/logger"
|
||||
"github.com/int128/kubelogin/pkg/tlsclientconfig"
|
||||
"github.com/int128/kubelogin/pkg/usecases/authentication"
|
||||
"github.com/int128/kubelogin/pkg/usecases/authentication/authcode"
|
||||
"github.com/int128/kubelogin/pkg/usecases/authentication/ropc"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSetup_DoStage2(t *testing.T) {
|
||||
issuedIDToken := testingJWT.EncodeF(t, func(claims *testingJWT.Claims) {
|
||||
claims.Issuer = "https://issuer.example.com"
|
||||
claims.Subject = "YOUR_SUBJECT"
|
||||
claims.ExpiresAt = jwt.NewNumericDate(time.Now().Add(1 * time.Hour))
|
||||
})
|
||||
dummyTLSClientConfig := tlsclientconfig.Config{
|
||||
CACertFilename: []string{"/path/to/cert"},
|
||||
}
|
||||
var grantOptionSet authentication.GrantOptionSet
|
||||
|
||||
ctx := context.Background()
|
||||
in := Stage2Input{
|
||||
IssuerURL: "https://accounts.google.com",
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
ExtraScopes: []string{"email"},
|
||||
GrantOptionSet: grantOptionSet,
|
||||
TLSClientConfig: dummyTLSClientConfig,
|
||||
}
|
||||
mockAuthentication := authentication_mock.NewMockInterface(t)
|
||||
mockAuthentication.EXPECT().
|
||||
Do(ctx, authentication.Input{
|
||||
Provider: oidc.Provider{
|
||||
IssuerURL: "https://accounts.google.com",
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
ExtraScopes: []string{"email"},
|
||||
},
|
||||
GrantOptionSet: grantOptionSet,
|
||||
TLSClientConfig: dummyTLSClientConfig,
|
||||
}).
|
||||
Return(&authentication.Output{
|
||||
TokenSet: oidc.TokenSet{
|
||||
IDToken: issuedIDToken,
|
||||
RefreshToken: "YOUR_REFRESH_TOKEN",
|
||||
},
|
||||
}, nil)
|
||||
u := Setup{
|
||||
Authentication: mockAuthentication,
|
||||
Logger: logger.New(t),
|
||||
}
|
||||
if err := u.DoStage2(ctx, in); err != nil {
|
||||
t.Errorf("DoStage2 returned error: %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_makeCredentialPluginArgs(t *testing.T) {
|
||||
in := Stage2Input{
|
||||
IssuerURL: "https://oidc.example.com",
|
||||
ClientID: "test_kid",
|
||||
ClientSecret: "test_ksecret",
|
||||
ExtraScopes: []string{"groups"},
|
||||
PKCEMethodArg: "S256",
|
||||
ListenAddressArgs: []string{"127.0.0.1:8080", "127.0.0.1:8888"},
|
||||
GrantOptionSet: authentication.GrantOptionSet{
|
||||
AuthCodeBrowserOption: &authcode.BrowserOption{
|
||||
SkipOpenBrowser: true,
|
||||
BrowserCommand: "firefox",
|
||||
LocalServerCertFile: "/path/to/cert.crt",
|
||||
LocalServerKeyFile: "/path/to/cert.key",
|
||||
},
|
||||
ROPCOption: &ropc.Option{
|
||||
Username: "user1",
|
||||
},
|
||||
},
|
||||
TLSClientConfig: tlsclientconfig.Config{
|
||||
CACertFilename: []string{"/path/to/ca.crt"},
|
||||
CACertData: []string{"base64encoded1"},
|
||||
SkipTLSVerify: true,
|
||||
},
|
||||
}
|
||||
expet := []string{
|
||||
"--oidc-issuer-url=https://oidc.example.com",
|
||||
"--oidc-client-id=test_kid",
|
||||
"--oidc-client-secret=test_ksecret",
|
||||
"--oidc-extra-scope=groups",
|
||||
"--oidc-pkce-method=S256",
|
||||
"--certificate-authority=/path/to/ca.crt",
|
||||
"--certificate-authority-data=base64encoded1",
|
||||
"--insecure-skip-tls-verify",
|
||||
"--skip-open-browser",
|
||||
"--browser-command=firefox",
|
||||
"--local-server-cert=/path/to/cert.crt",
|
||||
"--local-server-key=/path/to/cert.key",
|
||||
"--listen-address=127.0.0.1:8080",
|
||||
"--listen-address=127.0.0.1:8888",
|
||||
"--username=user1",
|
||||
}
|
||||
got := makeCredentialPluginArgs(in)
|
||||
assert.Equal(t, expet, got)
|
||||
}
|
||||
Reference in New Issue
Block a user