-
Notifications
You must be signed in to change notification settings - Fork 12
/
setup-ssh
executable file
Β·180 lines (153 loc) Β· 6.41 KB
/
setup-ssh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
#!/bin/bash -e
#
# GNU Bash required for process substitution `<()` later.
#
# Environment variables:
#
# - `GITHUB_ACTION_PATH`: path to this repository.
# - `GITHUB_ACTOR`: GitHub username of whoever triggered the action.
# - `GITHUB_WORKSPACE`: default path for the workflow (the tmux session will start there).
# - `IX_USERNAME`: (optional) The username for ix.io where SSH connection string will be dropped
# - `IX_PASSWORD`: (optional) The password for ix.io
#
notify_func() {
cat
}
#
# ix.io notification routine: save the URL of the paste in `/tmp/ix-io.txt` so
# we can delete the paste from the ix.io server on session end.
#
if [ ! -z "$IX_USERNAME" ] && [ ! -z "$IX_PASSWORD" ]; then
notify_func() {
# `f:1=<-` will set the value of the `f:1` form field with data from `stdin`
tee >(curl -u "$IX_USERNAME:$IX_PASSWORD" -F 'f:1=<-' http://ix.io > /tmp/ix-io.txt)
}
fi
# Check non-coreutils dependencies
EXTERNAL_DEPS="curl jq ssh-keygen"
for dep in $EXTERNAL_DEPS; do
if ! command -v "$dep" > /dev/null 2>&1; then
echo "Command $dep not installed on the system!" >&2
exit 1
fi
done
cd "$GITHUB_ACTION_PATH"
cloudflared_url=https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64
echo "Downloading \`cloudflared\` from <$cloudflared_url>..."
curl --location --silent --output cloudflared "$cloudflared_url"
chmod +x cloudflared
curl -s "https://api.github.com/users/$GITHUB_ACTOR/keys" | jq -r '.[].key' > authorized_keys
if grep -q . authorized_keys; then
echo "Configured SSH key(s) for user: $GITHUB_ACTOR"
else
echo "No SSH key found for user: $GITHUB_ACTOR"
echo "Setting SSH password..."
#
# This command could be "beautifully" lazy like the commented line below,
# but for some reason on GitHub Actions specifically (and nowhere else I
# could find) it logs the following warnings:
#
# tr: write error: Broken pipe
# tr: write error
# base64: write error: Broken pipe
# base64: write error
#
# Even though it still succeeds and behaves properly, I'd rather not have
# those warnings show up, so I use `head` twice, getting 16 bytes off
# `/dev/urandom`, Base64-encoding it (which should give us 25 characters),
# and we drop all the nonalphanumeric ones and trim it to 16 chars.
#
password=$(head -c 16 /dev/urandom | base64 | tr -cd '[:alnum:]' | head -c 16)
# password=$(base64 < /dev/urandom | tr -cd '[:alnum:]' | head -c16)
(echo "$password"; echo "$password") | sudo passwd "$USER"
fi
# `-q` is to make it quiet, `-N ''` is for empty passphrase
echo 'Creating SSH server key...'
ssh-keygen -q -f ssh_host_rsa_key -N ''
echo "$fingerprint"
echo 'Creating SSH server config...'
sed "s,\$PWD,$PWD,;s,\$USER,$USER," sshd_config.template > sshd_config
echo 'Starting SSH server...'
/usr/sbin/sshd -f sshd_config -D &
sshd_pid=$!
echo 'Starting tmux session...'
(cd "$GITHUB_WORKSPACE" && tmux new-session -d -s debug)
# Use `sed -u` (unbuffered) otherwise logs don't show up in the UI
echo 'Starting Cloudflare tunnel...'
./cloudflared tunnel --no-autoupdate --url tcp://localhost:2222 2>&1 | tee cloudflared.log | sed -u 's/^/cloudflared: /' &
cloudflared_pid=$!
#
# Tail `cloudflared.log` to find the part where they share the relay
# hostname.
#
# Shell substitution `<()` required to prevent the pipeline from hanging
# even after it finds a first match. See <https://stackoverflow.com/a/45327054>.
#
# Requires GNU Bash.
#
url=$(head -1 <(tail -f cloudflared.log | grep --line-buffered -o 'https://.*\.trycloudflare.com'))
# POSIX-compatible but just hangs
# url=$(tail -f cloudflared.log | grep --line-buffered -o 'https://.*\.trycloudflare.com' | head -1)
# POSIX-compatible using simple polling instead
# url=$(while ! grep -o 'https://.*\.trycloudflare.com' cloudflared.log; do sleep 1; done)
# Ignore the `user@host` part at the end of the public key
public_key=$(cut -d' ' -f1,2 < ssh_host_rsa_key.pub)
# Notify the actor and output to the run log
(
# Echo spaces on empty lines because if we just echo a newline, GitHub will eat it
echo ' '
echo ' '
echo ' '
echo ' '
echo 'Run the following command to connect:'
echo ' '
echo " ssh-keygen -R action-sshd-cloudflared && echo 'action-sshd-cloudflared $public_key' >> ~/.ssh/known_hosts && ssh -o ProxyCommand='cloudflared access tcp --hostname $url' runner@action-sshd-cloudflared"
#
# Unlike Upterm that runs a custom SSH server to accept passwords in the
# username field with the `:password` syntax, standard `sshd` doesn't
# accept that, so we need to paste the password like mere mortals.
#
if [ -n "$password" ]; then
echo ' '
echo " # Password: $password"
fi
#
# You might notice we use `action-sshd-cloudflared` as a SSH host to connect.
# This is abritrary and we could put anything here, because of the
# `ProxyCommand` option later, the host is ignored and we directly go through
# the tunnel exposed by `cloudflared`. But for the `ssh` command to be valid,
# we still need to give it a host.
#
echo ' '
echo "What the one-liner does:"
echo ' '
echo ' # Remove old SSH server public key for `action-sshd-cloudflared`'
echo " ssh-keygen -R action-sshd-cloudflared"
echo ' '
echo ' # Trust the public key for this session'
echo " echo 'action-sshd-cloudflared $public_key' >> ~/.ssh/known_hosts"
echo ' '
echo ' # Connect using `cloudflared` as a transport (SSH is end-to-end encrpted over this tunnel)'
echo " ssh -o ProxyCommand='cloudflared access tcp --hostname $url' runner@action-sshd-cloudflared"
echo ' '
echo " # Alternative if you don't want to verify the host key"
echo " ssh -o ProxyCommand='cloudflared access tcp --hostname $url' -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=accept-new runner@action-sshd-cloudflared"
echo ' '
echo ' '
echo ' '
echo ' '
) | notify_func
#
# The `channel` argument is not used but needs to be passed.
#
# `wait-for` will hang until we call `tmux wait-for -S channel` (which we
# don't) but it'll also exit when all tmux sessions are over, which is
# fine with us!
#
tmux wait-for channel
echo 'Session ended'
kill "$cloudflared_pid"
kill "$sshd_pid"
if [ -s /tmp/ix-io.txt ]; then
curl -u "$IX_USERNAME:$IX_PASSWORD" -X DELETE "$(cat /tmp/ix-io.txt)"
fi