Repository: okteto/okteto (go)

Issue:
Add configurable SSH server key type
**Is your feature request related to a problem? Please describe.**

Okteto generates an RSA key when creating the SSH server for `okteto up`.

https://github.com/okteto/okteto/blob/29b67e00c07a33576f845f212012614c067f6332/pkg/ssh/key.go#L87

Visual Studio 2022 V17.10 no longer supports RSA key for remote debugging.

https://learn.microsoft.com/en-us/cpp/linux/connect-to-your-remote-linux-computer?view=msvc-170#set-up-the-remote-connection

> Versions of Visual Studio before 17.10 support EC, RSA, and DSA keys for remote connections. Because of security concerns, RSA and DSA keys are no longer supported in VS 17.10 and later. Only EC keys are currently supported. To create a key pair compatible with the connection manager use the command: ssh-keygen -m pem -t ecdsa -f <key-name>

**Describe the solution you'd like**
The ability to configure what type of SSH key is generated to support backward and forward compatibility.

**Describe alternatives you've considered**
The ability to tell Okteto what key file to use for it's ssh server.



Pull request:
Add EC encryption to `okteto up`
# Proposed changes

Fixes #4399 DEV-603


- Change the RSA key generated by okteto to a EC key
- Changes the remote SSH server to start using EC keys instead of RSA (Done in https://github.com/okteto/remote)

Depends on:
- https://github.com/okteto/remote/pull/33
- https://github.com/okteto/bin-image/pull/14

## How to validate

Use the variable `OKTETO_BIN=jlopezbarb/bin:ecdsa` by running `export OKTETO_BIN=jlopezbarb/bin:ecdsa`

Test using https://github.com/okteto/go-getting-started but it works with any repo

1. Run `okteto up` and check that you get the message  `✓  Client certificates generated`
2. On another terminal run ssh `hello-world.okteto` and check that the warning mentions `ECDSA` (`Warning: Permanently added '[localhost]:59218' (ECDSA) to the list of known hosts`)
3. Check on `.ssh/config` that your last entry (or the one for `hello-world.okteto`) has an `ecdsa` encryption
4. From the `.ssh/config`, get the `IdentityFile`, read it and check that the file is an elliptic curve key file


## CLI Quality Reminders 🔧

For both authors and reviewers:

- Scrutinize for potential regressions
- Ensure key automated tests are in place
- Build the CLI and test using the validation steps
- Assess Developer Experience impact (log messages, performances, etc)
- If too broad, consider breaking into smaller PRs
- Adhere to our [code style](https://github.com/okteto/okteto/blob/master/docs/code-style.md) and [code review](https://github.com/okteto/okteto/blob/master/docs/code-review.md) guidelines

<!-- Remove comment when okteto/okteto is out of wait list for Copilot for Pull Requests
----

<details>
<summary>🧪 Copilot generated PR description</summary>

copilot:all

</details>
-->


Patch:
diff --git a/cmd/deploy/deploy.go b/cmd/deploy/deploy.go
index a0721f3c..c98ebac7 100644
--- a/cmd/deploy/deploy.go
+++ b/cmd/deploy/deploy.go
@@ -102,6 +102,7 @@ type getDeployerFunc func(
 	*io.Controller,
 	*io.K8sLogger,
 	dependencyEnvVarsGetter,
+	executionEnvVarsGetter,
 ) (Deployer, error)
 
 type cleanUpFunc func(context.Context, error)
@@ -494,7 +495,17 @@ func (dc *Command) Run(ctx context.Context, deployOptions *Options) error {
 
 func (dc *Command) deploy(ctx context.Context, deployOptions *Options, cwd string, c kubernetes.Interface) error {
 	// If the command is configured to execute things remotely (--remote, deploy.image or deploy.remote) it should be executed in the remote. If not, it should be executed locally
-	deployer, err := dc.GetDeployer(ctx, deployOptions, dc.Builder.GetBuildEnvVars, dc.CfgMapHandler, dc.K8sClientProvider, dc.IoCtrl, dc.K8sLogger, GetDependencyEnvVars)
+	deployer, err := dc.GetDeployer(
+		ctx,
+		deployOptions,
+		dc.Builder.GetBuildEnvVars,
+		dc.CfgMapHandler,
+		dc.K8sClientProvider,
+		dc.IoCtrl,
+		dc.K8sLogger,
+		GetDependencyEnvVars,
+		deployable.GetPlatformEnvironment,
+	)
 	if err != nil {
 		return err
 	}
@@ -594,10 +605,11 @@ func GetDeployer(ctx context.Context,
 	ioCtrl *io.Controller,
 	k8Logger *io.K8sLogger,
 	dependencyEnvVarsGetter dependencyEnvVarsGetter,
+	executionEnvVarGetter executionEnvVarsGetter,
 ) (Deployer, error) {
 	if shouldRunInRemote(opts) {
 		oktetoLog.Info("Deploying remotely...")
-		return newRemoteDeployer(buildEnvVarsGetter, ioCtrl, dependencyEnvVarsGetter), nil
+		return newRemoteDeployer(buildEnvVarsGetter, ioCtrl, dependencyEnvVarsGetter, executionEnvVarGetter), nil
 	}
 
 	oktetoLog.Info("Deploying locally...")
diff --git a/cmd/deploy/remote.go b/cmd/deploy/remote.go
index 766742fa..cc69338b 100644
--- a/cmd/deploy/remote.go
+++ b/cmd/deploy/remote.go
@@ -52,6 +52,8 @@ type buildEnvVarsGetter func() map[string]string
 
 type dependencyEnvVarsGetter func(environGetter environGetter) map[string]string
 
+type executionEnvVarsGetter func(ctx context.Context) map[string]string
+
 type remoteDeployer struct {
 	getBuildEnvVars buildEnvVarsGetter
 	runner          remoteRunner
@@ -60,10 +62,11 @@ type remoteDeployer struct {
 	ioCtrl *io.Controller
 
 	getDependencyEnvVars dependencyEnvVarsGetter
+	getExecutionEnvVars  executionEnvVarsGetter
 }
 
 // newRemoteDeployer creates the remote deployer
-func newRemoteDeployer(buildVarsGetter buildEnvVarsGetter, ioCtrl *io.Controller, getDependencyEnvVars dependencyEnvVarsGetter) *remoteDeployer {
+func newRemoteDeployer(buildVarsGetter buildEnvVarsGetter, ioCtrl *io.Controller, getDependencyEnvVars dependencyEnvVarsGetter, executionEnvVarGetter executionEnvVarsGetter) *remoteDeployer {
 	fs := afero.NewOsFs()
 	builder := buildCmd.NewOktetoBuilder(
 		&okteto.ContextStateless{
@@ -77,6 +80,7 @@ func newRemoteDeployer(buildVarsGetter buildEnvVarsGetter, ioCtrl *io.Controller
 		runner:               runner,
 		ioCtrl:               ioCtrl,
 		getDependencyEnvVars: getDependencyEnvVars,
+		getExecutionEnvVars:  executionEnvVarGetter,
 	}
 }
 
@@ -120,6 +124,7 @@ func (rd *remoteDeployer) Deploy(ctx context.Context, deployOptions *Options) er
 		CommandFlags:              commandsFlags,
 		BuildEnvVars:              rd.getBuildEnvVars(),
 		DependenciesEnvVars:       rd.getDependencyEnvVars(os.Environ),
+		ExecutionEnvVars:          rd.getExecutionEnvVars(ctx),
 		DockerfileName:            dockerfileTemporalName,
 		Deployable:                dep,
 		Manifest:                  deployOptions.Manifest,
diff --git a/cmd/destroy/local.go b/cmd/destroy/local.go
index b58aae5b..c8c91665 100644
--- a/cmd/destroy/local.go
+++ b/cmd/destroy/local.go
@@ -16,6 +16,7 @@ package destroy
 import (
 	"context"
 	"errors"
+	"fmt"
 	"os"
 	"os/signal"
 
@@ -42,7 +43,7 @@ func newLocalDestroyer(
 }
 
 // Destroy starts the execution of the local destroyer
-func (ld *localDestroyCommand) Destroy(_ context.Context, opts *Options) error {
+func (ld *localDestroyCommand) Destroy(ctx context.Context, opts *Options) error {
 	stop := make(chan os.Signal, 1)
 	signal.Notify(stop, os.Interrupt)
 	exit := make(chan error, 1)
@@ -52,7 +53,11 @@ func (ld *localDestroyCommand) Destroy(_ context.Context, opts *Options) error {
 			exit <- nil
 			return
 		}
+		vars := opts.Variables
 
+		for k, v := range deployable.GetPlatformEnvironment(ctx) {
+			vars = append(vars, fmt.Sprintf("%s=%s", k, v))
+		}
 		params := deployable.DestroyParameters{
 			Name:         opts.Name,
 			Namespace:    opts.Namespace,
@@ -60,7 +65,7 @@ func (ld *localDestroyCommand) Destroy(_ context.Context, opts *Options) error {
 			Deployable: deployable.Entity{
 				Commands: opts.Manifest.Destroy.Commands,
 			},
-			Variables: opts.Variables,
+			Variables: vars,
 		}
 		if err := ld.runner.RunDestroy(params); err != nil {
 			exit <- err
diff --git a/cmd/destroy/remote.go b/cmd/destroy/remote.go
index 62e124c4..cc08a7bf 100644
--- a/cmd/destroy/remote.go
+++ b/cmd/destroy/remote.go
@@ -118,6 +118,7 @@ func (rd *remoteDestroyCommand) Destroy(ctx context.Context, opts *Options) erro
 		TemplateName:              templateName,
 		CommandFlags:              commandFlags,
 		BuildEnvVars:              make(map[string]string),
+		ExecutionEnvVars:          deployable.GetPlatformEnvironment(ctx),
 		DependenciesEnvVars:       make(map[string]string),
 		DockerfileName:            dockerfileTemporalName,
 		Deployable:                dep,
diff --git a/pkg/deployable/deploy.go b/pkg/deployable/deploy.go
index 1a1939b3..7f81d9a2 100644
--- a/pkg/deployable/deploy.go
+++ b/pkg/deployable/deploy.go
@@ -264,6 +264,9 @@ func (r *DeployRunner) RunDeploy(ctx context.Context, params DeployParameters) e
 			// Set OKTETO_DOMAIN=okteto-subdomain env variable
 			fmt.Sprintf("%s=%s", model.OktetoDomainEnvVar, okteto.GetSubdomain()),
 		)
+		for k, v := range GetPlatformEnvironment(ctx) {
+			params.Variables = append(params.Variables, fmt.Sprintf("%s=%s", k, v))
+		}
 	}
 	oktetoLog.EnableMasking()
 	err = r.runCommandsSection(ctx, params)
@@ -427,3 +430,17 @@ func createTempOktetoEnvFile(fs afero.Fs) (afero.File, func(), error) {
 		}
 	}, nil
 }
+
+func GetPlatformEnvironment(ctx context.Context) map[string]string {
+	c, err := okteto.NewOktetoClient()
+	if err != nil {
+		return nil
+	}
+
+	env, err := c.User().GetExecutionEnv(ctx)
+	if err != nil {
+		oktetoLog.Debugf("failed to get platform environment: %s", err)
+		return map[string]string{}
+	}
+	return env
+}
diff --git a/pkg/model/dev.go b/pkg/model/dev.go
index 64ca627b..af4e1960 100644
--- a/pkg/model/dev.go
+++ b/pkg/model/dev.go
@@ -43,7 +43,7 @@ import (
 
 var (
 	// OktetoBinImageTag image tag with okteto internal binaries
-	OktetoBinImageTag = "okteto/bin:1.5.0"
+	OktetoBinImageTag = "okteto/bin:1.6.0"
 
 	errBadName = fmt.Errorf("Invalid name: must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character")
 
diff --git a/pkg/okteto/secrets.go b/pkg/okteto/secrets.go
index 63795a14..b889299c 100644
--- a/pkg/okteto/secrets.go
+++ b/pkg/okteto/secrets.go
@@ -55,6 +55,10 @@ type getRegistryCredentialsQuery struct {
 	RegistryCredentials registryCredsQuery `graphql:"registryCredentials(registryUrl: $regHost)"`
 }
 
+type getExecutionEnvQuery struct {
+	ExecutionEnv []variablesQuery `graphql:"executionEnv"`
+}
+
 type userQuery struct {
 	Id              graphql.String
 	Name            graphql.String
@@ -288,3 +292,21 @@ func (c *userClient) GetRegistryCredentials(ctx context.Context, host string) (d
 	}, nil
 
 }
+
+func (c *userClient) GetExecutionEnv(ctx context.Context) (map[string]string, error) {
+	result := make(map[string]string)
+
+	var queryStruct getExecutionEnvQuery
+	err := query(ctx, &queryStruct, nil, c.client)
+	if err != nil {
+		if strings.Contains(err.Error(), "Cannot query field \"executionEnv\" on type \"Query\"") {
+			return result, nil
+		}
+		return result, err
+	}
+
+	for _, envVar := range queryStruct.ExecutionEnv {
+		result[string(envVar.Name)] = string(envVar.Value)
+	}
+	return result, nil
+}
diff --git a/pkg/remote/remote.go b/pkg/remote/remote.go
index 76233a4c..e81a0a75 100644
--- a/pkg/remote/remote.go
+++ b/pkg/remote/remote.go
@@ -97,6 +97,10 @@ ARG {{ .InvalidateCacheArgName }}
 RUN echo "${{ .InvalidateCacheArgName }}" > /etc/.oktetocachekey
 RUN okteto registrytoken install --force --log-output=json
 
+{{ range $key, $val := .OktetoExecutionEnvVars}}
+ENV {{$key}} {{$val}}
+{{ end }}
+
 RUN \
   {{range $key, $path := .Caches }}--mount=type=cache,target={{$path}} {{end}}\
   --mount=type=secret,id=known_hosts --mount=id=remote,type=ssh \
@@ -147,6 +151,7 @@ type Runner struct {
 type Params struct {
 	BuildEnvVars        map[string]string
 	DependenciesEnvVars map[string]string
+	ExecutionEnvVars    map[string]string
 	Manifest            *model.Manifest
 	Command             string
 	TemplateName        string
@@ -187,6 +192,7 @@ type dockerfileTemplateProperties struct {
 	RemoteDeployEnvVar       string
 	OktetoBuildEnvVars       map[string]string
 	OktetoDependencyEnvVars  map[string]string
+	OktetoExecutionEnvVars   map[string]string
 	ContextArgName           string
 	NamespaceArgName         string
 	TokenArgName             string
@@ -410,6 +416,7 @@ func (r *Runner) createDockerfile(tmpDir string, params *Params) (string, error)
 		BuildKitHostArgName:      model.OktetoBuildkitHostURLEnvVar,
 		OktetoRegistryURLArgName: model.OktetoRegistryURLEnvVar,
 		OktetoDependencyEnvVars:  params.DependenciesEnvVars,
+		OktetoExecutionEnvVars:   params.ExecutionEnvVars,
 		Command:                  params.Command,
 		OktetoIsPreviewEnv:       constants.OktetoIsPreviewEnvVar,
 		Caches:                   params.Caches,
diff --git a/pkg/ssh/key.go b/pkg/ssh/key.go
index d0bc3958..ca557b35 100644
--- a/pkg/ssh/key.go
+++ b/pkg/ssh/key.go
@@ -10,12 +10,12 @@
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
-
 package ssh
 
 import (
+	"crypto/ecdsa"
+	"crypto/elliptic"
 	"crypto/rand"
-	"crypto/rsa"
 	"crypto/x509"
 	"encoding/pem"
 	"fmt"
@@ -29,9 +29,12 @@ import (
 )
 
 const (
-	privateKeyFile = "id_rsa_okteto"
-	publicKeyFile  = "id_rsa_okteto.pub"
-	bitSize        = 4096
+	privateKeyFile = "id_ecdsa_okteto"
+	publicKeyFile  = "id_ecdsa_okteto.pub"
+)
+
+var (
+	curve = elliptic.P256() // You can use P256(), P384(), or P521()
 )
 
 // KeyExists returns true if the okteto key pair exists
@@ -53,14 +56,14 @@ func KeyExists() bool {
 	return true
 }
 
-// GenerateKeys generates a SSH key pair on path
+// GenerateKeys generates an SSH key pair on path
 func GenerateKeys() error {
 	publicKeyPath, privateKeyPath := getKeyPaths()
-	return generate(publicKeyPath, privateKeyPath, bitSize)
+	return generate(publicKeyPath, privateKeyPath)
 }
 
-func generate(public, private string, bitSize int) error {
-	privateKey, err := generatePrivateKey(bitSize)
+func generate(public, private string) error {
+	privateKey, err := generatePrivateKey()
 	if err != nil {
 		return fmt.Errorf("failed to generate private SSH key: %w", err)
 	}
@@ -70,7 +73,10 @@ func generate(public, private string, bitSize int) error {
 		return fmt.Errorf("failed to generate public SSH key: %w", err)
 	}
 
-	privateKeyBytes := encodePrivateKeyToPEM(privateKey)
+	privateKeyBytes, err := encodePrivateKeyToPEM(privateKey)
+	if err != nil {
+		return fmt.Errorf("failed to encode private SSH key: %w", err)
+	}
 
 	if err := os.WriteFile(public, publicKeyBytes, 0600); err != nil {
 		return fmt.Errorf("failed to write public SSH key: %w", err)
@@ -84,44 +90,40 @@ func generate(public, private string, bitSize int) error {
 	return nil
 }
 
-func generatePrivateKey(bitSize int) (*rsa.PrivateKey, error) {
-	// Private Key generation
-	privateKey, err := rsa.GenerateKey(rand.Reader, bitSize)
-	if err != nil {
-		return nil, err
-	}
-
-	// Validate Private Key
-	err = privateKey.Validate()
+func generatePrivateKey() (*ecdsa.PrivateKey, error) {
+	// Generate ECDSA private key
+	privateKey, err := ecdsa.GenerateKey(curve, rand.Reader)
 	if err != nil {
 		return nil, err
 	}
-
 	return privateKey, nil
 }
 
-func encodePrivateKeyToPEM(privateKey *rsa.PrivateKey) []byte {
-	privDER := x509.MarshalPKCS1PrivateKey(privateKey)
+func encodePrivateKeyToPEM(privateKey *ecdsa.PrivateKey) ([]byte, error) {
+	// Marshal the private key to DER format
+	privDER, err := x509.MarshalECPrivateKey(privateKey)
+	if err != nil {
+		return nil, fmt.Errorf("failed to marshal ECDSA private key: %w", err)
+	}
 
+	// Create a PEM block
 	privBlock := pem.Block{
-		Type:    "RSA PRIVATE KEY",
-		Headers: nil,
-		Bytes:   privDER,
+		Type:  "EC PRIVATE KEY",
+		Bytes: privDER,
 	}
 
+	// Encode the PEM block to memory
 	privatePEM := pem.EncodeToMemory(&privBlock)
-
-	return privatePEM
+	return privatePEM, nil
 }
 
-func generatePublicKey(privatekey *rsa.PublicKey) ([]byte, error) {
-	publicRsaKey, err := ssh.NewPublicKey(privatekey)
+func generatePublicKey(privatekey *ecdsa.PublicKey) ([]byte, error) {
+	publicECDSAKey, err := ssh.NewPublicKey(privatekey)
 	if err != nil {
 		return nil, err
 	}
 
-	pubKeyBytes := ssh.MarshalAuthorizedKey(publicRsaKey)
-
+	pubKeyBytes := ssh.MarshalAuthorizedKey(publicECDSAKey)
 	return pubKeyBytes, nil
 }
 
diff --git a/pkg/ssh/ssh.go b/pkg/ssh/ssh.go
index 4482a074..3ea45b59 100644
--- a/pkg/ssh/ssh.go
+++ b/pkg/ssh/ssh.go
@@ -45,8 +45,8 @@ func add(path, name, iface string, port int) error {
 	host := newHost([]string{name}, []string{"entry generated by okteto"})
 	host.params = []*param{
 		newParam(forwardAgentKeyword, []string{"yes"}),
-		newParam(pubkeyAcceptedKeyTypesKeyword, []string{"+ssh-rsa"}),
-		newParam(hostKeyAlgorithms, []string{"+ssh-rsa"}),
+		newParam(pubkeyAcceptedKeyTypesKeyword, []string{"+ecdsa-sha2-nistp256"}),
+		newParam(hostKeyAlgorithms, []string{"+ecdsa-sha2-nistp256"}),
 		newParam(hostNameKeyword, []string{iface}),
 		newParam(portKeyword, []string{strconv.Itoa(port)}),
 		newParam(strictHostKeyCheckingKeyword, []string{"no"}),
diff --git a/pkg/types/interface.go b/pkg/types/interface.go
index 1d199781..3fd4a391 100644
--- a/pkg/types/interface.go
+++ b/pkg/types/interface.go
@@ -38,6 +38,7 @@ type UserInterface interface {
 	GetClusterCertificate(ctx context.Context, cluster, ns string) ([]byte, error)
 	GetClusterMetadata(ctx context.Context, ns string) (ClusterMetadata, error)
 	GetRegistryCredentials(ctx context.Context, host string) (dockertypes.AuthConfig, error)
+	GetExecutionEnv(ctx context.Context) (map[string]string, error)
 }
 
 // NamespaceInterface represents the client that connects to the namespace functions


Test patch:
diff --git a/cmd/deploy/deploy_test.go b/cmd/deploy/deploy_test.go
index b335432e..275d74aa 100644
--- a/cmd/deploy/deploy_test.go
+++ b/cmd/deploy/deploy_test.go
@@ -238,6 +238,7 @@ func (f *fakeDeployer) Get(ctx context.Context,
 	ioCtrl *io.Controller,
 	k8Logger *io.K8sLogger,
 	dependencyEnvVarsGetter dependencyEnvVarsGetter,
+	executionEnvVarGetter executionEnvVarsGetter,
 ) (Deployer, error) {
 	args := f.Called(ctx, opts, buildEnvVarsGetter, cmapHandler, k8sProvider, ioCtrl, k8Logger, dependencyEnvVarsGetter)
 	return args.Get(0).(Deployer), args.Error(1)
diff --git a/cmd/deploy/remote_test.go b/cmd/deploy/remote_test.go
index d081de11..12630bd5 100644
--- a/cmd/deploy/remote_test.go
+++ b/cmd/deploy/remote_test.go
@@ -165,7 +165,8 @@ func TestGetCommandFlags(t *testing.T) {
 func Test_newRemoteDeployer(t *testing.T) {
 	getBuildEnvVars := func() map[string]string { return nil }
 	getDependencyEnvVars := func(_ environGetter) map[string]string { return nil }
-	got := newRemoteDeployer(getBuildEnvVars, io.NewIOController(), getDependencyEnvVars)
+	getExecutionEvVars := func(_ context.Context) map[string]string { return make(map[string]string) }
+	got := newRemoteDeployer(getBuildEnvVars, io.NewIOController(), getDependencyEnvVars, getExecutionEvVars)
 	require.IsType(t, &remoteDeployer{}, got)
 	require.NotNil(t, got.getBuildEnvVars)
 }
@@ -199,6 +200,7 @@ func TestDeployRemote(t *testing.T) {
 		CommandFlags:        []string{"--name \"test\""},
 		BuildEnvVars:        map[string]string{"BUILD_VAR_1": "value"},
 		DependenciesEnvVars: map[string]string{"DEP_VAR_1": "value"},
+		ExecutionEnvVars:    map[string]string{"PLAT_VAR_1": "value"},
 		DockerfileName:      dockerfileTemporalName,
 		Deployable: deployable.Entity{
 			Divert:   manifest.Deploy.Divert,
@@ -219,6 +221,9 @@ func TestDeployRemote(t *testing.T) {
 		getDependencyEnvVars: func(environGetter) map[string]string {
 			return map[string]string{"DEP_VAR_1": "value"}
 		},
+		getExecutionEnvVars: func(ctx context.Context) map[string]string {
+			return map[string]string{"PLAT_VAR_1": "value"}
+		},
 	}
 	opts := &Options{
 		Name:             "test",
@@ -259,6 +264,7 @@ func TestDeployRemoteWithError(t *testing.T) {
 		CommandFlags:        []string{"--name \"test\""},
 		BuildEnvVars:        map[string]string{"BUILD_VAR_1": "value"},
 		DependenciesEnvVars: map[string]string{"DEP_VAR_1": "value"},
+		ExecutionEnvVars:    map[string]string{"PLAT_VAR_1": "value"},
 		DockerfileName:      dockerfileTemporalName,
 		Deployable: deployable.Entity{
 			Divert:   manifest.Deploy.Divert,
@@ -315,6 +321,9 @@ func TestDeployRemoteWithError(t *testing.T) {
 				getDependencyEnvVars: func(environGetter) map[string]string {
 					return map[string]string{"DEP_VAR_1": "value"}
 				},
+				getExecutionEnvVars: func(_ context.Context) map[string]string {
+					return map[string]string{"PLAT_VAR_1": "value"}
+				},
 			}
 			opts := &Options{
 				Name:             "test",
diff --git a/cmd/test/cmd.go b/cmd/test/cmd.go
index 2bd818b7..5c9315df 100644
--- a/cmd/test/cmd.go
+++ b/cmd/test/cmd.go
@@ -337,6 +337,7 @@ func doRun(ctx context.Context, servicesToTest []string, options *Options, ioCtr
 			CommandFlags:        commandFlags,
 			BuildEnvVars:        builder.GetBuildEnvVars(),
 			DependenciesEnvVars: deployCMD.GetDependencyEnvVars(os.Environ),
+			ExecutionEnvVars:    deployable.GetPlatformEnvironment(ctx),
 			DockerfileName:      "Dockerfile.test",
 			Deployable: deployable.Entity{
 				Commands: commands,
diff --git a/internal/test/client/user.go b/internal/test/client/user.go
index d1844c82..9b8bea54 100644
--- a/internal/test/client/user.go
+++ b/internal/test/client/user.go
@@ -71,3 +71,7 @@ func (c *FakeUserClient) GetClusterMetadata(_ context.Context, _ string) (types.
 func (*FakeUserClient) GetRegistryCredentials(_ context.Context, _ string) (dockertypes.AuthConfig, error) {
 	return dockertypes.AuthConfig{}, nil
 }
+
+func (*FakeUserClient) GetExecutionEnv(_ context.Context) (map[string]string, error) {
+	return map[string]string{}, nil
+}
diff --git a/pkg/okteto/secrets_test.go b/pkg/okteto/secrets_test.go
index 6d9de408..53eef6f3 100644
--- a/pkg/okteto/secrets_test.go
+++ b/pkg/okteto/secrets_test.go
@@ -752,3 +752,80 @@ func TestGetRegistryCredentials(t *testing.T) {
 		})
 	}
 }
+
+func TestGetExecutionEnv(t *testing.T) {
+	ctx := context.Background()
+
+	type input struct {
+		client *fakeGraphQLClient
+	}
+	type expected struct {
+		vars      map[string]string
+		expectErr bool
+	}
+	testCases := []struct {
+		name     string
+		cfg      input
+		expected expected
+	}{
+		{
+			name: "happy path",
+			cfg: input{
+				client: &fakeGraphQLClient{
+					queryResult: &getExecutionEnvQuery{
+						ExecutionEnv: []variablesQuery{
+							{Name: "A", Value: "A"},
+							{Name: "B", Value: "B"},
+							{Name: "C", Value: "C"},
+						},
+					},
+				},
+			},
+			expected: expected{
+				vars: map[string]string{
+					"A": "A",
+					"B": "B",
+					"C": "C",
+				},
+			},
+		},
+		{
+			name: "does not fail on incompatible schema",
+			cfg: input{
+				client: &fakeGraphQLClient{
+					err: errors.New("Cannot query field \"executionEnv\" on type \"Query\""),
+				},
+			},
+			expected: expected{
+				vars: map[string]string{},
+			},
+		},
+		{
+			name: "fails on error",
+			cfg: input{
+				client: &fakeGraphQLClient{
+					err: errors.New("this is my error"),
+				},
+			},
+			expected: expected{
+				vars:      map[string]string{},
+				expectErr: true,
+			},
+		},
+	}
+	for _, tc := range testCases {
+		tc := tc
+		t.Run(tc.name, func(t *testing.T) {
+			uc := &userClient{
+				client: tc.cfg.client,
+			}
+			result, err := uc.GetExecutionEnv(ctx)
+			if tc.expected.expectErr {
+				assert.Error(t, err)
+			} else {
+				assert.NoError(t, err)
+			}
+			assert.Equal(t, tc.expected.vars, result)
+		})
+	}
+}
diff --git a/pkg/remote/remote_test.go b/pkg/remote/remote_test.go
index 2ca1050c..e5cb9be8 100644
--- a/pkg/remote/remote_test.go
+++ b/pkg/remote/remote_test.go
@@ -324,6 +324,7 @@ func TestCreateDockerfile(t *testing.T) {
 			config: config{
 				params: &Params{
 					BaseImage:           "test-image",
+					ExecutionEnvVars:    map[string]string{"A": "A"},
 					Manifest:            fakeManifest,
 					BuildEnvVars:        map[string]string{"OKTETO_BUIL_SVC_IMAGE": "ONE_VALUE", "OKTETO_BUILD_SVC2_IMAGE": "TWO_VALUE"},
 					DependenciesEnvVars: map[string]string{"OKTETO_DEPENDENCY_DATABASE_VARIABLE_PASSWORD": "dependency_pass", "OKTETO_DEPENDENCY_DATABASE_VARIABLE_USERNAME": "dependency_user"},
@@ -382,6 +383,10 @@ ARG OKTETO_INVALIDATE_CACHE
 RUN echo "$OKTETO_INVALIDATE_CACHE" > /etc/.oktetocachekey
 RUN okteto registrytoken install --force --log-output=json
 
+
+ENV A A
+
+
 RUN \
   \
   --mount=type=secret,id=known_hosts --mount=id=remote,type=ssh \
@@ -469,6 +474,8 @@ ARG OKTETO_INVALIDATE_CACHE
 RUN echo "$OKTETO_INVALIDATE_CACHE" > /etc/.oktetocachekey
 RUN okteto registrytoken install --force --log-output=json
 
+
+
 RUN \
   \
   --mount=type=secret,id=known_hosts --mount=id=remote,type=ssh \
diff --git a/pkg/ssh/key_test.go b/pkg/ssh/key_test.go
index c51521ba..96c7f298 100644
--- a/pkg/ssh/key_test.go
+++ b/pkg/ssh/key_test.go
@@ -64,7 +64,7 @@ func Test_generate(t *testing.T) {
 
 	t.Setenv(constants.OktetoFolderEnvVar, dir)
 	public, private := getKeyPaths()
-	if err := generate(public, private, 128); err != nil {
+	if err := generate(public, private); err != nil {
 		t.Error(err)
 	}