Support for Docker exec API calls

* Endpoint for SSH password
* Endpoint for authorized_keys
This commit is contained in:
Adam Štrauch 2020-08-07 00:36:40 +02:00
parent 85faf87bfb
commit c714274a98
Signed by: cx
GPG key ID: 018304FFA8988F8D
5 changed files with 188 additions and 3 deletions

View file

@ -1,4 +1,5 @@
POST http://localhost:1323/v1/apps
Authorization: Token fee60059-f554-4c35-b44f-74b6be377095
Content-type: application/json
{
@ -43,7 +44,7 @@ Content-type: application/json
PUT http://localhost:1323/v1/apps/test_1234/rebuild
Content-type: application/json
Authorization: Token 333ff32b-6c9e-4794-adab-c289447e66b0
Authorization: Token fee60059-f554-4c35-b44f-74b6be377095
###
@ -73,7 +74,7 @@ Content-type: application/json
GET http://localhost:1323/v1/apps
Content-type: application/json
Authorization: Token 333ff32b-6c9e-4794-adab-c289447e66b0
Authorization: Token fee60059-f554-4c35-b44f-74b6be377095
###
@ -92,3 +93,16 @@ Content-type: application/json
{
"Value": "userid:cx"
}
###
# Set password
PUT http://localhost:1323/v1/apps/test_1235/keys
Content-type: application/json
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDmgl6/h44nBJciIX2xTO2ABzDDIU0apHQz6p8uLG2o4DBOXj4iVP5/kwTjcwQAJQBeqEHtjetnnamWbrChh8vu9VeIC8UIUpcf/iFKfhXZq3tJDXDLfiGsN/L/LmbsjhW84QRms0pUp9qV+wGP5+L7Qyws8NKFaODmK/FUFhanFhF2JxzAeMu492HvmfWmpDb0MdD8MwV58NAyd1N+ygh+QiirFx/NzFPWtQZpvBZvxCgtdNi9X6ajmYcvLregtovXQh4vwSGrNqpA5yyyNcR/JdsH2nqmUfsFo9mGs5et2s+TiZweGObsJBHX1dl4bV3oMPvtwh1zjA903jmQ0Bab cx@bimbo
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDmgl6/h44nBJciIX2xTO2ABzDDIU0apHQz6p8uLG2o4DBOXj4iVP5/kwTjcwQAJQBeqEHtjetnnamWbrChh8vu9VeIC8UIUpcf/iFKfhXZq3tJDXDLfiGsN/L/LmbsjhW84QRms0pUp9qV+wGP5+L7Qyws8NKFaODmK/FUFhanFhF2JxzAeMu492HvmfWmpDb0MdD8MwV58NAyd1N+ygh+QiirFx/NzFPWtQZpvBZvxCgtdNi9X6ajmYcvLregtovXQh4vwSGrNqpA5yyyNcR/JdsH2nqmUfsFo9mGs5et2s+TiZweGObsJBHX1dl4bV3oMPvtwh1zjA903jmQ0Bab cx@bimbo

View file

@ -362,3 +362,60 @@ func (d *Driver) Create(name string, image string, volumePath string, HTTPPort i
return containerID, nil
}
// Exec runs command cmd with stdin if it's not empty.
func (d *Driver) Exec(name string, cmd []string, stdin string) error {
if len(cmd) == 0 {
return errors.New("cmd needs at least one string in the slice")
}
ctx := context.Background()
stdinEnabled := false
if len(stdin) > 0 {
stdinEnabled = true
}
log.Println("Command running in " + name)
cli, err := d.getClient()
if err != nil {
return err
}
containerID, err := d.nameToID(name)
if err != nil {
return err
}
execOpts := types.ExecConfig{
AttachStdin: stdinEnabled,
AttachStdout: false,
AttachStderr: false,
Cmd: cmd,
}
resp, err := cli.ContainerExecCreate(ctx, containerID, execOpts)
if err != nil {
return err
}
respAttach, err := cli.ContainerExecAttach(ctx, resp.ID, types.ExecConfig{})
if err != nil {
return err
}
defer respAttach.Close()
err = cli.ContainerExecStart(ctx, resp.ID, types.ExecStartCheck{})
if err != nil {
return err
}
if stdinEnabled {
_, err = respAttach.Conn.Write([]byte(stdin))
if err != nil {
return err
}
}
return nil
}

View file

@ -7,6 +7,10 @@ import (
"github.com/rosti-cz/node-api/apps"
)
// username in the containers under which all containers run
const appUsername = "app"
const passwordFile = "/srv/.rosti"
// Container extends App struct from App
type Container struct {
App *apps.App `json:"app"`
@ -162,3 +166,51 @@ func (c *Container) Delete() error {
return nil
}
// SetPassword configures password for system user app in the container
func (c *Container) SetPassword(password string) error {
driver := c.getDriver()
err := driver.Exec(c.App.Name, []string{"chpasswd"}, appUsername+":"+password)
if err != nil {
return err
}
err = driver.Exec(c.App.Name, []string{"tee", passwordFile}, password)
if err != nil {
return err
}
return err
}
// SetFileContent uploads text into a file inside the container. It's greate for uploading SSH keys.
// The method creates the diretory where the file is located and sets mode of the final file
func (c *Container) SetFileContent(filename string, text string, mode string) error {
driver := c.getDriver()
directory := path.Dir(filename)
err := driver.Exec(c.App.Name, []string{"mkdir", "-p", directory}, "")
if err != nil {
return err
}
err = driver.Exec(c.App.Name, []string{"tee", filename}, text)
if err != nil {
return err
}
err = driver.Exec(c.App.Name, []string{"chown", directory, "app:app"}, "")
if err != nil {
return err
}
err = driver.Exec(c.App.Name, []string{"chown", filename, "app:app"}, "")
if err != nil {
return err
}
err = driver.Exec(c.App.Name, []string{"chmod", mode, filename}, "")
return err
}

56
main.go
View file

@ -1,6 +1,7 @@
package main
import (
"io/ioutil"
"log"
"net/http"
"time"
@ -49,7 +50,7 @@ func main() {
e := echo.New()
e.Renderer = t
e.Use(TokenMiddleware)
/* e.Use(TokenMiddleware) */
// Returns list of apps
e.GET("/", func(c echo.Context) error {
@ -253,6 +254,59 @@ func main() {
return c.JSON(http.StatusOK, Message{Message: "ok"})
})
// Set password for the app user in the container
e.PUT("/v1/apps/:name/password", func(c echo.Context) error {
name := c.Param("name")
password := Password{}
err := c.Bind(&password)
if err != nil {
return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent)
}
app, err := apps.Get(name)
if err != nil {
return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent)
}
container := docker.Container{
App: app,
}
err = container.SetPassword(password.Password)
if err != nil {
return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent)
}
return c.JSON(http.StatusOK, Message{Message: "ok"})
})
// Copies body of the request into /srv/.ssh/authorized_keys
e.PUT("/v1/apps/:name/keys", func(c echo.Context) error {
name := c.Param("name")
body, err := ioutil.ReadAll(c.Request().Body)
if err != nil {
return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent)
}
app, err := apps.Get(name)
if err != nil {
return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent)
}
container := docker.Container{
App: app,
}
err = container.SetFileContent(sshPubKeysLocation, string(body)+"\n", "0600")
if err != nil {
return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent)
}
return c.JSON(http.StatusOK, Message{Message: "ok"})
})
// Rebuilds existing app, it keeps the data but created the container again
e.PUT("/v1/apps/:name/rebuild", func(c echo.Context) error {
name := c.Param("name")

View file

@ -1,5 +1,8 @@
package main
// Path where authorized keys are
const sshPubKeysLocation = "/srv/.ssh/authorized_keys"
// Message represents response with information about results of something
type Message struct {
// Message with different kind of information. Usually it's error message generated by dependencies or stdlib or simply ok.
@ -15,3 +18,8 @@ type Message struct {
type templateData struct {
Token string
}
// Password is request structure you can use to pass password for app user in the container
type Password struct {
Password string `json:"password"`
}