Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Dockerfile.in
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ RUN apt-get update \
openssh-client \
&& rm -rf /var/lib/apt/lists/*

# By default we will run as this user...
RUN echo "git-sync:x:65533:65533::/tmp:/sbin/nologin" >> /etc/passwd
# ...but the user might choose a different UID and pass --add-user
# which needs to be able to write to /etc/passwd.
RUN chmod 0666 /etc/passwd

ADD bin/{ARG_OS}_{ARG_ARCH}/{ARG_BIN} /{ARG_BIN}

Expand Down
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,9 @@ test: $(BUILD_DIRS)
"
@./test_e2e.sh

test-tools:
@docker build -t $(REGISTRY)/test/test-sshd _test_tools/sshd

$(BUILD_DIRS):
@mkdir -p $@

Expand Down
39 changes: 39 additions & 0 deletions _test_tools/sshd/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Stolen from https://github.com/linuxkit/linuxkit/tree/master/pkg/sshd/

FROM alpine AS base

RUN mkdir -p /out/etc/apk && cp -r /etc/apk/* /out/etc/apk/
RUN apk add --no-cache --initdb -p /out \
alpine-baselayout \
apk-tools \
busybox \
ca-certificates \
git \
musl \
openssh-server \
tini \
util-linux \
wireguard-tools \
&& true

###############

FROM scratch

ENTRYPOINT []
WORKDIR /

COPY --from=base /out/ /

RUN mkdir -p /etc/ssh && rm /etc/motd
COPY sshd_config /etc/ssh/
COPY sshd.sh /

# Callers should mount a .ssh directory here. Our sshd.sh will copy it and
# manage permissions.
VOLUME /dot_ssh

# Callers can SSH as user "test"
RUN echo "test:x:65533:65533::/home/test:/usr/bin/git-shell" >> /etc/passwd

CMD ["/sbin/tini", "/sshd.sh"]
52 changes: 52 additions & 0 deletions _test_tools/sshd/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# An SSHD for tests git-over-ssh

DO NOT USE THIS FOR ANYTHING BUT TESTING GIT OVER SSH!!!

## How to use it

Build yourself a test image. We use example.com so you can't accidentally push
it.

```
$ docker build -t example.com/test/test-sshd .
...lots of output...
Successfully tagged example.com/test/test-sshd:latest
```

Generate keys for a fake user named "test".

```
$ mkdir -p dot_ssh

$ ssh-keygen -f dot_ssh/id_test -P ""
Generating public/private rsa key pair.
Your identification has been saved in dot_ssh/id_test.
Your public key has been saved in dot_ssh/id_test.pub.
...lots of output...

$ cat dot_ssh/id_test.pub > dot_ssh/authorized_keys
```

Run it.

```
$ docker run -d -v $(pwd)/dot_ssh:/dot_ssh:ro example.com/test/test-sshd
6d05b4111b03c66907031e3cd7587763f0e4fab6c50fac33c4a8284732b448ae
```

Find your IP.

```
$ docker inspect 6d05b4111b03c66907031e3cd7587763f0e4fab6c50fac33c4a8284732b448ae | jq -r .[0].NetworkSettings.IPAddress
192.168.1.2
```

SSH to it.

```
$ ssh -i dot_ssh/id_test -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null test@192.168.9.2
Warning: Permanently added '192.168.9.2' (ECDSA) to the list of known hosts.
fatal: Interactive git shell is not enabled.
hint: ~/git-shell-commands should exist and have read and execute access.
Connection to 192.168.9.2 closed.
```
14 changes: 14 additions & 0 deletions _test_tools/sshd/sshd.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/bin/sh

KEYS=$(find /etc/ssh -name 'ssh_host_*_key')
[ -z "$KEYS" ] && ssh-keygen -A >/dev/null 2>/dev/null

# Copy creds for the test user, so we don't have to bake them into the image
# and so users don't have to manage permissions.
mkdir -p /home/test/.ssh
cp -a /dot_ssh/* /home/test/.ssh
chown -R test /home/test/.ssh
chmod 0700 /home/test/.ssh
chmod 0600 /home/test/.ssh/*

exec /usr/sbin/sshd -D -e
12 changes: 12 additions & 0 deletions _test_tools/sshd/sshd_config
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# This is the sshd server system-wide configuration file. See
# sshd_config(5) for more information.

# The default is to check both .ssh/authorized_keys and .ssh/authorized_keys2
# but this is overridden so installations will only check .ssh/authorized_keys
AuthorizedKeysFile .ssh/authorized_keys

# To disable tunneled clear text passwords, change to no here!
PasswordAuthentication no

# Change to no to disable s/key passwords
ChallengeResponseAuthentication no
32 changes: 32 additions & 0 deletions cmd/git-sync/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ var flSSHKnownHosts = flag.Bool("ssh-known-hosts", envBool("GIT_KNOWN_HOSTS", tr
"enable SSH known_hosts verification")
var flSSHKnownHostsFile = flag.String("ssh-known-hosts-file", envString("GIT_SSH_KNOWN_HOSTS_FILE", "/etc/git-secret/known_hosts"),
"the known_hosts file to use")
var flAddUser = flag.Bool("add-user", envBool("GIT_SYNC_ADD_USER", false),
"add a record to /etc/passwd for the current UID/GID (needed to use SSH with a different UID)")

var flCookieFile = flag.Bool("cookie-file", envBool("GIT_COOKIE_FILE", false),
"use git cookiefile")
Expand Down Expand Up @@ -241,6 +243,13 @@ func main() {
os.Exit(1)
}

if *flAddUser {
if err := addUser(); err != nil {
fmt.Fprintf(os.Stderr, "ERROR: can't write to /etc/passwd: %v\n", err)
os.Exit(1)
}
}

// This context is used only for git credentials initialization. There are no long-running operations like
// `git clone`, so initTimeout set to 30 seconds should be enough.
ctx, cancel := context.WithTimeout(context.Background(), initTimeout)
Expand Down Expand Up @@ -390,6 +399,29 @@ func sleepForever() {
os.Exit(0)
}

// Put the current UID/GID into /etc/passwd so SSH can look it up. This
// assumes that we have the permissions to write to it.
func addUser() error {
home := os.Getenv("HOME")
if home == "" {
cwd, err := os.Getwd()
if err != nil {
return fmt.Errorf("can't get current working directory: %v", err)
}
home = cwd
}

f, err := os.OpenFile("/etc/passwd", os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
return err
}
defer f.Close()

str := fmt.Sprintf("git-sync:x:%d:%d::%s:/sbin/nologin\n", os.Getuid(), os.Getgid(), home)
_, err = f.WriteString(str)
return err
}

// updateSymlink atomically swaps the symlink to point at the specified
// directory and cleans up the previous worktree. If there was a previous
// worktree, this returns the path to it.
Expand Down
9 changes: 7 additions & 2 deletions docs/ssh.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ Secret (e.g. "git-creds" used in both above examples).
- name: git-secret
secret:
secretName: git-creds
defaultMode: 288 # 0440
defaultMode: 0400
# ...
```

Expand Down Expand Up @@ -103,6 +103,11 @@ that this is a Pod-wide setting, unlike the container `securityContext` above.
# ...
```

If you want git-sync to run as a different (non-root) UID and GID, you can
change these last blocks to any UID/GID you like. SSH demands that the current
UID be present in /etc/passwd, so in this case you will need to add the
`--add-user` flag to git-sync's args array.

**Note:** Kubernetes mounts the Secret with permissions 0444 by default (not
restrictive enough to be used as an SSH key), so make sure you set the
`defaultMode`.
Expand Down Expand Up @@ -130,7 +135,7 @@ spec:
- name: git-secret
secret:
secretName: git-creds
defaultMode: 0440
defaultMode: 0400
containers:
- name: git-sync
image: k8s.gcr.io/git-sync:v3.1.1
Expand Down
42 changes: 42 additions & 0 deletions test_e2e.sh
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ function freencport() {

# Build it
make container REGISTRY=e2e VERSION=$(make -s version)
make test-tools REGISTRY=e2e

RUNID="${RANDOM}${RANDOM}"
DIR=""
Expand Down Expand Up @@ -95,6 +96,12 @@ function clean_root() {
mkdir -p "$ROOT"
}

# Init SSH for test cases.
DOT_SSH="$DIR/dot_ssh"
mkdir -p "$DOT_SSH"
ssh-keygen -f "$DOT_SSH/id_test" -P "" >/dev/null
cat "$DOT_SSH/id_test.pub" > "$DOT_SSH/authorized_keys"

function finish() {
if [ $? -ne 0 ]; then
echo "The directory $DIR was not removed as it contains"\
Expand All @@ -118,8 +125,10 @@ function GIT_SYNC() {
-v "$DIR":"$DIR":rw \
-v "$(pwd)/slow_git.sh":"$SLOW_GIT":ro \
-v "$(pwd)/askpass_git.sh":"$ASKPASS_GIT":ro \
-v "$DOT_SSH/id_test":"/etc/git-secret/ssh":ro \
--env XDG_CONFIG_HOME=$DIR \
e2e/git-sync:$(make -s version)__$(go env GOOS)_$(go env GOARCH) \
--add-user \
"$@"
}

Expand Down Expand Up @@ -963,5 +972,38 @@ fi
rm -rf $SUBMODULE
pass

##############################################
# Test SSH
##############################################
testcase "ssh"
echo "$TESTCASE" > "$REPO"/file
# Run a git-over-SSH server
CTR=$(docker run \
-d \
--rm \
--label git-sync-e2e="$RUNID" \
-v "$DOT_SSH":/dot_ssh:ro \
-v "$REPO":/src:ro \
e2e/test/test-sshd)
IP=$(docker inspect "$CTR" | jq -r .[0].NetworkSettings.IPAddress)
git -C "$REPO" commit -qam "$TESTCASE"
GIT_SYNC \
--logtostderr \
--v=5 \
--one-time \
--ssh \
--ssh-known-hosts=false \
--repo="test@$IP:/src" \
--branch=master \
--rev=HEAD \
--root="$ROOT" \
--dest="link" \
> "$DIR"/log."$TESTCASE" 2>&1
assert_link_exists "$ROOT"/link
assert_file_exists "$ROOT"/link/file
assert_file_eq "$ROOT"/link/file "$TESTCASE"
# Wrap up
pass

echo "cleaning up $DIR"
rm -rf "$DIR"