explain how the script works in the documentation
[chaz/docker-connect] / docker-connect
1 #!/bin/sh
2
3 : <<'=cut'
4 =pod
5
6 =head1 NAME
7
8 docker-connect - Easily connect to Docker sockets over SSH
9
10 =head1 VERSION
11
12 Version 0.80
13
14 =head1 SYNOPSIS
15
16 docker-connect HOSTNAME [SHELL_ARGS]...
17
18 # launch a new shell wherein docker commands go to staging-01.acme.tld
19 docker-connect staging-01.acme.tld
20
21 # list the docker processes running on staging-01.acme.tld
22 docker-connect staging-01.acme.tld -c 'docker ps'
23
24 # connect as a specific user and a specific port
25 docker-connect myusername@staging-01.acme.tld:2222
26
27 =head1 DESCRIPTION
28
29 This script provides an alternative to Docker Machine for connecting your Docker client to a remote
30 Docker daemon. Instead of connecting directly to a Docker daemon listening on an external TCP port,
31 this script sets up a connection to the UNIX socket via SSH.
32
33 The main use case for this is when dealing with "permanent" app servers in an environment where you
34 have a team of individuals who all need access.
35
36 Machine doesn't have a great way to support multiple concurrent users. You can add an existing
37 machine to which you have SSH access using the generic driver on your computer, but if your
38 colleague does the same then Machine will regenerate the Docker daemon TLS certificates, replacing
39 the ones Machine set up for you.
40
41 Furthermore, the Docker daemon relies on TLS certificates for client authorization, which is all
42 fine and good, but organizations are typically not as prepared to deal with the management of client
43 TLS certificates as they are with the management of SSH keys. Worse, the Docker daemon doesn't
44 support certificate revocation lists! So if a colleague leaves, you must replace the certificate
45 authority and recreate and distribute certificates for each remaining member of the team. Ugh!
46
47 Much easier to just use SSH for authorization.
48
49 To be clear, this script isn't a full replacement for Docker Machine. For one thing, Machine has
50 a lot more features and can actually create machines. This script just assists with a particular
51 workflow that is currently underserved by Machine.
52
53 =head1 HOW IT WORKS
54
55 What this script actually does is something similar to this sequence of commands:
56
57 ssh -L$PWD/docker.sock:/run/docker.sock $REMOTE_USER@$REMOTE_HOST -p$REMOTE_PORT -nNT &
58 export DOCKER_HOST="unix://$PWD/docker.sock"
59 unset DOCKER_CERT_PATH
60 unset DOCKER_TLS_VERIFY
61
62 This uses L<ssh(1)> to create a UNIX socket that forwards to the Docker daemon's own UNIX socket on
63 the remote host. The benefit that C<docker-connect> has over executing these commands directly is
64 C<docker-connect> doesn't require write access to the current directory since it puts its sockets in
65 C<$TMPDIR> (typically F</tmp>).
66
67 If your local system doesn't support UNIX sockets, you could use the following C<ssh> command
68 instead which uses a TCP socket:
69
70 ssh -L2000:/run/docker.sock $REMOTE_USER@$REMOTE_HOST -p$REMOTE_PORT -nNT &
71 export DOCKER_HOST="tcp://localhost:2000"
72
73 An important drawback here is that any local user on the machine will then have unchallenged access
74 to the remote Docker daemon by just connecting to localhost:2000. But this may be a reasonable
75 alternative for use on non-multiuser machines only.
76
77 =head1 REQUIREMENTS
78
79 =over
80
81 =item * a Bourne-compatible, POSIX-compatible shell
82
83 This program is written in shell script.
84
85 =item * L<OpenSSH|https://www.openssh.com> 6.7+
86
87 Needed to make the socket connection.
88
89 =item * L<Docker|https://www.docker.com> client
90
91 Not technically required, but this program isn't useful without it.
92
93 =back
94
95 =head1 INSTALL
96
97 =for markdown [![Build Status](https://travis-ci.org/chazmcgarvey/docker-connect.svg?branch=master)](https://travis-ci.org/chazmcgarvey/docker-connect)
98
99 To install, just copy F<docker-connect> into your C<PATH> and make sure it is executable.
100
101 # Assuming you have "$HOME/bin" in your $PATH:
102 cp docker-connect ~/bin/
103 chmod +x ~/bin/docker-connect
104
105 =head1 ENVIRONMENT
106
107 The following environment variables may affect or will be set by this program:
108
109 =over
110
111 =item * C<DOCKER_CONNECT_SOCKET>
112
113 The absolute path to the local socket.
114
115 =item * C<DOCKER_CONNECT_HOSTNAME>
116
117 The hostname of the remote peer.
118
119 =item * C<DOCKER_CONNECT_PID>
120
121 The process ID of the SSH process maintaining the connection.
122
123 =item * C<DOCKER_HOST>
124
125 The URI of the local socket.
126
127 =back
128
129 =head1 TIPS
130
131 If you run many shells and connections, having the hostname of the host that the Docker client is
132 connected to in your prompt may be handy. Try something like this in your local shell config file:
133
134 if [ -n "$DOCKER_CONNECT_HOSTNAME" ]
135 then
136 PS1="[docker:$DOCKER_CONNECT_HOSTNAME] $PS1"
137 fi
138
139 =head1 AUTHOR
140
141 Charles McGarvey <chazmcgarvey@brokenzipper.com>
142
143 =head1 LICENSE
144
145 This software is copyright (c) 2017 by Charles McGarvey.
146
147 This is free software, licensed under:
148
149 The MIT (X11) License
150
151 =cut
152
153 set -e
154
155 prog=$(basename "$0")
156 version="0.80"
157 quiet=0
158 socket="$DOCKER_CONNECT_SOCKET"
159 remote_socket=${REMOTE_SOCKET:-/run/docker.sock}
160 timeout=${TIMEOUT:-15}
161
162 usage() {
163 cat <<END
164 $prog [OPTIONS]... HOSTNAME [SHELL_ARGS]...
165 Easily connect to Docker sockets over SSH.
166
167 OPTIONS:
168 -h Show this help info and exit.
169 -q Be less verbose; can be repeated to enhance effect.
170 -r STR Specify the absolute path of the remote socket.
171 -s STR Specify the absolute path of the local socket.
172 -v Show the program version.
173 END
174 }
175
176 log() {
177 _l=$1
178 shift
179 if [ "$_l" -ge "$quiet" ]
180 then
181 echo >&2 "$prog: $@"
182 fi
183 }
184
185 while getopts "hqr:s:v" opt
186 do
187 case "$opt" in
188 q)
189 quiet=$(expr $quiet + 1)
190 ;;
191 s)
192 socket="$OPTARG"
193 ;;
194 r)
195 remote_socket="$OPTARG"
196 ;;
197 h)
198 usage
199 exit 0
200 ;;
201 v)
202 echo "docker-connect $version"
203 exit 0
204 ;;
205 *)
206 usage
207 exit 1
208 ;;
209 esac
210 done
211 shift $(expr $OPTIND - 1)
212
213 connect=$1
214 if [ -z "$connect" ]
215 then
216 echo >&2 "Missing HOSTNAME."
217 usage
218 exit 1
219 fi
220 shift
221
222 if [ -z "$socket" ]
223 then
224 socket_dir="${TMPDIR:-/tmp}/docker-connect-$(id -u)"
225 mkdir -p "$socket_dir"
226 chmod 0700 "$socket_dir"
227 socket="$socket_dir/docker-$$.sock"
228 fi
229
230 if [ -S "$socket" ]
231 then
232 if [ -n "$DOCKER_CONNECT_HOSTNAME" ]
233 then
234 log 2 "Docker is already connected to $DOCKER_CONNECT_HOSTNAME in this shell."
235 exit 2
236 else
237 log 2 "Docker socket already exists."
238 log 1 "To force a new connection, first remove the file: $socket"
239 exit 3
240 fi
241 elif [ -e "$socket" ]
242 then
243 log 2 "Cannot create socket because another file is in the way."
244 log 1 "To create a new connection, you may first remove the file: $socket"
245 exit 4
246 fi
247
248 hostname=
249 port=
250 user=
251
252 if echo "$connect" |grep -q ':'
253 then
254 hostname=$(echo "$connect" |cut -d: -f1)
255 port=$(echo "$connect" |cut -d: -f2)
256 else
257 hostname="$connect"
258 fi
259
260 if echo "$hostname" |grep -q '@'
261 then
262 user=$(echo "$hostname" |cut -d@ -f1)
263 hostname=$(echo "$hostname" |cut -d@ -f2)
264 fi
265
266 ssh_connect="$hostname"
267
268 if [ "$user" != "" ]
269 then
270 ssh_connect="$user@$ssh_connect"
271 fi
272
273 if [ "$port" != "" ]
274 then
275 ssh_connect="$ssh_connect -p$port"
276 fi
277
278 ${SSH:-ssh} $ssh_connect -L"$socket:$remote_socket" \
279 -oControlPath=none -oConnectTimeout="$timeout" -nNT &
280 ssh_pid=$!
281 ssh_connected=
282
283 handle_noconnect() {
284 log 2 "The connection could not be established."
285 log 1 "Please ensure that you can execute this command successfully:"
286 log 1 " ${SSH:-ssh} $ssh_connect -oControlPath=none echo OK"
287 exit 5
288 }
289
290 handle_disconnect() {
291 kill $ssh_pid 2>/dev/null || true
292 rm -f "$socket"
293 log 0 "Disconnected docker from $hostname."
294 }
295
296 # Wait for the socket connection to be made.
297 for i in $(seq 1 "${timeout}0")
298 do
299 if [ -S "$socket" ]
300 then
301 ssh_connected=1
302 break
303 fi
304 if ! kill -s 0 $ssh_pid 2>/dev/null
305 then
306 handle_noconnect
307 fi
308 sleep 0.1
309 done
310
311 if [ -z "$ssh_connected" ]
312 then
313 handle_noconnect
314 fi
315
316 trap handle_disconnect EXIT
317
318 export DOCKER_CONNECT_HOSTNAME="$hostname"
319 export DOCKER_CONNECT_PID="$ssh_pid"
320 export DOCKER_CONNECT_SOCKET="$socket"
321 export DOCKER_HOST="unix://$socket"
322
323 # Remove incompatible variables set by Docker Machine.
324 unset DOCKER_MACHINE_NAME
325 unset DOCKER_CERT_PATH
326 unset DOCKER_TLS_VERIFY
327
328 log 1 "Executing new shell with docker connected to $hostname."
329 log 0 "This connection will be terminated when the shell exits."
330 ${SHELL:-/bin/sh} "$@"
331
This page took 0.062051 seconds and 4 git commands to generate.