Skip to content

Commit

Permalink
Use HTTPS (#5)
Browse files Browse the repository at this point in the history
* add defaults

* recursively make pki directory

* updates tests for https
  • Loading branch information
binarymason committed Jul 17, 2019
1 parent 52d3412 commit 77fe923
Show file tree
Hide file tree
Showing 9 changed files with 227 additions and 15 deletions.
23 changes: 22 additions & 1 deletion config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@ import (
"gopkg.in/yaml.v2"
)

const defaultCertPath = "/etc/bashrpc/"

type config struct {
Cert string `yaml:"cert"`
Key string `yaml:"key"`
Port string `yaml:"port"`
Routes []route `yaml:"routes"`
Secret string `yaml:"secret"`
Whitelisted []string `yaml:"whitelisted_clients"`
Routes []route `yaml:"routes"`
}

type route struct {
Expand All @@ -22,6 +26,8 @@ type route struct {

func loadConfig(p string) (config, error) {
cfg := config{}
setConfigDefaults(&cfg)

data, err := ioutil.ReadFile(p)
if err != nil {
return cfg, err
Expand All @@ -31,6 +37,13 @@ func loadConfig(p string) (config, error) {
return cfg, err
}

func setConfigDefaults(cfg *config) {
defaultPKIPath := "/etc/bashrpc/pki"
cfg.Key = defaultPKIPath + "/bashrpc.key"
cfg.Cert = defaultPKIPath + "/bashrpc.cert"
cfg.Port = "8675"
}

func validateConfig(cfg config) error {
var issues []string

Expand All @@ -42,6 +55,14 @@ func validateConfig(cfg config) error {
issues = append(issues, "secret is missing")
}

if cfg.Key == "" {
issues = append(issues, "key is missing")
}

if cfg.Cert == "" {
issues = append(issues, "cert is missing")
}

if len(cfg.Whitelisted) == 0 {
issues = append(issues, "no whitelisted clients are specified")
}
Expand Down
53 changes: 47 additions & 6 deletions config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,36 @@ func TestLoad(t *testing.T) {
Assert(cfg.Routes, expectedRoutes, t)
}

func TestSetConfigDefaults(t *testing.T) {
cfg := config{}

setConfigDefaults(&cfg)

Assert(cfg.Port, "8675", t)
Assert(cfg.Key, "/etc/bashrpc/pki/bashrpc.key", t)
Assert(cfg.Cert, "/etc/bashrpc/pki/bashrpc.cert", t)
}

var validConfig = config{
Port: "1234",
Secret: "secret",
Whitelisted: []string{"127.0.0.1"},
Key: "/path/to/key",
Cert: "/path/to/cert",
}

func TestValidConfig(t *testing.T) {
When("config is valid")
cfg := config{Port: "1234", Secret: "secret", Whitelisted: []string{"127.0.0.1"}}

Then("there should be NO errors")
if err := validateConfig(cfg); err != nil {
if err := validateConfig(validConfig); err != nil {
t.Errorf("expected NO errors but received %v", err)
}
}

func TestConfigMissingPort(t *testing.T) {
When("port is not specified")
cfg := config{Secret: "secret", Whitelisted: []string{"127.0.0.1"}}
cfg := validConfig
cfg.Port = ""

Then("an error is returned")
if err := validateConfig(cfg); err == nil {
Expand All @@ -38,7 +55,8 @@ func TestConfigMissingPort(t *testing.T) {

func TestConfigMissingSecret(t *testing.T) {
When("secret is not specified")
cfg := config{Port: "8675", Whitelisted: []string{"127.0.0.1"}}
cfg := validConfig
cfg.Secret = ""

Then("an error is returned")
if err := validateConfig(cfg); err == nil {
Expand All @@ -48,7 +66,30 @@ func TestConfigMissingSecret(t *testing.T) {

func TestConfigMissingWhitelisted(t *testing.T) {
When("whitelisted clients are not specified")
cfg := config{Secret: "secret", Port: "8675", Whitelisted: []string{}}
cfg := validConfig
cfg.Whitelisted = []string{}

Then("an error is returned")
if err := validateConfig(cfg); err == nil {
t.Error("expected errors but received none")
}
}

func TestConfigMissingKey(t *testing.T) {
When("key file is not specified")
cfg := validConfig
cfg.Key = ""

Then("an error is returned")
if err := validateConfig(cfg); err == nil {
t.Error("expected errors but received none")
}
}

func TestConfigMissingCert(t *testing.T) {
When("cert file is not specified")
cfg := validConfig
cfg.Cert = ""

Then("an error is returned")
if err := validateConfig(cfg); err == nil {
Expand Down
5 changes: 4 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,8 @@ func main() {
os.Exit(1)
}

rtr.listen()
if err := rtr.listen(); err != nil {
log.Fatal(fmt.Sprintf("%v", err))
os.Exit(1)
}
}
6 changes: 3 additions & 3 deletions router.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ func newRouter(p string) (router, error) {
return rtr, nil
}

func (rtr *router) listen() {
func (rtr *router) listen() error {
initSSL(rtr.config.Cert, rtr.config.Key)
http.HandleFunc("/", rtr.handler)

fmt.Println("listening on port", rtr.config.Port)
http.ListenAndServe(":"+rtr.config.Port, nil)

return http.ListenAndServeTLS(":"+rtr.config.Port, rtr.config.Cert, rtr.config.Key, nil)
}

func (rtr *router) handler(w http.ResponseWriter, r *http.Request) {
Expand Down
80 changes: 80 additions & 0 deletions ssl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package main

import (
"fmt"
"log"
"os"
"path/filepath"
"strings"
)

// initSSL creates a SSL certificate and key using system's openssl.
// TODO: don't blow away PKI if it already exists and is valid.
func initSSL(certPath, keyPath string) ([]byte, error) {
if out, err := initRndFile(); err != nil {
return out, err
}

fqdn, err := getFQDN()

if err != nil {
return []byte{}, err
}
if err := mkdirP(certPath); err != nil {
return []byte{}, err
}
if err := mkdirP(keyPath); err != nil {
return []byte{}, err
}

command := "openssl"
args := []string{
"req",
"-new",
"-newkey",
"rsa:4096",
"-days",
"3650",
"-nodes",
"-x509",
"-subj",
fmt.Sprintf("/C=US/ST=Somewhere/L=Unknown/O=Idk/CN=%s", fqdn),
"-keyout",
keyPath,
"-out",
certPath,
}

return runCommand(command + " " + strings.Join(args, " "))
}

func initRndFile() ([]byte, error) {
return runCommand(`openssl rand -out "$HOME/.rnd" -hex 256`)
}

func getFQDN() (fqdn string, err error) {
out, err := runCommand("hostname --fqdn")

if err != nil {
return
}

fqdn = string(out)
fqdn = fqdn[:len(fqdn)-1] // removing EOL

return
}

func mkdirP(p string) error {
absPath, _ := filepath.Abs(p)
dir := filepath.Dir(absPath)
_, err := os.Stat(absPath)

if os.IsExist(err) {
return nil
}

log.Println("# creating directory: ", dir)
return os.MkdirAll(dir, 0700)

}
56 changes: 56 additions & 0 deletions ssl_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package main

import (
"fmt"
"os"
"os/exec"
"testing"

. "github.com/binarymason/bashRPC/internal/testhelpers"
"github.com/google/uuid"
"github.com/pkg/errors"
)

func fileExists(filename string) bool {
info, err := os.Stat(filename)
if os.IsNotExist(err) {
return false

}
return !info.IsDir()

}
func TestInitSSL(t *testing.T) {
id, _ := uuid.NewUUID()

var (
testDir = "/tmp/bashrpc-testing"
pkiDir = fmt.Sprintf("%s/test-%v", testDir, id)
keyPath = pkiDir + "/pki/test-host.key"
certPath = pkiDir + "/pki/test-host.cert"
)

Given("openssl is available on the machine")
if out, err := exec.Command("openssl", "version").CombinedOutput(); err != nil {
t.Error(errors.Wrap(err, string(out)))
}

When("an output directory is specified")
if _, err := initSSL(certPath, keyPath); err != nil {
t.Error(err)
}

Then("a SSL private key is generated")
Assert(fileExists(keyPath), true, t)
if out, err := exec.Command("openssl", "rsa", "-in", keyPath, "-check").CombinedOutput(); err != nil {
t.Error(errors.Wrap(err, string(out)))
}

And("a SSL certificate is generated")
Assert(fileExists(certPath), true, t)
if out, err := exec.Command("openssl", "x509", "-in", certPath, "-text").CombinedOutput(); err != nil {
t.Error(errors.Wrap(err, string(out)))
}

os.RemoveAll("/tmp/bashrpc")
}
6 changes: 6 additions & 0 deletions test/data/complex_config.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
---

port: 8675

secret: supersecret
cert: /tmp/foo/pki/bar.cert
key: /tmp/foo/pki/bar.key

whitelisted_clients:
- 127.0.0.1
Expand All @@ -11,6 +14,9 @@ whitelisted_clients:
- 5.6.7.8.9

routes:
- path: /
cmd: echo bashrpc

- path: /version
cmd: echo "1.2.3"

Expand Down
5 changes: 5 additions & 0 deletions test/data/simple_config.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
---

port: 8675

cert: /tmp/foo/bar.cert
key: /tmp/foo/bar.key


secret: supersecret
whitelisted_clients:
- 127.0.0.1
Expand Down
8 changes: 4 additions & 4 deletions test/integration_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ set -e

test_secret=supersecret
cmd="bashRPC -c ./test/data/complex_config.yml"
url="localhost:8675"
url="https://localhost:8675"

setup() {
go install
Expand Down Expand Up @@ -35,13 +35,13 @@ assert_status_code() {
local expected_code="$1"
local status_code
echo -e "\t* ${*:2}"
status_code=$(curl -sw '%{http_code}' "${@:2}" | tail -n 1)
status_code=$(curl -k -sw '%{http_code}' "${@:2}" | tail -n 1)
if [ "$status_code" != "$expected_code" ]; then
fail "expected status code to be $expected_code but got $status_code"
fi

}
curl -H "Authorization: supersecret" localhost:8675
curl -kf -H "Authorization: supersecret" "$url"

echo "# test 404 endpoint"
assert_status_code 404 -H "Authorization: $test_secret" "$url/foobar"
Expand All @@ -62,7 +62,7 @@ assert_response() {
endpoint="$1"
expected_response="$2"
echo -e "\t* $endpoint"
res=$(curl -H "Authorization: $test_secret" -s "$url$endpoint")
res=$(curl -k -H "Authorization: $test_secret" -s "$url$endpoint")
if [ "$res" != "$expected_response" ]; then
fail "expected response to be: $expected_response, but got: $res"
fi
Expand Down

0 comments on commit 77fe923

Please sign in to comment.