Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Dockerfile and integration tests #4

Merged
merged 14 commits into from
Jul 16, 2019
Merged
Show file tree
Hide file tree
Changes from 8 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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
bin/
keybaseca.config
nohup.out
env.sh
46 changes: 43 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# SSHCA Bot
# SSH CA Bot

This repo contains a work in progress SSH CA bot built on top of Keybase. This project is not yet complete and is not
ready to be used.
ddworken marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -19,8 +19,48 @@ binaries.

`kssh` is the replacement SSH binary. It automatically pulls config files from KBFS.

# Getting Started (local environment)
# Integration Tests

This project contains integration tests that can be run via `./integrationTest.sh`. Note that prior to running
the integration tests you need to `cp tests/env.sh.example tests/env.sh` and fill in `tests/env.sh`.

# Getting Started (docker)

```bash
cd docker/
cp env.sh.example env.sh
keybase signup # Follow the prompts to create a new Keybase users to use for the SSH CA bot
keybase paperkey # Generate a new paper key
# Create a new Keybase subteam that this user is in along with anyone else you wish to grant SSH access to
nano env.sh # Fill in the values including the just generated paper key
make generate
```

This will output the public key for the CA.
For each server that you wish to make accessible to the CA bot:

1. Place the public key in `/etc/ssh/ca.pub`
2. Add the line `TrustedUserCAKeys /etc/ssh/ca.pub` to `/etc/ssh/sshd_config`
3. Restart ssh `service ssh restart`

Now start the chatbot itself:

```bash
make serve
```

Now build kssh and start SSHing!

```bash
go build -o bin/kssh cmd/kssh/kssh.go
sudo cp bin/kssh /usr/local/bin/ # Optional
bin/kssh root@server
```

Anyone else in `{TEAM}.ssh` can also run kssh in order to ssh into the server.

# Getting Started (local environment)
###### Recommended only for development work
In all of these directions, replace `{USER}` with your username and `{TEAM}` with the name of the team that you wish to
configure this bot for.

Expand Down Expand Up @@ -52,5 +92,5 @@ For each server that you wish to make accessible to the CA bot:

Now start the chatbot itself: `keybase --home /tmp/keybase service & go run cmd/keybaseca/keybaseca.go -c ~/keybaseca.config service` and leave it running.

Now you just run `go run cmd/kssh/kssh.go root@server` in order to SSH into your server. Anyone else in `{TEAM}.ssh` can
Now you run `go run cmd/kssh/kssh.go root@server` in order to SSH into your server. Anyone else in `{TEAM}.ssh` can
also run that command in order to ssh into the server.
15 changes: 13 additions & 2 deletions cmd/keybaseca/keybaseca.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"strings"

"github.com/keybase/bot-ssh-ca/keybaseca/bot"
"github.com/keybase/bot-ssh-ca/keybaseca/config"
Expand Down Expand Up @@ -86,7 +87,17 @@ func writeClientConfig(conf config.Config) error {

content, err := json.Marshal(kssh.ConfigFile{TeamName: conf.GetTeams()[0], BotName: username})

return ioutil.WriteFile(filename, content, 0600)
return KBFSWrite(filename, string(content))
}

func KBFSWrite(filename string, contents string) error {
cmd := exec.Command("keybase", "fs", "write", filename)
cmd.Stdin = strings.NewReader(string(contents))
bytes, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("Failed to write to file at %s: %s (%v)", filename, string(bytes), err)
}
return nil
}

