Skip to content

Commit

Permalink
Okteto kubeconfig will use kubetoken client auth if available on okte…
Browse files Browse the repository at this point in the history
…to server (okteto#3409)

* Fill user authinfo with exec
* Check for kubetoken capability on server
* Add context for kubetoken to user.Exec
* Use oktetoURL and namespace in exec args
* Update golang-ci
* add os.Environ to cmd

Signed-off-by: Agustín Díaz <[email protected]>
  • Loading branch information
AgustinRamiroDiaz authored Mar 28, 2023
1 parent 3b63511 commit 8838b49
Show file tree
Hide file tree
Showing 13 changed files with 197 additions and 13 deletions.
4 changes: 2 additions & 2 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ commands:
executors:
golang-ci:
docker:
- image: okteto/golang-ci:1.18.0
- image: okteto/golang-ci:2.2.2

jobs:
build-binaries:
Expand Down Expand Up @@ -139,11 +139,11 @@ jobs:
chmod +x /usr/local/bin/kubectl
cp $(pwd)/artifacts/bin/okteto-Linux-x86_64 /usr/local/bin/okteto
/usr/local/bin/okteto login --token ${API_STAGING_TOKEN}
- integration-deprecated
- integration-deploy
- integration-up
- integration-actions
- integration-okteto
- integration-deprecated
- integration-build
- save_cache:
key: v4-pkg-cache-{{ checksum "go.sum" }}
Expand Down
5 changes: 3 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,9 @@ integration-up:

.PHONY: integration-deprecated
integration-deprecated:
go test github.com/okteto/okteto/integration/deprecated/push -tags="integration" --count=1 -v -timeout 15m && go test github.com/okteto/okteto/integration/deprecated/stack -tags="integration" --count=1 -v -timeout 15m

go test github.com/okteto/okteto/integration/deprecated/stack -tags="integration" --count=1 -v -timeout 15m && \
go test github.com/okteto/okteto/integration/deprecated/push -tags="integration" --count=1 -v -timeout 15m

.PHONY: build
build:
$(BUILDCOMMAND) -o ${BINDIR}/okteto
Expand Down
24 changes: 22 additions & 2 deletions cmd/context/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"os"
"strings"
Expand All @@ -42,7 +43,8 @@ type ContextCommand struct {
LoginController login.LoginInterface
OktetoClientProvider types.OktetoClientProvider

OktetoContextWriter okteto.ContextConfigWriterInterface
OktetoContextWriter okteto.ContextConfigWriterInterface
IsOktetoKubeTokenPresent func(url string) (bool, error)
}

// NewContextCommand creates a new ContextCommand
Expand All @@ -52,6 +54,21 @@ func NewContextCommand() *ContextCommand {
LoginController: login.NewLoginController(),
OktetoClientProvider: okteto.NewOktetoClientProvider(),
OktetoContextWriter: okteto.NewContextConfigWriter(),
IsOktetoKubeTokenPresent: func(oktetoURL string) (bool, error) {
// this is a hack to check if the server was upgraded to a version that supports the /auth/kubetoken/{namespace} endpoint
// if the endpoint is not present, the server will return a 401 unauthorized error
kubeTokenURL, err := okteto.ParseOktetoURLWithPath(oktetoURL, "auth/kubetoken/default")
if err != nil {
return false, fmt.Errorf("failed to parse kubetoken url: %w", err)
}

resp, err := http.Get(kubeTokenURL)
if err != nil {
return false, fmt.Errorf("failed to get kubetoken url: %w", err)
}

return resp.StatusCode == http.StatusUnauthorized, nil
},
}
}

