diff options
-rw-r--r-- | .gitignore | 11 | ||||
-rw-r--r-- | Makefile | 46 | ||||
-rw-r--r-- | README | 7 | ||||
-rw-r--r-- | custodia.conf | 47 | ||||
-rwxr-xr-x | entrypoint.sh | 8 | ||||
-rw-r--r-- | gustodia.go | 112 | ||||
-rwxr-xr-x | gustodia.py | 71 |
7 files changed, 302 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2dd975c --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +*.pyc +.*swp +__pycache__ + +venv + +custodia.audit.log +secrets.db +server_socket + +gustodia diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..534ad1f --- /dev/null +++ b/Makefile @@ -0,0 +1,46 @@ +VENV=venv +PYTHON=python2.7 +CUSTODIA=${VENV}/bin/custodia +REQUIREMENTS=cryptography jwcrypto git+https://github.com/simo5/custodia +CURL_CMD=curl --unix-socket server_socket -H "REMOTE_USER: user" + +all: ${CUSTODIA} + +${VENV}: + virtualenv --python=${PYTHON} ${VENV} + ${VENV}/bin/pip install --upgrade pip setuptools + +${CUSTODIA}: | ${VENV} + ${VENV}/bin/pip install ${REQUIREMENTS} + +gustodia: gustodia.go + go build $< + +.PHONY=example +example: gustodia + env -i EXAMPLE=foo CUSTODIA_SECRET_DB_PASSWORD=tests/mysecret \ + ./gustodia ./entrypoint.sh apache + +.PHONY=run_server +run_server: ${CUSTODIA} + ${CUSTODIA} + +.PHONY=init_secret +init_secret: + ${CURL_CMD} -X POST http://localhost/secrets/tests/ + ${CURL_CMD} -H "Content-Type: application/json" \ + -X PUT \ + -d '{"type": "simple", "value": "SuperSecretPassword"}' \ + http://localhost/secrets/tests/mysecret + +.PHONY=upgrade +upgrade: | ${VENV} + ${VENV}/bin/pip install --upgrade ${REQUIREMENTS} + +.PHONY=clean +clean: + @rm -rf ${VENV} + @rm -rf secrets.db + @rm -f custodia.audit.log + @rm -f gustodia + @rm -f server_socket @@ -0,0 +1,7 @@ +$ make run_server + +$ make init_db +$ make example + +curl --unix-socket server_socket -H "REMOTE_USER: user" http://localhost/secrets/tests/mysecret + diff --git a/custodia.conf b/custodia.conf new file mode 100644 index 0000000..40f513e --- /dev/null +++ b/custodia.conf @@ -0,0 +1,47 @@ +[global] +server_version = "Secret/0.0.7" +debug = True + +#[auth:simple] +#handler = custodia.httpd.authenticators.SimpleCredsAuth +#uid = 48 +#gid = 48 + +[auth:header] +handler = custodia.httpd.authenticators.SimpleHeaderAuth +name = REMOTE_USER + +[authz:paths] +handler = custodia.httpd.authorizers.SimplePathAuthz +paths = /. + +[authz:namespaces] +handler = custodia.secrets.Namespaces +path = /secrets/ +store = simple + +[store:simple] +handler = custodia.store.sqlite.SqliteStore +dburi = secrets.db +table = secrets + +[/] +handler = custodia.root.Root +store = simple + + +# Multi-tenant example +[store:tenant1] +handler = custodia.store.sqlite.SqliteStore +dburi = secrets.db +table = tenant1 + +[authz:tenant1] +handler = custodia.secrets.Namespaces +path = /tenant1/secrets/ +store = tenant1 + +[/tenant1/secrets] +handler = custodia.root.Secrets +store = tenant1 + diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100755 index 0000000..3b432ec --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +# sample entry point + +echo "arg0: $0" +echo "args: $@" +echo "Env:" +env diff --git a/gustodia.go b/gustodia.go new file mode 100644 index 0000000..9757542 --- /dev/null +++ b/gustodia.go @@ -0,0 +1,112 @@ +package main + +import ( + "fmt" + "encoding/json" + "errors" + "net" + "net/http" + "os" + "strings" + "syscall" +) + +const PREFIX string = "CUSTODIA_" +const SECRET_PREFIX string = PREFIX + "SECRET_" +const BASE_PATH string = "http://localhost/secrets/" +const SOCKET string = "./server_socket" + +type CustodiaMessage struct { + Type string `json:"type"` + Value string `json:"value"` +} + +type CustodiaSecret struct { + Name string + Value string + Secret string +} + +func unixDial(proto, addr string) (conn net.Conn, err error) { + typ := "unix" + conn, err = net.DialUnix(typ, nil, &net.UnixAddr{SOCKET, typ}) + return conn, err +} + +func query_custodia(secrets []*CustodiaSecret) { + tr := &http.Transport{ + Dial: unixDial, + } + client := &http.Client{Transport: tr} + + for _, sec := range secrets { + path := BASE_PATH + sec.Value + req, err := http.NewRequest( + "GET", path, nil) + if err != nil { + panic(err) + } + req.Header.Add("REMOTE_USER", "gustodia") + resp, err := client.Do(req) + if err != nil { + panic(err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + msg := fmt.Sprintf("%s: %s", path, resp.Status) + err := errors.New(msg) + panic(err) + } + var m CustodiaMessage + body := json.NewDecoder(resp.Body) + body.Decode(&m) + sec.Secret = m.Value; + } +} + +func get_custodia_envs() ([]*CustodiaSecret) { + secrets := []*CustodiaSecret{} + for _, env := range os.Environ() { + if strings.HasPrefix(env, SECRET_PREFIX) { + pair := strings.SplitN(env, "=", 2) + sec := &CustodiaSecret{ + Name: pair[0][len(SECRET_PREFIX):], + Value: pair[1], + Secret: "", + } + secrets = append(secrets, sec) + } + } + return secrets +} + +func make_env(secrets []*CustodiaSecret) []string { + environ := []string{} + for _, env := range os.Environ() { + if ! strings.HasPrefix(env, PREFIX) { + environ = append(environ, env) + } + } + for _, sec := range secrets { + env := fmt.Sprintf("%s=%s", sec.Name, sec.Secret) + environ = append(environ, env) + } + return environ +} + +func main() { + secrets := get_custodia_envs() + query_custodia(secrets) + environ := make_env(secrets) + /* + for _, sec := range secrets { + fmt.Printf("%+v\n", *sec) + } + for _, env := range environ { + fmt.Println(env) + } + */ + args := os.Args[1:] + syscall.Exec(args[0], args, environ) +} diff --git a/gustodia.py b/gustodia.py new file mode 100755 index 0000000..4958cbd --- /dev/null +++ b/gustodia.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python2.7 +from __future__ import print_function + +import httplib +import json +import os +import socket +import sys + +PREFIX = 'CUSTODIA_' +SECRET_PREFIX = PREFIX + 'SECRET_' +SOCK = './server_socket' + +class HTTPUnixConnection(httplib.HTTPConnection): + def __init__(self, socket_file): + self.socket_file = socket_file + httplib.HTTPConnection.__init__(self, 'localhost') + + def connect(self): + sock = socket.socket(socket.AF_UNIX) + sock.connect(self.socket_file) + self.sock = sock + + +def get_custodia_envs(): + envs = {} + for name, value in os.environ.iteritems(): + if name.startswith(SECRET_PREFIX): + name = name[len(SECRET_PREFIX):] + envs[name] = value + return envs + + +def query_custodia(envs, sock=SOCK): + conn = HTTPUnixConnection(sock) + headers = {'REMOTE_USER': 'gustodia'} + result = {} + for name, path in envs.iteritems(): + conn.request( + 'GET', 'http://localhost/secrets/' + path, headers=headers + ) + response = conn.getresponse() + if response.status != 200: + print(r1.status, r1.reason) + continue + value = json.loads(response.read()) + result[name] = value['value'] + return result + + +def filtered_env(): + env = {} + for name, value in os.environ.iteritems(): + if not name.startswith(PREFIX): + env[name] = value + return env + + +def main(): + cenvs = get_custodia_envs() + print('env vars', cenvs) + secrets = query_custodia(cenvs) + print('secrets from custodia', secrets) + envp = filtered_env() + envp.update(secrets) + argv = sys.argv[1:] + filename = argv[0] + print("execve(%s, %s, %s)" % (filename, argv, envp)) + +if __name__ == '__main__': + main() |