From 0c943e604f0d08a7f7950876cc3c3d45dd02c2e8 Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Wed, 14 Aug 2019 11:30:47 +0900 Subject: [PATCH] Add support for acquiring shell environment --- buildsrht-keys | 16 +++++++++++++ buildsrht-shell | 52 +++++++++++++++++++++++++++++++++++++++++++ buildsrht/manifest.py | 12 ++++++++-- setup.py | 4 +++- worker/context.go | 5 +++++ worker/http.go | 35 ++++++++++++++++++++++++----- worker/manifest.go | 1 + 7 files changed, 117 insertions(+), 8 deletions(-) create mode 100755 buildsrht-keys create mode 100755 buildsrht-shell diff --git a/buildsrht-keys b/buildsrht-keys new file mode 100755 index 0000000..e427b41 --- /dev/null +++ b/buildsrht-keys @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# We just let everyone in at this stage, authentication is done later on. +from srht.config import cfg +import sys +import os + +key_type = sys.argv[3] +b64key = sys.argv[4] + +default_shell = os.path.join(os.path.dirname(sys.argv[0]), "buildsrht-shell") +shell = cfg("git.sr.ht", "shell", default=default_shell) +keys = ("command=\"{} '{}'\",".format(shell, b64key) + + "no-port-forwarding,no-X11-forwarding,no-agent-forwarding" + + " {} {} somebody".format(key_type, b64key) + "\n") +print(keys) +sys.exit(0) diff --git a/buildsrht-shell b/buildsrht-shell new file mode 100755 index 0000000..c74c406 --- /dev/null +++ b/buildsrht-shell @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +from srht.config import cfg, get_origin +import os +import requests +import shlex +import sys + +def fail(reason): + owner = cfg("sr.ht", "owner-name") + email = cfg("sr.ht", "owner-email") + print(reason) + print(f"Please reach out to {owner} <{email}> for support.") + sys.exit(1) + +b64key = sys.argv[1] + +cmd = os.environ.get("SSH_ORIGINAL_COMMAND") or "" +cmd = shlex.split(cmd) +if len(cmd) != 2: + fail("Usage: ssh ... connect ") +op = cmd[0] +if op not in ["connect"]: + fail("Usage: ssh ... connect ") +job_id = int(cmd[1]) + +r = requests.get(f"http://localhost:8080/job/{job_id}/info") +if r.status_code != 200: + fail("No such job found.") +info = r.json() + +meta_origin = get_origin("meta.sr.ht") +r = requests.get(f"{meta_origin}/api/ssh-key/{b64key}") +if r.status_code == 200: + username = r.json()["owner"]["name"] +else: + fail("Temporary authentication failure. Try again later.") + +if username != info["username"]: + fail("You are not permitted to connect to this job.") + +sys.stdout.flush() +sys.stderr.flush() +tty = os.open("/dev/tty", os.O_RDWR) +os.dup2(0, tty) +os.execvp("ssh", [ + "ssh", "-qt", + "-p", str(info["port"]), + "-o", "UserKnownHostsFile=/dev/null", + "-o", "StrictHostKeyChecking=no", + "-o", "LogLevel=quiet", + "build@localhost", "bash" +]) diff --git a/buildsrht/manifest.py b/buildsrht/manifest.py index 90916e5..41509f8 100644 --- a/buildsrht/manifest.py +++ b/buildsrht/manifest.py @@ -58,10 +58,11 @@ class Manifest: sources = self.yaml.get("sources") env = self.yaml.get("environment") secrets = self.yaml.get("secrets") + shell = self.yaml.get("shell") if not image: raise Exception("Missing image in manifest") if not isinstance(image, str): - raise Exception("Expected imagease to be a string") + raise Exception("Expected image to be a string") if packages: if not isinstance(packages, list) or not all([isinstance(p, str) for p in packages]): raise Exception("Expected packages to be a string array") @@ -82,6 +83,8 @@ class Manifest: raise Exception("Expected secrets to be a UUID array") # Will throw exception on invalid UUIDs as well secrets = list(map(uuid.UUID, secrets)) + if not isinstance(shell, bool): + raise Exception("Expected shell to be a boolean") self.image = image self.arch = arch self.packages = packages @@ -89,9 +92,13 @@ class Manifest: self.sources = sources self.environment = env self.secrets = secrets + self.shell = shell tasks = self.yaml.get("tasks") if not tasks or not isinstance(tasks, list): - raise Exception("Attempted to create manifest with no tasks") + if (tasks is None or tasks == []) and not self.shell: + raise Exception("Attempted to create manifest with no tasks") + else: + tasks = [] self.tasks = [Task(t) for t in tasks] for task in self.tasks: if len([t for t in self.tasks if t.name == task.name]) != 1: @@ -113,6 +120,7 @@ class Manifest: "secrets": [str(s) for s in self.secrets] if self.secrets else None, "tasks": [{ t.name: t.script } for t in self.tasks], "triggers": [t.to_dict() for t in self.triggers] if any(self.triggers) else None, + "shell": self.shell, } def to_yaml(self): diff --git a/setup.py b/setup.py index 7613aa8..1502cc1 100755 --- a/setup.py +++ b/setup.py @@ -68,6 +68,8 @@ setup( ] }, scripts = [ - 'buildsrht-migrate' + 'buildsrht-keys', + 'buildsrht-migrate', + 'buildsrht-shell', ] ) diff --git a/worker/context.go b/worker/context.go index c2812ec..67e3a3a 100644 --- a/worker/context.go +++ b/worker/context.go @@ -173,6 +173,11 @@ func (wctx *WorkerContext) RunBuild( } } + if manifest.Shell { + ctx.Log.Println("TODO: Print shell access details here") + <-goctx.Done() + } + jobsMutex.Lock() delete(jobs, job_id) jobsMutex.Unlock() diff --git a/worker/http.go b/worker/http.go index f6a1b96..3d9d9bd 100644 --- a/worker/http.go +++ b/worker/http.go @@ -1,6 +1,7 @@ package main import ( + "encoding/json" "net/http" "fmt" @@ -19,13 +20,37 @@ func HttpServer() { w.Write([]byte("404 not found")) return } - if r.Method != "POST" { - w.WriteHeader(405) - w.Write([]byte("405 method not allowed")) - return - } switch op { + case "info": + if r.Method != "GET" { + w.WriteHeader(405) + w.Write([]byte("405 method not allowed")) + return + } + if job, ok := jobs[jobId]; ok { + w.WriteHeader(200) + bytes, _ := json.Marshal(struct { + Note *string `json:"note"` + OwnerId int `json:"owner_id"` + Port int `json:"port"` + Username string `json:"username"` + } { + Note: job.Job.Note, + OwnerId: job.Job.OwnerId, + Port: job.Port, + Username: job.Job.Username, + }) + w.Write(bytes) + } else { + w.WriteHeader(404) + w.Write([]byte("404 not found")) + } case "cancel": + if r.Method != "POST" { + w.WriteHeader(405) + w.Write([]byte("405 method not allowed")) + return + } jobsMutex.Lock() defer jobsMutex.Unlock() if job, ok := jobs[jobId]; ok { diff --git a/worker/manifest.go b/worker/manifest.go index aec3942..130c16d 100644 --- a/worker/manifest.go +++ b/worker/manifest.go @@ -7,6 +7,7 @@ type Manifest struct { Packages []string Repositories map[string]string Secrets []string + Shell bool Sources []string Tasks []map[string]string Triggers []map[string]interface{} -- 2.38.5