Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to start & stop ssh-agent process #16761

Merged
merged 6 commits into from
Jun 30, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions cmd/minikube/cmd/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import (
"k8s.io/minikube/pkg/minikube/out"
"k8s.io/minikube/pkg/minikube/out/register"
"k8s.io/minikube/pkg/minikube/reason"
"k8s.io/minikube/pkg/minikube/sshagent"
"k8s.io/minikube/pkg/minikube/style"
)

Expand Down Expand Up @@ -93,6 +94,9 @@ var hostAndDirsDeleter = func(api libmachine.API, cc *config.ClusterConfig, prof
if err := killMountProcess(); err != nil {
out.FailureT("Failed to kill mount process: {{.error}}", out.V{"error": err})
}
if err := sshagent.Stop(profileName); err != nil {
out.FailureT("Failed to stop ssh-agent process: {{.error}}", out.V{"error": err})
}

deleteHosts(api, cc)

Expand Down
73 changes: 49 additions & 24 deletions cmd/minikube/cmd/docker-env.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,22 @@ var dockerEnvTCPTmpl = fmt.Sprintf(
"{{ if .NoProxyVar }}"+
"{{ .Prefix }}{{ .NoProxyVar }}{{ .Delimiter }}{{ .NoProxyValue }}{{ .Suffix }}"+
"{{ end }}"+
"{{ if .SSHAuthSock }}"+
"{{ .Prefix }}%s{{ .Delimiter }}{{ .SSHAuthSock }}{{ .Suffix }}"+
"{{ end }}"+
"{{ if .SSHAgentPID }}"+
"{{ .Prefix }}%s{{ .Delimiter }}{{ .SSHAgentPID }}{{ .Suffix }}"+
"{{ end }}"+
"{{ .UsageHint }}",
constants.DockerTLSVerifyEnv,
constants.DockerHostEnv,
constants.DockerCertPathEnv,
constants.ExistingDockerTLSVerifyEnv,
constants.ExistingDockerHostEnv,
constants.ExistingDockerCertPathEnv,
constants.MinikubeActiveDockerdEnv)
constants.MinikubeActiveDockerdEnv,
constants.SSHAuthSock,
constants.SSHAgentPID)
var dockerEnvSSHTmpl = fmt.Sprintf(
spowelljr marked this conversation as resolved.
Show resolved Hide resolved
"{{ .Prefix }}%s{{ .Delimiter }}{{ .DockerHost }}{{ .Suffix }}"+
"{{ .Prefix }}%s{{ .Delimiter }}{{ .MinikubeDockerdProfile }}{{ .Suffix }}"+
Expand All @@ -99,6 +107,9 @@ type DockerShellConfig struct {
ExistingDockerCertPath string
ExistingDockerHost string
ExistingDockerTLSVerify string

SSHAuthSock string
SSHAgentPID string
}

var (
Expand Down Expand Up @@ -142,6 +153,9 @@ func dockerShellCfgSet(ec DockerEnvConfig, envMap map[string]string) *DockerShel

s.MinikubeDockerdProfile = envMap[constants.MinikubeActiveDockerdEnv]

s.SSHAuthSock = envMap[constants.SSHAuthSock]
s.SSHAgentPID = envMap[constants.SSHAgentPID]

if ec.noProxy {
noProxyVar, noProxyValue := defaultNoProxyGetter.GetNoProxyVar()

Expand Down Expand Up @@ -316,18 +330,20 @@ docker-cli install instructions: https://minikube.sigs.k8s.io/docs/tutorials/doc

hostIP := co.CP.IP.String()
ec := DockerEnvConfig{
EnvConfig: sh,
profile: cname,
driver: driverName,
ssh: sshHost,
hostIP: hostIP,
port: port,
certsDir: localpath.MakeMiniPath("certs"),
noProxy: noProxy,
username: d.GetSSHUsername(),
hostname: hostname,
sshport: sshport,
keypath: d.GetSSHKeyPath(),
EnvConfig: sh,
profile: cname,
driver: driverName,
ssh: sshHost,
hostIP: hostIP,
port: port,
certsDir: localpath.MakeMiniPath("certs"),
noProxy: noProxy,
username: d.GetSSHUsername(),
hostname: hostname,
sshport: sshport,
keypath: d.GetSSHKeyPath(),
sshAuthSock: co.Config.SSHAuthSock,
sshAgentPID: co.Config.SSHAgentPID,
}

dockerPath, err := exec.LookPath("docker")
Expand Down Expand Up @@ -371,17 +387,19 @@ docker-cli install instructions: https://minikube.sigs.k8s.io/docs/tutorials/doc
// DockerEnvConfig encapsulates all external inputs into shell generation for Docker
type DockerEnvConfig struct {
shell.EnvConfig
profile string
driver string
ssh bool
hostIP string
port int
certsDir string
noProxy bool
username string
hostname string
sshport int
keypath string
profile string
driver string
ssh bool
hostIP string
port int
certsDir string
noProxy bool
username string
hostname string
sshport int
keypath string
sshAuthSock string
sshAgentPID int
}

// dockerSetScript writes out a shell-compatible 'docker-env' script
Expand Down Expand Up @@ -497,11 +515,18 @@ func sshURL(username string, hostname string, port int) string {

// dockerEnvVars gets the necessary docker env variables to allow the use of minikube's docker daemon
func dockerEnvVars(ec DockerEnvConfig) map[string]string {
agentPID := strconv.Itoa(ec.sshAgentPID)
// set agentPID to nil value if not set
if agentPID == "0" {
agentPID = ""
}
envTCP := map[string]string{
constants.DockerTLSVerifyEnv: "1",
constants.DockerHostEnv: dockerURL(ec.hostIP, ec.port),
constants.DockerCertPathEnv: ec.certsDir,
constants.MinikubeActiveDockerdEnv: ec.profile,
constants.SSHAuthSock: ec.sshAuthSock,
constants.SSHAgentPID: agentPID,
}
envSSH := map[string]string{
constants.DockerHostEnv: sshURL(ec.username, ec.hostname, ec.sshport),
Expand Down
14 changes: 10 additions & 4 deletions cmd/minikube/cmd/docker-env_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -314,12 +314,14 @@ MINIKUBE_ACTIVE_DOCKERD
{
"none",
"text",
DockerEnvConfig{profile: "nonetext", driver: "docker", hostIP: "127.0.0.1", port: 32842, certsDir: "/certs"},
DockerEnvConfig{profile: "nonetext", driver: "docker", hostIP: "127.0.0.1", port: 32842, certsDir: "/certs", sshAuthSock: "/var/folders/9l/6wpxv6wd1b901m1146r579wc00rqw3/T//ssh-KCQt1sNqrCPI/agent.29227", sshAgentPID: 29228},
nil,
`DOCKER_TLS_VERIFY=1
DOCKER_HOST=tcp://127.0.0.1:32842
DOCKER_CERT_PATH=/certs
MINIKUBE_ACTIVE_DOCKERD=nonetext
SSH_AUTH_SOCK=/var/folders/9l/6wpxv6wd1b901m1146r579wc00rqw3/T//ssh-KCQt1sNqrCPI/agent.29227
SSH_AGENT_PID=29228
`,
`DOCKER_TLS_VERIFY
DOCKER_HOST
Expand All @@ -338,13 +340,15 @@ MINIKUBE_ACTIVE_DOCKERD
{
"none",
"json",
DockerEnvConfig{profile: "nonejson", driver: "docker", hostIP: "127.0.0.1", port: 32842, certsDir: "/certs"},
DockerEnvConfig{profile: "nonejson", driver: "docker", hostIP: "127.0.0.1", port: 32842, certsDir: "/certs", sshAuthSock: "/var/folders/9l/6wpxv6wd1b901m1146r579wc00rqw3/T//ssh-KCQt1sNqrCPI/agent.29227", sshAgentPID: 29228},
nil,
`{
"DOCKER_TLS_VERIFY": "1",
"DOCKER_HOST": "tcp://127.0.0.1:32842",
"DOCKER_CERT_PATH": "/certs",
"MINIKUBE_ACTIVE_DOCKERD": "nonejson"
"MINIKUBE_ACTIVE_DOCKERD": "nonejson",
"SSH_AUTH_SOCK": "/var/folders/9l/6wpxv6wd1b901m1146r579wc00rqw3/T//ssh-KCQt1sNqrCPI/agent.29227",
"SSH_AGENT_PID": "29228"
}`,
`[
"DOCKER_TLS_VERIFY",
Expand All @@ -367,12 +371,14 @@ MINIKUBE_ACTIVE_DOCKERD
{
"none",
"yaml",
DockerEnvConfig{profile: "noneyaml", driver: "docker", hostIP: "127.0.0.1", port: 32842, certsDir: "/certs"},
DockerEnvConfig{profile: "noneyaml", driver: "docker", hostIP: "127.0.0.1", port: 32842, certsDir: "/certs", sshAuthSock: "/var/folders/9l/6wpxv6wd1b901m1146r579wc00rqw3/T//ssh-KCQt1sNqrCPI/agent.29227", sshAgentPID: 29228},
nil,
`DOCKER_TLS_VERIFY: "1"
DOCKER_HOST: tcp://127.0.0.1:32842
DOCKER_CERT_PATH: /certs
MINIKUBE_ACTIVE_DOCKERD: noneyaml
SSH_AUTH_SOCK: /var/folders/9l/6wpxv6wd1b901m1146r579wc00rqw3/T//ssh-KCQt1sNqrCPI/agent.29227
SSH_AGENT_PID: "29228"
`,
`- DOCKER_TLS_VERIFY
- DOCKER_HOST
Expand Down
2 changes: 2 additions & 0 deletions pkg/minikube/config/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ type ClusterConfig struct {
SocketVMnetClientPath string
SocketVMnetPath string
StaticIP string
SSHAuthSock string
SSHAgentPID int
}

// KubernetesConfig contains the parameters used to configure the VM Kubernetes.
Expand Down
4 changes: 4 additions & 0 deletions pkg/minikube/constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ const (
// MinikubeActiveDockerdEnv holds the docker daemon which user's shell is pointing at
// value would be profile or empty if pointing to the user's host daemon.
MinikubeActiveDockerdEnv = "MINIKUBE_ACTIVE_DOCKERD"
// SSHAuthSock is used for docker-env
SSHAuthSock = "SSH_AUTH_SOCK"
// SSHAgentPID is used for docker-env
SSHAgentPID = "SSH_AGENT_PID"
// PodmanVarlinkBridgeEnv is used for podman settings
PodmanVarlinkBridgeEnv = "PODMAN_VARLINK_BRIDGE"
// PodmanContainerHostEnv is used for podman settings
Expand Down
137 changes: 137 additions & 0 deletions pkg/minikube/sshagent/sshagent.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/*
Copyright 2023 The Kubernetes Authors All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
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 sshagent

import (
"context"
"fmt"
"os"
"os/exec"
"regexp"
"runtime"
"strconv"
"strings"
"time"

"github.com/mitchellh/go-ps"
"k8s.io/klog/v2"
"k8s.io/minikube/pkg/minikube/config"
)

type sshAgent struct {
authSock string
agentPID int
}

// Start starts an ssh-agent process
func Start(profile string) error {
if runtime.GOOS == "windows" {
return fmt.Errorf("starting an SSH agent on Windows is not yet supported")
}
cc, err := config.Load(profile)
if err != nil {
return fmt.Errorf("failed loading config: %v", err)
}
running, err := isRunning(cc)
if err != nil {
return fmt.Errorf("failed checking if SSH agent is running: %v", err)
}
if running {
klog.Info("SSH agent is already running, aborting start")
return nil
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
out, err := exec.CommandContext(ctx, "ssh-agent").CombinedOutput()
if err != nil {
return fmt.Errorf("failed starting ssh-agent: %s: %v", string(out), err)
}
parsed, err := parseOutput(string(out))
if err != nil {
return fmt.Errorf("failed to parse ssh-agent output: %v", err)
}
cc.SSHAuthSock = parsed.authSock
cc.SSHAgentPID = parsed.agentPID
if err := config.Write(profile, cc); err != nil {
return fmt.Errorf("failed writing config: %v", err)
}
return nil
}

func parseOutput(out string) (*sshAgent, error) {
sockSubmatches := regexp.MustCompile(`SSH_AUTH_SOCK=(.*?);`).FindStringSubmatch(out)
if len(sockSubmatches) < 2 {
return nil, fmt.Errorf("SSH_AUTH_SOCK not found in output: %s", out)
}
pidSubmatches := regexp.MustCompile(`SSH_AGENT_PID=(.*?);`).FindStringSubmatch(out)
if len(pidSubmatches) < 2 {
return nil, fmt.Errorf("SSH_AGENT_PID not found in output: %s", out)
}
pid, err := strconv.Atoi(pidSubmatches[1])
if err != nil {
return nil, fmt.Errorf("failed to convert pid to int: %v", err)
}
return &sshAgent{sockSubmatches[1], pid}, nil
}

func isRunning(cc *config.ClusterConfig) (bool, error) {
if cc.SSHAgentPID == 0 {
return false, nil
}
entry, err := ps.FindProcess(cc.SSHAgentPID)
if err != nil {
return false, fmt.Errorf("failed finding process: %v", err)
}
if entry == nil {
return false, nil
}
return strings.Contains(entry.Executable(), "ssh-agent"), nil
}

// Stop stops an ssh-agent process
func Stop(profile string) error {
cc, err := config.Load(profile)
if err != nil {
return fmt.Errorf("failed loading config: %v", err)
}
running, err := isRunning(cc)
if err != nil {
return fmt.Errorf("failed checking if SSH agent is running: %v", err)
}
if running {
if err := killProcess(cc.SSHAgentPID); err != nil {
return fmt.Errorf("failed killing SSH agent process: %v", err)
}
}
cc.SSHAuthSock = ""
cc.SSHAgentPID = 0
if err := config.Write(profile, cc); err != nil {
return fmt.Errorf("failed writing config: %v", err)
}
return nil
}

func killProcess(pid int) error {
proc, err := os.FindProcess(pid)
if err != nil {
return fmt.Errorf("failed finding process: %v", err)
}
if err := proc.Kill(); err != nil {
return fmt.Errorf("failed killing process: %v", err)
}
return nil
}