func loadServerConfigAndWriteClientConfig(configFilename string) (config.Config, error) {
Expand Down
15 changes: 10 additions & 5 deletions cmd/kssh/kssh.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func main() {
team, remainingArgs, err := handleArgs(os.Args)
if err != nil {
fmt.Printf("Failed to parse arguments: %v\n", err)
return
os.Exit(1)
}
keyPath, err := getSignedKeyLocation(team)
if isValidCert(keyPath) {
Expand All @@ -29,7 +29,7 @@ func main() {
config, err := getConfig(team)
if err != nil {
fmt.Printf("%v\n", err)
return
os.Exit(1)
}
provisionNewKey(config, keyPath)
runSSHWithKey(keyPath, remainingArgs)
Expand All @@ -51,9 +51,11 @@ func getSignedKeyLocation(team string) (string, error) {
}

// handleArgs parses os.Args for use with kssh. This is handwritten rather than using go's flag library (or
// any other CLI argument parsing library) since we want to have custom arguments and access any other arguments.
// any other CLI argument parsing library) since we want to have custom arguments and access any other remaining
// arguments. This function calls os.Exit(0) if it finds and handles a --set-default-team CLI flag.
// handleArgs returns (theDefaultTeam, theRemainingArguments, err)
func handleArgs(args []string) (string, []string, error) {
// TODO: Provide a way to clear default teams or at least a better message if there is a bad value there
if len(args) > 1 {
if args[1] == "--team" {
if len(args) == 2 {
Expand All @@ -65,11 +67,13 @@ func handleArgs(args []string) (string, []string, error) {
if len(args) == 2 {
return "", nil, fmt.Errorf("Got --set-default-team argument with no value!")
}
// We exit immediately after setting the default team
err := kssh.SetDefaultTeam(args[2])
if err != nil {
return "", nil, err
fmt.Printf("Failed to set the default team: %v", err)
os.Exit(1)
}
return "", args[3:], nil
os.Exit(0)
}
}
return "", args[1:], nil
Expand Down Expand Up @@ -189,6 +193,7 @@ func runSSHWithKey(keyPath string, remainingArgs []string) {
cmd.Stdin = os.Stdin
err := cmd.Run()
if err != nil {
fmt.Printf("SSH exited with err: %v", err)
os.Exit(1)
}
os.Exit(0)
Expand Down
33 changes: 33 additions & 0 deletions docker/Dockerfile-ca
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# This dockerfile builds a container capable of running the SSH CA bot
FROM ubuntu:18.04

RUN apt-get -qq update
RUN apt-get -qq install curl software-properties-common -y
RUN useradd -ms /bin/bash keybase
USER keybase
WORKDIR /home/keybase
RUN curl --remote-name https://prerelease.keybase.io/keybase_amd64.deb
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i hit issues a while back with this being really old and failing something. maybe not an issue, but this is how to get more up-to-date versions. might be better to keep the old one. /shrug
https://prerelease.keybase.io/nightly/keybase_amd64.deb

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Until we see issues with this, I think it is best to stick with that link (which is listed as the stable release on our website). Given the nature of this project, I don't think it is a good idea to recommend running it on top of nightly builds unless truly necessary.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed

USER root

# Silence the error from dpkg about failing to configure keybase since `apt-get install -f` fixes it
RUN dpkg -i keybase_amd64.deb || true
RUN apt-get install -fy
USER keybase

# Install go
USER root
RUN add-apt-repository ppa:gophers/archive -y
RUN apt-get update
RUN apt-get install golang-1.11-go git sudo -y
USER keybase

# Install go dependencies (speeds up future builds)
COPY --chown=keybase go.mod .
COPY --chown=keybase go.sum .
RUN /usr/lib/go-1.11/bin/go mod download

COPY --chown=keybase ./ /home/keybase/
RUN /usr/lib/go-1.11/bin/go build -o bin/keybaseca cmd/keybaseca/keybaseca.go
RUN /usr/lib/go-1.11/bin/go build -o bin/kssh cmd/kssh/kssh.go

USER root
18 changes: 18 additions & 0 deletions docker/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
SHELL := /bin/bash

.PHONY: generate serve generatekey

generate: | build generatekey

serve:
source env.sh && cat keybaseca.config.gen | envsubst > ../example-keybaseca-volume/keybaseca.config
source env.sh && docker run -e KEYBASE_USERNAME -e PAPERKEY -v $(PWD)/../example-keybaseca-volume:/mnt:rw ca:latest docker/entrypoint-server.sh

build:
docker build -t ca -f Dockerfile-ca ..

generatekey:
source env.sh && cat keybaseca.config.gen | envsubst > ../example-keybaseca-volume/keybaseca.config
source env.sh && docker run -e KEYBASE_USERNAME -e PAPERKEY -v $(PWD)/../example-keybaseca-volume:/mnt:rw ca:latest docker/entrypoint-generate.sh
@echo -e '\nFor each server that you wish to make accessible to the CA bot:\n\n1. Place the public key in `/etc/ssh/ca.pub`\n2. Add the line `TrustedUserCAKeys /etc/ssh/ca.pub` to `/etc/ssh/sshd_config`\n3. Restart ssh `service ssh restart`'

14 changes: 14 additions & 0 deletions docker/entrypoint-generate.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/bin/bash
set -euo pipefail
IFS=$'\n\t'

# chown as root
chown keybase:keybase /mnt

# Run everything else as the keybase user
sudo -i -u keybase bash << EOF
nohup bash -c "run_keybase -g &"
sleep 3
keybase oneshot --username $KEYBASE_USERNAME --paperkey "$PAPERKEY"
bin/keybaseca -c /mnt/keybaseca.config generate
ddworken marked this conversation as resolved.
Show resolved Hide resolved
EOF
14 changes: 14 additions & 0 deletions docker/entrypoint-server.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/bin/bash
set -euo pipefail
IFS=$'\n\t'

# chown as root
chown keybase:keybase /mnt

# Run everything else as the keybase user
sudo -i -u keybase bash << EOF
nohup bash -c "run_keybase -g &"
sleep 3
keybase oneshot --username $KEYBASE_USERNAME --paperkey "$PAPERKEY"
bin/keybaseca -c /mnt/keybaseca.config service
EOF
8 changes: 8 additions & 0 deletions docker/env.sh.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/bin/bash
set -euo pipefail
IFS=$'\n\t'

# The subteam that will be used to grant SSH access
export SUBTEAM="teamname.subteam_for_ssh"
export KEYBASE_USERNAME="username_of_ca_bot"
export PAPERKEY="paper key for the ca bot"
8 changes: 8 additions & 0 deletions docker/keybaseca.config.gen
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Note that you do not need to edit this file. It is used with env.sh and envsubst in order to generate a config file
ca_key_location: /mnt/keybase-ca-key
key_expiration: "+1h"
ssh_user: root
teams:
- $SUBTEAM
keybase_paper_key: $PAPERKEY
keybase_username: $KEYBASE_USERNAME
3 changes: 3 additions & 0 deletions example-keybaseca-volume/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*
!.gitignore
!README.md
1 change: 1 addition & 0 deletions example-keybaseca-volume/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This directory is used with the docker instructions as an example volume used to store the CA key. Do not override the gitignore in this file in order to commit any files in this directory.
33 changes: 33 additions & 0 deletions integrationTest.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/bin/bash
set -euo pipefail
IFS=$'\n\t'

# Some colors for pretty output
RED='\033[0;31m'
GREEN='\033[0;32m'
NC='\033[0m'

# A function used to indent the log output from the tests
indent() { sed 's/^/ /'; }

cd tests/
source env.sh
cat keybaseca.config.gen | envsubst > keybaseca.config
echo "Building containers..."
docker-compose build 2>&1 > /dev/null
echo "Running integration tests..."
docker-compose up -d

TEST_EXIT_CODE=`docker wait tests_kssh_1`

docker logs tests_kssh_1 | indent
ddworken marked this conversation as resolved.
Show resolved Hide resolved

if [ -z ${TEST_EXIT_CODE+x} ] || [ "$TEST_EXIT_CODE" -ne 0 ] ; then
printf "${RED}Tests Failed${NC} - Exit Code: $TEST_EXIT_CODE\n"
else
printf "${GREEN}Tests Passed${NC}\n"
fi

docker-compose stop 2>&1 > /dev/null
docker-compose kill 2>&1 > /dev/null
docker-compose rm -f
ddworken marked this conversation as resolved.
Show resolved Hide resolved
4 changes: 2 additions & 2 deletions keybaseca/sshutils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ func GenerateNewSSHKey(filename string, overwrite bool, printPubKey bool) error
}

cmd := exec.Command("ssh-keygen", "-t", "ed25519", "-f", filename, "-m", "PEM", "-N", "")
err := cmd.Run()
bytes, err := cmd.CombinedOutput()
if err != nil {
return err
return fmt.Errorf("ssh-keygen failed: %s (%v)", string(bytes), err)
ddworken marked this conversation as resolved.
Show resolved Hide resolved
}
if printPubKey {
bytes, err := ioutil.ReadFile(shared.KeyPathToPubKey(filename))
Expand Down
Loading