Expand Down Expand Up @@ -264,7 +281,10 @@ func (c *ContextCommand) initOktetoContext(ctx context.Context, ctxOptions *Cont
if cfg == nil {
cfg = kubeconfig.Create()
}
okteto.AddOktetoCredentialsToCfg(cfg, &userContext.Credentials, ctxOptions.Namespace, userContext.User.ID, okteto.Context().Name)
err = okteto.AddOktetoCredentialsToCfg(cfg, &userContext.Credentials, ctxOptions.Namespace, userContext.User.ID, okteto.Context().Name, c.IsOktetoKubeTokenPresent)
if err != nil {
return err
}
okteto.Context().Cfg = cfg
okteto.Context().IsOkteto = true
okteto.Context().IsInsecure = okteto.IsInsecureSkipTLSVerifyPolicy()
Expand Down
3 changes: 3 additions & 0 deletions cmd/context/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ func newFakeContextCommand(c *client.FakeOktetoClient, user *types.User, fakeObj
LoginController: test.NewFakeLoginController(user, nil),
OktetoClientProvider: client.NewFakeOktetoClientProvider(c),
OktetoContextWriter: test.NewFakeOktetoContextWriter(),
IsOktetoKubeTokenPresent: func(url string) (bool, error) {
return false, nil
},
}
}

Expand Down
3 changes: 3 additions & 0 deletions cmd/namespace/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ func newFakeContextCommand(c *client.FakeOktetoClient, user *types.User) *contex
K8sClientProvider: test.NewFakeK8sProvider(nil),
LoginController: test.NewFakeLoginController(user, nil),
OktetoContextWriter: test.NewFakeOktetoContextWriter(),
IsOktetoKubeTokenPresent: func(url string) (bool, error) {
return false, nil
},
}
}

