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