From 6e5a2e36e22e19d8f4cc17374525d71fe74c6122 Mon Sep 17 00:00:00 2001 From: Akis Kesoglou Date: Tue, 5 Jun 2018 16:07:12 +0300 Subject: [PATCH] Initial commit --- .gitignore | 5 + README.md | 57 ++++++++++ Vagrantfile | 30 +++++ compose/common.yml | 15 +++ compose/services.yml | 217 ++++++++++++++++++++++++++++++++++++ haproxy/Dockerfile | 6 + haproxy/entry.sh | 9 ++ haproxy/haproxy.cfg | 123 ++++++++++++++++++++ scripts/_keyid.js | 79 +++++++++++++ scripts/gen-root-ca-cert | 55 +++++++++ scripts/gen-token-auth-cert | 42 +++++++ scripts/make-env | 40 +++++++ scripts/patch-hosts | 36 ++++++ scripts/run-fig-command | 31 ++++++ scripts/select-project | 34 ++++++ scripts/start-project | 58 ++++++++++ src/README | 1 + 17 files changed, 838 insertions(+) create mode 100644 README.md create mode 100644 Vagrantfile create mode 100644 compose/common.yml create mode 100644 compose/services.yml create mode 100644 haproxy/Dockerfile create mode 100755 haproxy/entry.sh create mode 100644 haproxy/haproxy.cfg create mode 100644 scripts/_keyid.js create mode 100755 scripts/gen-root-ca-cert create mode 100755 scripts/gen-token-auth-cert create mode 100755 scripts/make-env create mode 100755 scripts/patch-hosts create mode 100755 scripts/run-fig-command create mode 100755 scripts/select-project create mode 100755 scripts/start-project create mode 100644 src/README diff --git a/.gitignore b/.gitignore index e69de29..de0be92 100644 --- a/.gitignore +++ b/.gitignore @@ -0,0 +1,5 @@ +*.srl +.DS_Store +.project +.vagrant/ +src/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..e08cd9b --- /dev/null +++ b/README.md @@ -0,0 +1,57 @@ + +## Host requirements + +- Docker >= 18.05.0 +- Docker Compose >= 1.11 +- OpenSSL >= 1.0.0 +- Python >= 2.7 or >=3.4 + +## Installation + +### Debian/Ubuntu + +Make sure you have the software listed above installed. + +In a terminal, change into the `open-balena` directory and create a new +deployment: + + $ ./scripts/start-project + +This will create a new directory, `demo`, and generate appropriate SSL +certificates and configuration for the platform. You can configure the +deployment name by passing it as the first argument to the `start-project` +command. If you wish to run the platform under a specific domain name, +you can specify it as the second argument. The default is `openbalena.local`. +For example: + + $ ./scripts/start-project mydeployment my.domain.com + +You can create as many deployments as needed and switch between them using: + + $ ./scripts/select-project mydeployment + +Remove all traces of a project by deleting its folder. + +Start the platform with: + + $ ./scripts/run-fig-command up + +Stop the platform with: + + $ ./scripts/run-fig-command stop + +### macOS & Windows + +On macOS and Windows you need Vagrant. `open-balena` is not being tested with +docker-machine. `open-balena` comes with an appropriate `Vagrantfile` for +setting up the VM, installing dependencies and starting the platform. + +- Install Vagrant >= 2.0 +- `$ vagrant plugin install vagrant-docker-compose` +- `$ vagrant up` + +When provisioning completes and the VM has started, `open-balena` services +should be running inside the VM. You will need to expose these services to +the outside in order for them to be reachable by devices. To do so, you must +setup DNS for the domain name you've deployed the instance as to point to the +VM's IP address. diff --git a/Vagrantfile b/Vagrantfile new file mode 100644 index 0000000..f4421de --- /dev/null +++ b/Vagrantfile @@ -0,0 +1,30 @@ +Vagrant.require_version '>= 2.0.0' + +[ 'vagrant-vbguest', 'vagrant-docker-compose' ].each do |p| + unless Vagrant.has_plugin?(p) + raise "Please install missing plugin: vagrant plugin install #{p}" + end +end + +Vagrant.configure('2') do |config| + config.vm.define 'open-balena-vm' + config.vm.box = 'bento/ubuntu-16.04' + config.vm.box_url = 'https://vagrantcloud.com/bento/boxes/ubuntu-16.04/versions/201803.24.0/providers/virtualbox.box' + + config.vm.synced_folder '.', '/vagrant', disabled: true + config.vm.synced_folder '.', '/home/vagrant/open-balena' + config.vm.network 'public_network', bridge: ENV.fetch('OPENBALENA_BRIDGE', true) + + config.ssh.forward_agent = true + + config.vm.provision :docker + config.vm.provision :docker_compose + + # FIXME: remove node + config.vm.provision :shell, inline: 'apt-get install nodejs' + + # FIXME: remove `docker login` + config.vm.provision :shell, inline: "docker login --username resindev --password #{ENV.fetch('DOCKERHUB_PASSWORD')}" + config.vm.provision :shell, inline: '/home/vagrant/open-balena/scripts/start-project' + config.vm.provision :shell, inline: '/home/vagrant/open-balena/scripts/run-fig-command up -d', run: 'always' +end diff --git a/compose/common.yml b/compose/common.yml new file mode 100644 index 0000000..dee6497 --- /dev/null +++ b/compose/common.yml @@ -0,0 +1,15 @@ +version: '2.1' + +services: + component: + cap_add: + - SYS_ADMIN + - SYS_RESOURCE + environment: + - CONFD_BACKEND=ENV + volumes: + - /sys/fs/cgroup:/sys/fs/cgroup:ro + + system: + security_opt: + - seccomp:unconfined diff --git a/compose/services.yml b/compose/services.yml new file mode 100644 index 0000000..5fb9cdc --- /dev/null +++ b/compose/services.yml @@ -0,0 +1,217 @@ +version: '2.1' + +volumes: + db: + registry: + # FIXME: remove + img: + +services: + api: + extends: + file: ./common.yml + service: component + image: resin/resin-api:${OPENBALENA_API_VERSION_TAG:-master} + depends_on: + - db + security_opt: + - seccomp:unconfined + environment: + ADMIN_LIST: + AUTH_RESINOS_REGISTRY_CODE: ${OPENBALENA_RESINOS_REGISTRY_CODE} + BALENA_ROOT_CA: ${OPENBALENA_ROOT_CA} + DB_HOST: db + DB_PASSWORD: docker + DB_PORT: 5432 + DB_USER: docker + DEBUG: '${OPENBALENA_DEBUG}' + DEVICE_CONFIG_OPENVPN_CA: yyy + DEVICE_CONFIG_OPENVPN_CONFIG: yyy + DEVICE_CONFIG_SSH_AUTHORIZED_KEYS: yyy + EMAIL_RECIPIENT_OVERRIDE: + HOST: ${OPENBALENA_HOST_NAME} + IMAGE_MAKER_URL: img.${OPENBALENA_HOST_NAME} + JSON_WEB_TOKEN_EXPIRY_MINUTES: 10080 + JSON_WEB_TOKEN_SECRET: ${OPENBALENA_JWT_SECRET} + PRODUCTION_MODE: '${OPENBALENA_PRODUCTION_MODE}' + REGISTRY2_HOST: registry.${OPENBALENA_HOST_NAME} + TOKEN_AUTH_CERT_ISSUER: api.${OPENBALENA_HOST_NAME} + TOKEN_AUTH_CERT_KEY: ${OPENBALENA_TOKEN_AUTH_KEY} + TOKEN_AUTH_CERT_KID: ${OPENBALENA_TOKEN_AUTH_KID} + TOKEN_AUTH_CERT_PUB: ${OPENBALENA_TOKEN_AUTH_PUB} + TOKEN_AUTH_JWT_ALGO: 'ES256' + VPN_HOST: vpn.${OPENBALENA_HOST_NAME} + VPN_PORT: 443 + VPN_SERVICE_API_KEY: ${OPENBALENA_VPN_SERVICE_API_KEY} + # FIXME: remove all the following + ADMIN_HOST: __unused__ + AUTH_FACEBOOK_CLIENT_ID: __unused__ + AUTH_FACEBOOK_CLIENT_SECRET: __unused__ + AUTH_GITHUB_CLIENT_ID: __unused__ + AUTH_GITHUB_CLIENT_SECRET: __unused__ + AUTH_GOOGLE_CLIENT_ID: __unused__ + AUTH_GOOGLE_CLIENT_SECRET: __unused__ + AUTH_TWITTER_CONSUMER_KEY: __unused__ + AUTH_TWITTER_CONSUMER_SECRET: __unused__ + BUILDER_SERVICE_API_KEY: __unused__1 # must be unique + COOKIE_SESSION_SECRET: __unused__ + DB_ANALYTICS_PASSWORD: + DELTA_HOST: __unused__ + DELTA_SERVICE_API_KEY: __unused__2 # must be unique + DEVICE_URLS_BASE: __unused__ + DIGITIZER_API_KEY: __unused__3 # must be unique + GA_ID: __unused__ + GA_SITE: __unused__ + GEOIP_LICENCE_KEY: __unused__ + GEOIP_USER_ID: __unused__ + GIT_HOST: __unused__ + GIT_HOSTNAME: __unused__ + GIT_PORT: 80 + GIT_SERVICE_API_KEY: __unused__4 # must be unique + INTERCOM_APP_ID: __unused__ + INTERCOM_SECRET_KEY: __unused__ + MAILGUN_API_KEY: __unused__ + MAILGUN_DOMAIN: __unused__ + MIXPANEL_ACCOUNT_ID: __unused__ + MIXPANEL_API_KEY: __unused__ + MIXPANEL_API_SECRET: __unused__ + MIXPANEL_TOKEN: __unused__ + PROXY_SERVICE_API_KEY: __unused__5 # must be unique + PUBNUB_PUBLISH_KEY: __unused__ + PUBNUB_SUBSCRIBE_KEY: __unused__ + RECAPTCHA_PRIVATE_KEY: __unused__ + RECAPTCHA_PUBLIC_KEY: __unused__ + RECURLY_PRIVATE_KEY: __unused__ + RECURLY_PUBLIC_KEY: __unused__ + RECURLY_SUBDOMAIN: __unused__ + REDIS_HOST: __unused__ + REDIS_PORT: 6379 + REGISTRY_HOST: __unused__ + SENTRY_DSN: + TOKEN_AUTH_BUILDER_TOKEN: __unused__ + TWOFACTOR_ISSUER: __unused__ + UI_HOST: __unused__ + ZENDESK_DOMAIN: __unused__ + ZENDESK_SHARED_SECRET: __unused__ + + registry: + extends: + file: ./common.yml + service: component + image: resin/open-balena-registry:${OPENBALENA_REGISTRY_VERSION_TAG:-master} + depends_on: + - api + volumes: + - registry:/data + environment: + API_TOKENAUTH_CRT: ${OPENBALENA_TOKEN_AUTH_PUB} + BALENA_ROOT_CA: ${OPENBALENA_ROOT_CA} + COMMON_REGION: + REGISTRY2_HOST: registry.${OPENBALENA_HOST_NAME} + REGISTRY2_S3_BUCKET: + REGISTRY2_S3_KEY: + REGISTRY2_S3_SECRET: + REGISTRY2_STORAGEPATH: /data + TOKENAUTH_ISSUER: api.${OPENBALENA_HOST_NAME} + TOKENAUTH_REALM: https://api.${OPENBALENA_HOST_NAME}/auth/v1/token + # FIXME: remove the following + REGISTRY2_SECRETKEY: __unused__ + + vpn: + extends: + file: ./common.yml + service: component + image: resin/resin-vpn:${OPENBALENA_VPN_VERSION_TAG:-master} + depends_on: + - api + cap_add: + - NET_ADMIN + environment: + BALENA_ROOT_CA: ${OPENBALENA_ROOT_CA} + BALENA_VPN_PORT: 443 + LOGENTRIES_TOKEN: + PRODUCTION_MODE: '${OPENBALENA_PRODUCTION_MODE}' + RESIN_API_HOST: api.${OPENBALENA_HOST_NAME} + RESIN_VPN_GATEWAY: 10.2.0.1 + SENTRY_DSN: + VPN_HAPROXY_USEPROXYPROTOCOL: 'true' + VPN_SERVICE_API_KEY: ${OPENBALENA_VPN_SERVICE_API_KEY} + # FIXME: remove all of the following + API_SERVICE_API_KEY: __unused__ + PROXY_SERVICE_API_KEY: __unused__5 + + db: + extends: + file: ./common.yml + service: system + image: resin/open-balena-db:${OPENBALENA_DB_VERSION_TAG:-master} + volumes: + - db:/var/lib/postgresql/data + + haproxy: + extends: + file: ./common.yml + service: system + build: ../haproxy + depends_on: + - api + - registry + - vpn + - db + # FIXME: remove + - proxy + - img + ports: + - "80:80" + - "222:222" + - "443:443" + - "5432:5432" + environment: + BALENA_ROOT_CA: ${OPENBALENA_ROOT_CA} + HAPROXY_HOSTNAME: ${OPENBALENA_HOST_NAME} + +# FIXME: remove the following + + proxy: + extends: + file: ./common.yml + service: component + image: resin/resin-proxy:${OPENBALENA_PROXY_VERSION_TAG:-master} + depends_on: + - api + - vpn + environment: + BALENA_ROOT_CA: ${OPENBALENA_ROOT_CA} + DEBUG: '${OPENBALENA_DEBUG}' + DEVICE_URLS_BASE: devices.${OPENBALENA_HOST_NAME} + IDLE_CONNECTION_TIMEOUT: 50 + JSON_WEB_TOKEN_SECRET: ${OPENBALENA_JWT_SECRET} + LOGENTRIES_TOKEN: + PRODUCTION_MODE: '${OPENBALENA_PRODUCTION_MODE}' + PROXY_PROTOCOL_PORTS: + PROXY_SERVICE_API_KEY: __unused__5 + RESIN_API_HOST: api.${OPENBALENA_HOST_NAME} + SENTRY_DSN: + + img: + extends: + file: ./common.yml + service: component + image: resin/resin-img:${OPENBALENA_IMG_VERSION_TAG:-master} + depends_on: + - api + volumes: + - img:/prepdir + environment: + BALENA_ROOT_CA: ${OPENBALENA_ROOT_CA} + DEVELOPMENT: 1 + LKL_MEMORY: 268435456 + LOGENTRIES_TOKEN: + NODEPING_KEY: xxx + RESIN_IMG_S3_ACCESS_KEY: abcdef1234 + RESIN_IMG_S3_BUCKET: resin-image-maker + RESIN_IMG_S3_ENDPOINT: s3.${OPENBALENA_HOST_NAME} + RESIN_IMG_S3_FORCE_PATH_STYLE: 'true' + RESIN_IMG_S3_SECRET_KEY: 1234567890 + RESIN_IMG_STORAGE_DRIVER: passthrough + SENTRY_DSN: diff --git a/haproxy/Dockerfile b/haproxy/Dockerfile new file mode 100644 index 0000000..3416c32 --- /dev/null +++ b/haproxy/Dockerfile @@ -0,0 +1,6 @@ +FROM haproxy:1.8-alpine + +COPY haproxy.cfg /usr/local/etc/haproxy/haproxy.cfg +COPY entry.sh /open-balena-entry + +CMD /open-balena-entry diff --git a/haproxy/entry.sh b/haproxy/entry.sh new file mode 100755 index 0000000..62b49bb --- /dev/null +++ b/haproxy/entry.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +CA_B64="$BALENA_ROOT_CA" +CA_FILE=/etc/ssl/private/root.chain.pem + +mkdir -p $(dirname "$CA_FILE") +echo "$CA_B64" | base64 -d >"$CA_FILE" + +exec haproxy -f /usr/local/etc/haproxy/haproxy.cfg diff --git a/haproxy/haproxy.cfg b/haproxy/haproxy.cfg new file mode 100644 index 0000000..d84190f --- /dev/null +++ b/haproxy/haproxy.cfg @@ -0,0 +1,123 @@ +global + tune.ssl.default-dh-param 1024 + +defaults + timeout connect 5000 + timeout client 50000 + timeout server 50000 + +frontend http-in + mode http + option forwardfor + bind *:80 + reqadd X-Forwarded-Proto:\ http + + acl host_api hdr_dom(host) -i "api.${HAPROXY_HOSTNAME}" + use_backend backend_api if host_api + + acl host_registry hdr_dom(host) -i "registry.${HAPROXY_HOSTNAME}" + use_backend backend_registry if host_registry + + acl host_vpn hdr_dom(host) -i "vpn.${HAPROXY_HOSTNAME}" + use_backend backend_vpn if host_vpn + + # FIXME: remove + acl host_proxy hdr_dom(host) -i "devices.${HAPROXY_HOSTNAME}" + use_backend backend_proxy if host_proxy + + # FIXME: remove + acl host_img hdr_dom(host) -i "img.${HAPROXY_HOSTNAME}" + use_backend backend_img if host_img + +frontend ssl-in + mode tcp + bind *:443 + tcp-request inspect-delay 2s + tcp-request content accept if { req.ssl_hello_type 1 } + + acl is_ssl req.ssl_ver 2:3.4 + use_backend redirect-to-https-in if is_ssl + use_backend vpn-devices if !is_ssl + +backend redirect-to-https-in + mode tcp + balance roundrobin + server localhost 127.0.0.1:444 send-proxy-v2 + +frontend https-in + mode http + option forwardfor + bind 127.0.0.1:444 ssl crt /etc/ssl/private/root.chain.pem accept-proxy + reqadd X-Forwarded-Proto:\ https + + acl host_api hdr_dom(host) -i "api.${HAPROXY_HOSTNAME}" + use_backend backend_api if host_api + + acl host_registry hdr_dom(host) -i "registry.${HAPROXY_HOSTNAME}" + use_backend backend_registry if host_registry + + # FIXME: remove + acl host_proxy hdr_dom(host) -i "devices.${HAPROXY_HOSTNAME}" + use_backend backend_proxy if host_proxy + + # FIXME: remove + acl host_img hdr_dom(host) -i "img.${HAPROXY_HOSTNAME}" + use_backend backend_img if host_img + +backend backend_api + mode http + option forwardfor + balance roundrobin + server resin_api_1 api:80 check port 80 + +backend backend_registry + mode http + option forwardfor + balance roundrobin + server resin_registry_1 registry:80 check port 80 + +backend backend_vpn + mode http + option forwardfor + balance roundrobin + server resin_vpn_1 vpn:80 check port 80 + +# FIXME: remove +backend backend_proxy + mode http + option forwardfor + balance roundrobin + server resin_proxy_1 proxy:80 send-proxy check-send-proxy port 80 + +# FIXME: remove +backend backend_img + mode http + option forwardfor + balance roundrobin + server resin_img_1 img:80 check port 80 + +# FIXME: remove +frontend devices-ssh-gateway + mode tcp + bind *:222 + default_backend devices-ssh-gateway + timeout client 1h + +# FIXME: remove +backend devices-ssh-gateway + mode tcp + server resin_proxy_1 proxy:2222 check port 2222 + +backend vpn-devices + mode tcp + server resin_vpn_1 vpn:443 send-proxy-v2 check-send-proxy port 443 + +frontend db + mode tcp + bind *:5432 + default_backend db + timeout client 1h + +backend db + mode tcp + server resin_db_1 db:5432 check port 5432 diff --git a/scripts/_keyid.js b/scripts/_keyid.js new file mode 100644 index 0000000..61ee419 --- /dev/null +++ b/scripts/_keyid.js @@ -0,0 +1,79 @@ +'use strict'; + +var crypto = require('crypto'); +var fs = require('fs'); + +var base32 = (function() { + // Extracted from https://github.com/chrisumbel/thirty-two + // to avoid having to install packages for this script. + var charTable = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; + var byteTable = [ + 0xff, 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, + 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, + 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, + 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, + 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff + ]; + + function quintetCount(buff) { + var quintets = Math.floor(buff.length / 5); + return buff.length % 5 == 0 ? quintets: quintets + 1; + } + + return function(plain) { + if (!Buffer.isBuffer(plain)) { + plain = new Buffer(plain); + } + var i = 0; + var j = 0; + var shiftIndex = 0; + var digit = 0; + var encoded = new Buffer(quintetCount(plain) * 8); + + /* byte by byte isn't as pretty as quintet by quintet but tests a bit + faster. will have to revisit. */ + while(i < plain.length) { + var current = plain[i]; + + if(shiftIndex > 3) { + digit = current & (0xff >> shiftIndex); + shiftIndex = (shiftIndex + 5) % 8; + digit = (digit << shiftIndex) | ((i + 1 < plain.length) ? + plain[i + 1] : 0) >> (8 - shiftIndex); + i++; + } else { + digit = (current >> (8 - (shiftIndex + 5))) & 0x1f; + shiftIndex = (shiftIndex + 5) % 8; + if(shiftIndex == 0) i++; + } + + encoded[j] = charTable.charCodeAt(digit); + j++; + } + + for (i = j; i < encoded.length; i++) { + encoded[i] = 0x3d; //'='.charCodeAt(0) + } + return encoded; + } +})(); + +function joseKeyId(der) { + var hasher = crypto.createHash('sha256'); + hasher.update(der); + var b32 = base32(hasher.digest().slice(0, 30)).toString('ascii'); + var chunks = []; + for (var i = 0; i < b32.length; i += 4) { + chunks.push(b32.substr(i, 4)); + } + return chunks.join(':'); +} + +var derFilePath = process.argv[2]; +var der = fs.readFileSync(derFilePath); +process.stdout.write(joseKeyId(der)); \ No newline at end of file diff --git a/scripts/gen-root-ca-cert b/scripts/gen-root-ca-cert new file mode 100755 index 0000000..d4086ef --- /dev/null +++ b/scripts/gen-root-ca-cert @@ -0,0 +1,55 @@ +#!/bin/sh + +set -e + +CN=$1 +OUT=${2:-.} + +CA="${CN}-ca" +CA_FILE="${OUT}/ca" +CN_FILE="${OUT}/root" + +CN_EXPIRY_DAYS=730 +CA_EXPIRY_DAYS=3650 + +usage() { + echo "usage: $0 HOST_NAME [OUT]" + echo + echo " HOST_NAME the domain name the certificate is valid for, eg. example.com" + echo " OUT path to output directory generated files will be placed in" + echo +} + +if [ -z "$CN" ]; then + usage + exit 1 +fi + +cat > "${CN_FILE}.v3.ext" </dev/null +openssl req -x509 -new -nodes -sha256 -days $CA_EXPIRY_DAYS -key "${CA_FILE}.key" -subj "/CN=${CA}" -out "${CA_FILE}.pem" 2>/dev/null + +# Create a secret key and Certificate Signing Request (CSR) for the domain +openssl genrsa -out "${CN_FILE}.key" 2048 2>/dev/null +openssl req -new -key "${CN_FILE}.key" -subj "/CN=${CN}" -out "${CN_FILE}.csr" 2>/dev/null + +# Sign the request with the self-signed CA and extension file +openssl x509 -req -sha256 -days $CN_EXPIRY_DAYS -in "${CN_FILE}.csr" -CA "${CA_FILE}.pem" -CAkey "${CA_FILE}.key" -CAcreateserial -out "${CN_FILE}.pem" -extfile "${CN_FILE}.v3.ext" 2>/dev/null + +# Create the custom certificate chain file +cat "${CN_FILE}.pem" "${CA_FILE}.pem" "${CN_FILE}.key" >"${CN_FILE}.chain.pem" + +# Cleanup +rm "${CA_FILE}.key" "${CA_FILE}.pem" "${CN_FILE}.key" "${CN_FILE}.pem" "${CN_FILE}.csr" "${CN_FILE}.v3.ext" + +echo "ROOT_CA=${CN_FILE}.chain.pem" diff --git a/scripts/gen-token-auth-cert b/scripts/gen-token-auth-cert new file mode 100755 index 0000000..29197c2 --- /dev/null +++ b/scripts/gen-token-auth-cert @@ -0,0 +1,42 @@ +#!/bin/sh + +set -e + +CMD=$0 +DIR=$(dirname "$CMD") + +CN=$1 +OUT=${2:-.} + +CERT_FILE="${OUT}/token-auth" +EXPIRY_DAYS=730 + +usage() { + echo "usage: $0 HOST_NAME [OUT]" + echo + echo " HOST_NAME the domain name the certificate is valid for, eg. example.com" + echo " OUT path to output directory generated files will be placed in" + echo +} + +keyid() { + # FIXME: do this in bash or python, not node + nodejs "${DIR}/_keyid.js" "$1" +} + +if [ -z "$CN" ]; then + usage + exit 1 +fi + +openssl ecparam -name prime256v1 -genkey -noout -out "${CERT_FILE}.pem" 2>/dev/null +openssl req -x509 -new -nodes -days "${EXPIRY_DAYS}" -key "${CERT_FILE}.pem" -subj "/CN=api.${CN}" -out "${CERT_FILE}.crt" 2>/dev/null +openssl ec -in "${CERT_FILE}.pem" -pubout -outform DER -out "${CERT_FILE}".der 2>/dev/null +keyid "${CERT_FILE}".der >"${CERT_FILE}".kid + +# Cleanup +rm "${CERT_FILE}.der" + +echo "PUB=${CERT_FILE}.crt" +echo "KEY=${CERT_FILE}.pem" +echo "KID=${CERT_FILE}.kid" diff --git a/scripts/make-env b/scripts/make-env new file mode 100755 index 0000000..8f303f4 --- /dev/null +++ b/scripts/make-env @@ -0,0 +1,40 @@ +#!/bin/sh + +set -e + +HOST_NAME="$1" +ROOT_CA="$2" +TOKEN_AUTH_PUB="$3" +TOKEN_AUTH_KEY="$4" +TOKEN_AUTH_KID="$5" + +usage() { + echo "usage: $0 HOST_NAME ROOT_CA TOKEN_AUTH_PUB TOKEN_AUTH_KEY TOKEN_AUTH_KID" + echo +} + +randstr() { + LC_CTYPE=C tr -dc A-Za-z0-9 < /dev/urandom | fold -w ${1:-32} | head -n 1 +} + +b64encode() { + cat "$1" | base64 --wrap=0 2>/dev/null || cat "$1" | base64 --break=0 +} + +if [ -z "$HOST_NAME" ] || [ -z "$ROOT_CA" ] || [ -z "$TOKEN_AUTH_PUB" ] || [ -z "$TOKEN_AUTH_KEY" ] || [ -z "$TOKEN_AUTH_KID" ]; then + usage + exit 1 +fi + +cat </dev/null 2>&1 ; then + echo "adding $name" + echo "127.0.0.1 $name" >>$tmp + fi +done +cat $tmp | sudo tee -a /etc/hosts >/dev/null +rm -f $tmp diff --git a/scripts/run-fig-command b/scripts/run-fig-command new file mode 100755 index 0000000..3332ca8 --- /dev/null +++ b/scripts/run-fig-command @@ -0,0 +1,31 @@ +#!/bin/sh + +set -e + +CMD=$0 +DIR=$(dirname "$CMD") +BASE_DIR=$(dirname "$DIR") + +echo_bold() { + printf "\033[1m${@}\033[0m\n" +} + +PROJECT_FILE="${BASE_DIR}/.project" +if [ ! -f "$PROJECT_FILE" ]; then + echo_bold 'No project activated. Please create or select an existing one first.' + echo_bold 'See README.md for help.' + exit 1 +fi +PROJECT=$(cat "$PROJECT_FILE") +if [ ! -f "${PROJECT}/activate" ]; then + echo_bold 'No project activated. Please create or select an existing one first.' + echo_bold 'See README.md for help.' + exit 1 +fi +PROJECT_NAME=$(basename "$PROJECT") + +. "${PROJECT}/activate"; docker-compose \ + --project-name $PROJECT_NAME \ + -f "${BASE_DIR}/compose/services.yml" \ + -f "${PROJECT}/docker-compose.yml" \ + "$@" diff --git a/scripts/select-project b/scripts/select-project new file mode 100755 index 0000000..451f119 --- /dev/null +++ b/scripts/select-project @@ -0,0 +1,34 @@ +#!/bin/sh + +set -e + +CMD=$0 +DIR=$(dirname "$CMD") +BASE_DIR=$(dirname "$DIR") +PROJECT_PATH="$1" + +usage() { + echo "usage: $0 PROJECT_PATH" + echo + echo " PROJECT_PATH path to the project; can be relative to open-balena root." + echo +} + +if [ -z "$PROJECT_PATH" ]; then + usage + exit 1 +fi + +PROJECT_DIR=$(realpath "$PROJECT_PATH") + +if [ ! -d "$PROJECT_DIR" ]; then + echo 'Project path refers to a directory that does not exist.' + exit 1 +fi + +if [ ! -f "${PROJECT_DIR}/activate" ]; then + echo 'Project path refers to a directory that is not a valid porject.' + exit 1 +fi + +echo -n $PROJECT_DIR >"${BASE_DIR}/.project" diff --git a/scripts/start-project b/scripts/start-project new file mode 100755 index 0000000..ef2d38f --- /dev/null +++ b/scripts/start-project @@ -0,0 +1,58 @@ +#!/bin/sh + +set -e + +CMD=$0 +DIR=$(dirname "$CMD") + +HOST_NAME=${1:-openbalena.local} +PROJECT_NAME=${2:-demo} + +PROJECT_DIR="$(pwd)/${PROJECT_NAME}" +CERTS_DIR="${PROJECT_DIR}/certs" + +usage() { + echo "usage: $0 [HOST_NAME [PROJECT_NAME]]" + echo + echo " HOST_NAME the domain name this deployment will run as, eg. example.com. Default is 'openbalena.local'" + echo " PROJECT_NAME a name for the deployment, eg. staging. Default is 'demo'" + echo +} + +echo_bold() { + printf "\033[1m${@}\033[0m\n" +} + +if [ -d "$PROJECT_DIR" ]; then + echo 'Project directory already exists. Please remove it or specify another project name.' + exit 1 +fi + +echo_bold "==> Creating new project at: $PROJECT_DIR" +mkdir -p "$PROJECT_DIR" "$CERTS_DIR" + +echo_bold "==> Creating root CA cert..." +"${DIR}/gen-root-ca-cert" $HOST_NAME "$CERTS_DIR" + +echo_bold "==> Creating token auth cert..." +"${DIR}/gen-token-auth-cert" $HOST_NAME "$CERTS_DIR" + +echo_bold "==> Setting up environment..." +cat >"${PROJECT_DIR}/activate" < Adding default compose file..." +echo "version: '2.1'" >"${PROJECT_DIR}/docker-compose.yml" + +# FIXME: should be explicitly requested via a flag +echo_bold "==> Patching /etc/hosts..." +"${DIR}/patch-hosts" $HOST_NAME + +echo_bold "==> Activating project..." +"${DIR}/select-project" "$PROJECT_DIR" diff --git a/src/README b/src/README new file mode 100644 index 0000000..6952fa9 --- /dev/null +++ b/src/README @@ -0,0 +1 @@ +This is the working folder for any specific container you might want to work on.