Expand Down
1 change: 1 addition & 0 deletions integration/actions/apply_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ func executeApply(namespace string) error {
if _, err := os.Stat(kubepath); err != nil {
log.Printf("could not get kubepath: %s", err)
}
cmd.Env = os.Environ()
cmd.Env = append(cmd.Env, fmt.Sprintf("KUBECONFIG=%s", kubepath))
o, err := cmd.CombinedOutput()
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions integration/commands/preview.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ func RunOktetoDeployPreview(oktetoPath string, deployOptions *DeployPreviewOptio
func RunOktetoPreviewDestroy(oktetoPath string, destroyOptions *DestroyPreviewOptions) error {
log.Printf("okteto destroy %s", oktetoPath)
cmd := exec.Command(oktetoPath, "preview", "destroy", destroyOptions.Namespace)
cmd.Env = os.Environ()
if destroyOptions.Workdir != "" {
cmd.Dir = destroyOptions.Workdir
}
Expand Down
1 change: 1 addition & 0 deletions integration/commands/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
// RunOktetoPush runs an okteto push command
func RunOktetoPush(oktetoPath, workdir string) error {
cmd := exec.Command(oktetoPath, "push")
cmd.Env = os.Environ()
if workdir != "" {
cmd.Dir = workdir
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", constants.OktetoHomeEnvVar, workdir))
Expand Down
2 changes: 2 additions & 0 deletions integration/commands/stacks.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ type StackDestroyOptions struct {
// RunOktetoStackDeploy runs an okteto deploy command
func RunOktetoStackDeploy(oktetoPath string, deployOptions *StackDeployOptions) error {
cmd := exec.Command(oktetoPath, "stack", "deploy")
cmd.Env = os.Environ()
if deployOptions.Workdir != "" {
cmd.Dir = deployOptions.Workdir
}
Expand Down Expand Up @@ -77,6 +78,7 @@ func RunOktetoStackDeploy(oktetoPath string, deployOptions *StackDeployOptions)
// RunOktetoStackDestroy runs an okteto deploy command
func RunOktetoStackDestroy(oktetoPath string, deployOptions *StackDestroyOptions) error {
cmd := exec.Command(oktetoPath, "stack", "destroy")
cmd.Env = os.Environ()
if deployOptions.Workdir != "" {
cmd.Dir = deployOptions.Workdir
}
Expand Down
6 changes: 3 additions & 3 deletions pkg/okteto/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ func newOktetoHttpClient(contextName, token, oktetoUrlPath string) (*http.Client
if token == "" {
return nil, "", fmt.Errorf(oktetoErrors.ErrNotLogged, contextName)
}
u, err := parseOktetoURLWithPath(contextName, oktetoUrlPath)
u, err := ParseOktetoURLWithPath(contextName, oktetoUrlPath)
if err != nil {
return nil, "", err
}
Expand Down Expand Up @@ -177,10 +177,10 @@ func newOktetoClientFromGraphqlClient(url string, httpClient *http.Client) (*Okt
}

func parseOktetoURL(u string) (string, error) {
return parseOktetoURLWithPath(u, "graphql")
return ParseOktetoURLWithPath(u, "graphql")
}

func parseOktetoURLWithPath(u, path string) (string, error) {
func ParseOktetoURLWithPath(u, path string) (string, error) {
if u == "" {
return "", errURLNotSet
}
Expand Down
28 changes: 25 additions & 3 deletions pkg/okteto/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -338,11 +338,16 @@ func (*ContextConfigWriter) Write() error {
return nil
}

func AddOktetoCredentialsToCfg(cfg *clientcmdapi.Config, cred *types.Credential, namespace, userName, oktetoURL string) {
func AddOktetoCredentialsToCfg(cfg *clientcmdapi.Config, cred *types.Credential, namespace, userName, oktetoURL string, hasKubeTokenCapabily func(oktetoURL string) (bool, error)) error {
// If the context is being initialized within the execution of `okteto deploy` deploy command it should not
// write the Okteto credentials into the kubeconfig. It would overwrite the proxy settings
if os.Getenv(constants.OktetoSkipConfigCredentialsUpdate) == "true" {
return
return nil
}

hasKubeToken, err := hasKubeTokenCapabily(oktetoURL)
if err != nil {
return err
}

clusterName := UrlToKubernetesContext(oktetoURL)
Expand All @@ -361,7 +366,23 @@ func AddOktetoCredentialsToCfg(cfg *clientcmdapi.Config, cred *types.Credential,
if !ok {
user = clientcmdapi.NewAuthInfo()
}
user.Token = cred.Token

if hasKubeToken {
user.Token = ""
user.Exec = &clientcmdapi.ExecConfig{
Command: "okteto",
Args: []string{"kubetoken", oktetoURL, namespace},
APIVersion: "client.authentication.k8s.io/v1",
InstallHint: "Okteto needs to be installed and in your PATH to use this context. Please visit https://www.okteto.com/docs/getting-started/ for more information.",
ProvideClusterInfo: true,
InteractiveMode: "IfAvailable",
}
} else {
// fallback for okteto API before client authentication support
// TODO: remove once we stop supporting token based authentication https://github.com/okteto/okteto/pull/3409
user.Token = cred.Token
user.Exec = nil
}
cfg.AuthInfos[userName] = user

// create context
Expand All @@ -379,6 +400,7 @@ func AddOktetoCredentialsToCfg(cfg *clientcmdapi.Config, cred *types.Credential,
cfg.Contexts[clusterName] = context

cfg.CurrentContext = clusterName
return nil
}

func GetK8sClient() (*kubernetes.Clientset, *rest.Config, error) {
Expand Down
130 changes: 130 additions & 0 deletions pkg/okteto/context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,16 @@ package okteto

import (
"context"
"fmt"
"testing"

"k8s.io/apimachinery/pkg/runtime"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"

"github.com/okteto/okteto/internal/test"
"github.com/okteto/okteto/pkg/constants"
"github.com/okteto/okteto/pkg/types"
"github.com/stretchr/testify/require"
)

func Test_UrlToKubernetesContext(t *testing.T) {
Expand Down Expand Up @@ -122,3 +129,126 @@ func Test_RemoveSchema(t *testing.T) {
})
}
}

func Test_AddOktetoCredentialsToCfg(t *testing.T) {
t.Parallel()

cfg := clientcmdapi.NewConfig()
namespace := "namespace"
username := "cindy"
cred := &types.Credential{
Server: "https://ip.ip.ip.ip",
Certificate: "cred-cert",
Token: "cred-token",
}

oktetoURL := "https://cloud.okteto.com"
clusterAndContextName := "cloud_okteto_com"

tt := []struct {
name string
hasKubeToken bool
expectedCfg *clientcmdapi.Config
err error
expectedError error
}{
{
name: "has-kube-token",
hasKubeToken: true,
expectedCfg: &clientcmdapi.Config{
Extensions: map[string]runtime.Object{},
Clusters: map[string]*clientcmdapi.Cluster{
clusterAndContextName: {
Server: cred.Server,
CertificateAuthorityData: []byte(cred.Certificate),
Extensions: map[string]runtime.Object{},
},
},
Contexts: map[string]*clientcmdapi.Context{
clusterAndContextName: {
Cluster: clusterAndContextName,
AuthInfo: username,
Namespace: namespace,
Extensions: map[string]runtime.Object{
constants.OktetoExtension: nil,
},
},
},
AuthInfos: map[string]*clientcmdapi.AuthInfo{
username: {
Exec: &clientcmdapi.ExecConfig{
Command: "okteto",
Args: []string{"kubetoken", oktetoURL, namespace},
APIVersion: "client.authentication.k8s.io/v1",
InstallHint: "Okteto needs to be installed and in your PATH to use this context. Please visit https://www.okteto.com/docs/getting-started/ for more information.",
ProvideClusterInfo: true,
InteractiveMode: "IfAvailable",
},
Extensions: map[string]runtime.Object{},
ImpersonateUserExtra: map[string][]string{},
},
},
CurrentContext: clusterAndContextName,
Preferences: clientcmdapi.Preferences{
Extensions: map[string]runtime.Object{},
},
},
},
{
name: "no-kube-token",
hasKubeToken: false,
expectedCfg: &clientcmdapi.Config{
Extensions: map[string]runtime.Object{},
Clusters: map[string]*clientcmdapi.Cluster{
clusterAndContextName: {
Server: cred.Server,
CertificateAuthorityData: []byte(cred.Certificate),
Extensions: map[string]runtime.Object{},
},
},
Contexts: map[string]*clientcmdapi.Context{
clusterAndContextName: {
Cluster: clusterAndContextName,
AuthInfo: username,
Namespace: namespace,
Extensions: map[string]runtime.Object{
constants.OktetoExtension: nil,
},
},
},
AuthInfos: map[string]*clientcmdapi.AuthInfo{
username: {
Token: cred.Token,
Extensions: map[string]runtime.Object{},
ImpersonateUserExtra: map[string][]string{},
},
},
CurrentContext: clusterAndContextName,
Preferences: clientcmdapi.Preferences{
Extensions: map[string]runtime.Object{},
},
},
},
{
name: "error",
err: fmt.Errorf("error"),
expectedError: fmt.Errorf("error"),
expectedCfg: clientcmdapi.NewConfig(),
},
}

for _, testCase := range tt {
cfg := cfg.DeepCopy()
testCase := testCase
t.Run(testCase.name, func(t *testing.T) {
t.Parallel()
hasKubeTokenCapabily := func(oktetoURL string) (bool, error) {
return testCase.hasKubeToken, testCase.err
}
err := AddOktetoCredentialsToCfg(cfg, cred, namespace, username, oktetoURL, hasKubeTokenCapabily)
require.Equal(t, testCase.expectedCfg, cfg)
require.Equal(t, testCase.expectedError, err)
})
}

}
2 changes: 1 addition & 1 deletion pkg/types/credential.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ package types
type Credential struct {
Server string `json:"server" yaml:"server"`
Certificate string `json:"certificate" yaml:"certificate"`
Token string `json:"token" yaml:"token"`
Token string `json:"token" yaml:"token"` // TODO: Deprecate once we move to kubetoken auth
Namespace string `json:"namespace" yaml:"namespace"`
}

0 comments on commit 8838b49

Please sign in to comment.