github.com/drud/ddev@v1.21.5-alpha1.0.20230226034409-94fcc4b94453/pkg/ddevapp/ssh_auth.go (about) 1 package ddevapp 2 3 import ( 4 "bytes" 5 "fmt" 6 "github.com/drud/ddev/pkg/dockerutil" 7 "github.com/drud/ddev/pkg/globalconfig" 8 "github.com/drud/ddev/pkg/util" 9 "github.com/drud/ddev/pkg/versionconstants" 10 "github.com/fsouza/go-dockerclient" 11 "os" 12 "path" 13 "path/filepath" 14 "text/template" 15 ) 16 17 // SSHAuthName is the "machine name" of the ddev-ssh-agent docker-compose service 18 const SSHAuthName = "ddev-ssh-agent" 19 20 // SSHAuthComposeYAMLPath returns the filepath to the base .ssh-auth-compose yaml file. 21 func SSHAuthComposeYAMLPath() string { 22 globalDir := globalconfig.GetGlobalDdevDir() 23 dest := path.Join(globalDir, ".ssh-auth-compose.yaml") 24 return dest 25 } 26 27 // FullRenderedSSHAuthComposeYAMLPath returns the filepath to the rendered 28 // .ssh-auth-compose-full.yaml file. 29 func FullRenderedSSHAuthComposeYAMLPath() string { 30 globalDir := globalconfig.GetGlobalDdevDir() 31 dest := path.Join(globalDir, ".ssh-auth-compose-full.yaml") 32 return dest 33 } 34 35 // EnsureSSHAgentContainer ensures the ssh-auth container is running. 36 func (app *DdevApp) EnsureSSHAgentContainer() error { 37 sshContainer, err := findDdevSSHAuth() 38 if err != nil { 39 return err 40 } 41 // If we already have a running ssh container, there's nothing to do. 42 if sshContainer != nil && (sshContainer.State == "running" || sshContainer.State == "starting") { 43 return nil 44 } 45 46 dockerutil.EnsureDdevNetwork() 47 48 path, err := app.CreateSSHAuthComposeFile() 49 if err != nil { 50 return err 51 } 52 53 app.DockerEnv() 54 55 // run docker-compose up -d 56 // This will force-recreate, discarding existing auth if there is a stopped container. 57 _, _, err = dockerutil.ComposeCmd([]string{path}, "-p", SSHAuthName, "up", "--build", "--force-recreate", "-d") 58 if err != nil { 59 return fmt.Errorf("failed to start ddev-ssh-agent: %v", err) 60 } 61 62 // ensure we have a happy sshAuth 63 label := map[string]string{"com.docker.compose.project": SSHAuthName} 64 sshWaitTimeout := 60 65 _, err = dockerutil.ContainerWait(sshWaitTimeout, label) 66 if err != nil { 67 return fmt.Errorf("ddev-ssh-agent failed to become ready; debug with 'docker logs ddev-ssh-agent'; error: %v", err) 68 } 69 70 util.Warning("ssh-agent container is running: If you want to add authentication to the ssh-agent container, run 'ddev auth ssh' to enable your keys.") 71 return nil 72 } 73 74 // RemoveSSHAgentContainer brings down the ddev-ssh-agent if it's running. 75 func RemoveSSHAgentContainer() error { 76 // Stop the container if it exists 77 err := dockerutil.RemoveContainer(globalconfig.DdevSSHAgentContainer, 0) 78 if err != nil { 79 if _, ok := err.(*docker.NoSuchContainer); !ok { 80 return err 81 } 82 } 83 util.Warning("The ddev-ssh-agent container has been removed. When you start it again you will have to use 'ddev auth ssh' to provide key authentication again.") 84 return nil 85 } 86 87 // CreateSSHAuthComposeFile creates the docker-compose file for the ddev-ssh-agent 88 func (app *DdevApp) CreateSSHAuthComposeFile() (string, error) { 89 90 var doc bytes.Buffer 91 f, ferr := os.Create(SSHAuthComposeYAMLPath()) 92 if ferr != nil { 93 return "", ferr 94 } 95 defer util.CheckClose(f) 96 97 context := "./.sshimageBuild" 98 err := WriteBuildDockerfile(filepath.Join(globalconfig.GetGlobalDdevDir(), context, "Dockerfile"), "", nil, "", "") 99 if err != nil { 100 return "", err 101 } 102 103 uid, gid, username := util.GetContainerUIDGid() 104 105 app.DockerEnv() 106 107 templateVars := map[string]interface{}{ 108 "ssh_auth_image": versionconstants.SSHAuthImage, 109 "ssh_auth_tag": versionconstants.SSHAuthTag, 110 "AutoRestartContainers": globalconfig.DdevGlobalConfig.AutoRestartContainers, 111 "Username": username, 112 "UID": uid, 113 "GID": gid, 114 "BuildContext": context, 115 } 116 t, err := template.New("ssh_auth_compose_template.yaml").ParseFS(bundledAssets, "ssh_auth_compose_template.yaml") 117 if err != nil { 118 return "", err 119 } 120 err = t.Execute(&doc, templateVars) 121 util.CheckErr(err) 122 _, err = f.WriteString(doc.String()) 123 util.CheckErr(err) 124 125 fullHandle, err := os.Create(FullRenderedSSHAuthComposeYAMLPath()) 126 if err != nil { 127 return "", err 128 } 129 130 userFiles, err := filepath.Glob(filepath.Join(globalconfig.GetGlobalDdevDir(), "ssh-auth-compose.*.yaml")) 131 if err != nil { 132 return "", err 133 } 134 files := append([]string{SSHAuthComposeYAMLPath()}, userFiles...) 135 fullContents, _, err := dockerutil.ComposeCmd(files, "config") 136 if err != nil { 137 return "", err 138 } 139 _, err = fullHandle.WriteString(fullContents) 140 if err != nil { 141 return "", err 142 } 143 return FullRenderedSSHAuthComposeYAMLPath(), nil 144 } 145 146 // findDdevSSHAuth uses FindContainerByLabels to get our sshAuth container and 147 // return it (or nil if it doesn't exist yet) 148 func findDdevSSHAuth() (*docker.APIContainers, error) { 149 containerQuery := map[string]string{ 150 "com.docker.compose.project": SSHAuthName, 151 } 152 153 container, err := dockerutil.FindContainerByLabels(containerQuery) 154 if err != nil { 155 return nil, fmt.Errorf("failed to execute findContainersByLabels, %v", err) 156 } 157 return container, nil 158 } 159 160 // RenderSSHAuthStatus returns a user-friendly string showing sshAuth-status 161 func RenderSSHAuthStatus() string { 162 status := GetSSHAuthStatus() 163 var renderedStatus string 164 165 switch status { 166 case "healthy": 167 renderedStatus = util.ColorizeText(status, "green") 168 case "exited": 169 fallthrough 170 default: 171 renderedStatus = util.ColorizeText(status, "red") 172 } 173 return fmt.Sprintf("\nssh-auth status: %v", renderedStatus) 174 } 175 176 // GetSSHAuthStatus outputs sshAuth status and warning if not 177 // running or healthy, as applicable. 178 func GetSSHAuthStatus() string { 179 label := map[string]string{"com.docker.compose.project": SSHAuthName} 180 container, err := dockerutil.FindContainerByLabels(label) 181 182 if err != nil { 183 util.Error("Failed to execute FindContainerByLabels(%v): %v", label, err) 184 return SiteStopped 185 } 186 if container == nil { 187 return SiteStopped 188 } 189 health, _ := dockerutil.GetContainerHealth(container) 190 return health 191 192 }