diff --git a/bundle/index.js b/bundle/index.js new file mode 100644 index 00000000..2bae5d98 --- /dev/null +++ b/bundle/index.js @@ -0,0 +1,85 @@ +'use strict'; + +const Brule = require('brule'); +const Hapi = require('hapi'); +const Inert = require('inert'); +const Main = require('apr-main'); +const Rollover = require('rollover'); + +const Sso = require('minio-proto-auth'); +const Ui = require('my-joy-beta'); +const Nav = require('joyent-navigation'); +const Api = require('cloudapi-gql'); + +const { + PORT = 3069, + COOKIE_PASSWORD, + COOKIE_DOMAIN, + SDC_KEY_PATH, + SDC_ACCOUNT, + SDC_KEY_ID, + SDC_URL, + BASE_URL = `http://0.0.0.0:${PORT}`, + ROLLBAR_SERVER_TOKEN, + NODE_ENV = 'development' +} = process.env; + +const server = Hapi.server({ + port: PORT, + host: '127.0.0.1' +}); + +Main(async () => { + await server.register([ + // { + // plugin: Rollover, + // options: { + // rollbar: { + // accessToken: ROLLBAR_SERVER_TOKEN, + // reportLevel: 'error' + // } + // } + // }, + { + plugin: Brule, + options: { + auth: false + } + }, + { + plugin: Sso, + options: { + cookie: { + password: COOKIE_PASSWORD, + domain: COOKIE_DOMAIN, + isSecure: false, + isHttpOnly: true, + ttl: 1000 * 60 * 60 // 1 hour + }, + sso: { + keyPath: SDC_KEY_PATH, + keyId: '/' + SDC_ACCOUNT + '/keys/' + SDC_KEY_ID, + apiBaseUrl: SDC_URL, + url: 'https://sso.joyent.com/login', + permissions: { cloudapi: ['/my/*'] }, + baseUrl: BASE_URL, + isDev: NODE_ENV === 'development' + } + } + }, + { + plugin: Nav + }, + { + plugin: Ui + }, + { + plugin: Api + } + ]); + + server.auth.default('sso'); + + await server.start(); + console.log(`server started at http://localhost:${server.info.port}`); +}); diff --git a/bundle/package.json b/bundle/package.json new file mode 100644 index 00000000..b6057d45 --- /dev/null +++ b/bundle/package.json @@ -0,0 +1,21 @@ +{ + "name": "joyent-portal-bundle", + "version": "1.0.0", + "private": true, + "license": "MPL-2.0", + "scripts": { + "start": "NODE_ENV=development PORT=3069 REACT_APP_GQL_PORT=3069 REACT_APP_GQL_PROTOCOL=http node -r ./_env.js index.js", + "prepublish": "echo 0" + }, + "dependencies": { + "apr-main": "^2.0.2", + "brule": "^3.1.0", + "cloudapi-gql": "^4.3.1", + "hapi": "^17.2.0", + "inert": "^5.1.0", + "joyent-navigation": "^1.0.0", + "minio-proto-auth": "^1.1.0", + "my-joy-beta": "^1.0.0", + "rollover": "^1.0.0" + } +} diff --git a/bundle/scripts/gen-keys.sh b/bundle/scripts/gen-keys.sh new file mode 100755 index 00000000..24eb9058 --- /dev/null +++ b/bundle/scripts/gen-keys.sh @@ -0,0 +1,40 @@ +#!/bin/bash +set -e -o pipefail + +TRITON_ACCOUNT=$(triton account get | awk -F": " '/id:/{print $2}') +TRITON_DC=$(triton profile get | awk -F"/" '/url:/{print $3}' | awk -F'.' '{print $1}') + +DEFAULT_DOMAIN=${TRITON_ACCOUNT}.${TRITON_DC}.cns.triton.zone + +read -p "Enter the domain name you plan to use for this key [$DEFAULT_DOMAIN]: " domain +domain="${domain:-$DEFAULT_DOMAIN}" +echo -n "Enter the password to use for the key: " +read -s password +echo +echo "Generating key for $domain" + + + +keys_path=keys-$domain +mkdir -p $keys_path + +openssl genrsa -aes256 -passout pass:$password -out $keys_path/ca.key 4096 +chmod 400 $keys_path/ca.key +openssl req -new -x509 -sha256 -days 730 -key $keys_path/ca.key -out $keys_path/ca.crt -passin pass:$password -subj "/CN=copilot" +chmod 444 $keys_path/ca.crt + + +openssl genrsa -out $keys_path/server.key 2048 +chmod 400 $keys_path/server.key +openssl req -new -key $keys_path/server.key -sha256 -out $keys_path/server.csr -passin pass:$password -subj "/CN=$domain" +openssl x509 -req -days 365 -sha256 -in $keys_path/server.csr -passin pass:$password -CA $keys_path/ca.crt -CAkey $keys_path/ca.key -set_serial 1 -out $keys_path/server.crt +chmod 444 $keys_path/server.crt + +openssl genrsa -out $keys_path/client.key 2048 +openssl req -new -key $keys_path/client.key -out $keys_path/client.csr -subj "/CN=$domain" +openssl x509 -req -days 365 -sha256 -in $keys_path/client.csr -CA $keys_path/ca.crt -CAkey $keys_path/ca.key -set_serial 2 -out $keys_path/client.crt -passin pass:$password +openssl pkcs12 -export -clcerts -in $keys_path/client.crt -inkey $keys_path/client.key -out $keys_path/client.p12 -passout pass:$password + +# open $keys_path/client.p12 & +echo +echo "You can complete setup by running './setup.sh ~/path/to/TRITON_PRIVATE_KEY $keys_path/ca.crt $keys_path/server.key $keys_path/server.crt'" diff --git a/bundle/scripts/setup.sh b/bundle/scripts/setup.sh new file mode 100644 index 00000000..29a5cdc9 --- /dev/null +++ b/bundle/scripts/setup.sh @@ -0,0 +1,235 @@ +#!/bin/bash +set -e -o pipefail + +help() { + echo + echo 'Usage ./setup.sh ~/path/to/TRITON_PRIVATE_KEY ~/path/to/CA_CRT ~/path/to/SERVER_KEY ~/path/to/SERVER_CRT' + echo + echo 'Checks that your Triton and Docker environment is sane and configures' + echo 'an environment file to use.' + echo + echo 'TRITON_PRIVATE_KEY is the filesystem path to an SSH private key' + echo 'used to connect to Triton.' + echo + echo 'CA_CRT is the filesystem path to a certificate authority crt file.' + echo + echo 'SERVER_KEY is the filesystem path to a TLS server key file.' + echo + echo 'SERVER_CRT is the filesystem path to a TLS server crt file.' + echo +} + +# Check for correct configuration +check() { + + if [ -z "$1" ]; then + tput rev # reverse + tput bold # bold + echo 'Please provide a path to a SSH private key to access Triton.' + tput sgr0 # clear + + help + exit 1 + fi + + if [ ! -f "$1" ]; then + tput rev # reverse + tput bold # bold + echo 'SSH private key for Triton is unreadable.' + tput sgr0 # clear + + help + exit 1 + fi + + # Assign args to named vars + TRITON_PRIVATE_KEY_PATH=$1 + + + if [ -z "$2" ]; then + tput rev # reverse + tput bold # bold + echo 'Please provide a path to the NGINX CA crt file.' + tput sgr0 # clear + + help + exit 1 + fi + + if [ ! -f "$2" ]; then + tput rev # reverse + tput bold # bold + echo 'CA certificate for NGINX is unreadable.' + tput sgr0 # clear + + help + exit 1 + fi + + NGINX_CA_CRT_PATH=$2 + + + if [ -z "$3" ]; then + tput rev # reverse + tput bold # bold + echo 'Please provide a path to the server key file.' + tput sgr0 # clear + + help + exit 1 + fi + + if [ ! -f "$3" ]; then + tput rev # reverse + tput bold # bold + echo 'Server key file for NGINX is unreadable.' + tput sgr0 # clear + + help + exit 1 + fi + + NGINX_SERVER_KEY_PATH=$3 + + + if [ -z "$4" ]; then + tput rev # reverse + tput bold # bold + echo 'Please provide a path to the server crt file.' + tput sgr0 # clear + + help + exit 1 + fi + + if [ ! -f "$4" ]; then + tput rev # reverse + tput bold # bold + echo 'Server crt file for NGINX is unreadable.' + tput sgr0 # clear + + help + exit 1 + fi + + NGINX_SERVER_CRT_PATH=$4 + + command -v docker >/dev/null 2>&1 || { + echo + tput rev # reverse + tput bold # bold + echo 'Docker is required, but does not appear to be installed.' + tput sgr0 # clear + echo 'See https://docs.joyent.com/public-cloud/api-access/docker' + exit 1 + } + + command -v triton >/dev/null 2>&1 || { + echo + tput rev # reverse + tput bold # bold + echo 'Error! Joyent Triton CLI is required, but does not appear to be installed.' + tput sgr0 # clear + echo 'See https://www.joyent.com/blog/introducing-the-triton-command-line-tool' + exit 1 + } + + TRITON_USER=$(triton profile get | awk -F": " '/account:/{print $2}') + TRITON_DC=$(triton profile get | awk -F"/" '/url:/{print $3}' | awk -F'.' '{print $1}') + TRITON_ACCOUNT=$(triton account get | awk -F": " '/id:/{print $2}') + + SDC_URL=$(triton env | grep SDC_URL | awk -F"=" '{print $2}' | awk -F"\"" '{print $2}') + SDC_ACCOUNT=$(triton env | grep SDC_ACCOUNT | awk -F"=" '{print $2}' | awk -F"\"" '{print $2}') + SDC_KEY_ID=$(triton env | grep SDC_KEY_ID | awk -F"=" '{print $2}' | awk -F"\"" '{print $2}') + + DOCKER_CERT_PATH=$(triton env | grep DOCKER_CERT_PATH | awk -F"=" '{print $2}') + DOCKER_HOST=$(triton env | grep DOCKER_HOST | awk -F"=" '{print $2}') + + rm _env_consul + rm _env_mysql + rm _env + + echo MYSQL_DATABASE=bridge-db >> _env_mysql + echo 'MYSQL_ROOT_PASSWORD='$(cat /dev/urandom | LC_ALL=C tr -dc 'A-Za-z0-9' | head -c 12) >> _env_mysql + echo MYSQL_USER=bridge-user >> _env_mysql + echo 'MYSQL_PASSWORD='$(cat /dev/urandom | LC_ALL=C tr -dc 'A-Za-z0-9' | head -c 8) >> _env_mysql + + echo >> _env_mysql + + echo '# Consul discovery via Triton CNS' >> _env_consul + echo CONSUL=bridge-consul.svc.${TRITON_ACCOUNT}.${TRITON_DC}.cns.joyent.com >> _env_consul + echo CONSUL_AGENT=1 >> _env_consul + echo >> _env_consul + + TRITON_CREDS_PATH=/root/.triton + + echo '# Allowed list of account Ids who can access the site' >> _env + echo ALLOWED_ACCOUNTS=${TRITON_ACCOUNT} >> _env + echo >> _env + + echo '# Site URL' >> _env + echo BASE_URL=https://bridge.svc.${TRITON_ACCOUNT}.${TRITON_DC}.cns.triton.zone >> _env + echo COOKIE_DOMAIN=triton.zone >> _env + echo >> _env + + echo '# MySQL via Triton CNS' >> _env + echo MYSQL_HOST=bridge-mysql.svc.${TRITON_ACCOUNT}.${TRITON_DC}.cns.joyent.com >> _env + echo >> _env + + echo PORT=8080 >> _env + echo 'COOKIE_PASSWORD='$(cat /dev/urandom | LC_ALL=C tr -dc 'A-Za-z0-9' | head -c 36) >> _env + echo SDC_KEY_PATH=/root/.ssh/id_rsa >> _env + echo DOCKER_CERT_PATH=${TRITON_CREDS_PATH} >> _env + echo TRITON_CREDS_PATH=${TRITON_CREDS_PATH} >> _env + echo DOCKER_TLS_VERIFY=1 >> _env + echo DOCKER_HOST=${DOCKER_HOST} >> _env + echo SDC_URL=${SDC_URL} >> _env + echo SDC_ACCOUNT=${SDC_ACCOUNT} >> _env + echo SDC_KEY_ID=${SDC_KEY_ID} >> _env + echo CONSUL=bridge-consul.svc.${TRITON_ACCOUNT}.${TRITON_DC}.cns.joyent.com >> _env + + echo TRITON_CA=$(cat "${DOCKER_CERT_PATH}"/ca.pem | tr '\n' '#') >> _env + echo TRITON_CA_PATH=${TRITON_CREDS_PATH}/ca.pem >> _env + echo TRITON_KEY=$(cat "${DOCKER_CERT_PATH}"/key.pem | tr '\n' '#') >> _env + echo TRITON_KEY_PATH=${TRITON_CREDS_PATH}/key.pem >> _env + echo TRITON_CERT=$(cat "${DOCKER_CERT_PATH}"/cert.pem | tr '\n' '#') >> _env + echo TRITON_CERT_PATH=${TRITON_CREDS_PATH}/cert.pem >> _env + + echo SDC_KEY=$(cat "${TRITON_PRIVATE_KEY_PATH}" | tr '\n' '#') >> _env + echo SDC_KEY_PUB=$(cat "${TRITON_PRIVATE_KEY_PATH}".pub | tr '\n' '#') >> _env + + echo NGINX_CA_CRT=$(cat "${NGINX_CA_CRT_PATH}" | tr '\n' '#') >> _env + echo NGINX_SERVER_KEY=$(cat "${NGINX_SERVER_KEY_PATH}" | tr '\n' '#') >> _env + echo NGINX_SERVER_CRT=$(cat "${NGINX_SERVER_CRT_PATH}" | tr '\n' '#') >> _env + + echo >> _env +} + +# --------------------------------------------------- +# parse arguments + +# Get function list +funcs=($(declare -F -p | cut -d " " -f 3)) + +until + if [ ! -z "$1" ]; then + # check if the first arg is a function in this file, or use a default + if [[ " ${funcs[@]} " =~ " $1 " ]]; then + cmd=$1 + shift 1 + else + cmd="check" + fi + + $cmd "$@" + if [ $? == 127 ]; then + help + fi + + exit + else + help + fi +do + echo +done \ No newline at end of file diff --git a/commitlint.config.js b/commitlint.config.js index e953113f..79d62efb 100644 --- a/commitlint.config.js +++ b/commitlint.config.js @@ -4,7 +4,7 @@ module.exports = { 'scope-enum': [ 2, 'always', - ['ui-toolkit', 'icons', 'my-joy-beta', 'navigation'] + ['ui-toolkit', 'icons', 'my-joy-beta', 'navigation', 'bundle'] ] } }; diff --git a/package.json b/package.json index f165fde0..47b727c2 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,5 @@ "breeze-nexttick": "0.2.1", "zen-observable": "0.7.1" }, - "workspaces": [ - "packages/*" - ] + "workspaces": ["packages/*", "bundle"] } diff --git a/packages/icons/package.json b/packages/icons/package.json index 586c69e5..4bee4ba6 100644 --- a/packages/icons/package.json +++ b/packages/icons/package.json @@ -27,7 +27,7 @@ "babel-preset-joyent-portal": "^6.0.1", "eslint": "^4.13.1", "eslint-config-joyent-portal": "^3.2.0", - "joyent-react-scripts": "^6.5.1", + "joyent-react-scripts": "^7.2.0", "react": "^16.2.0", "redrun": "^5.10.0" }, diff --git a/packages/my-joy-beta/lib/index.js b/packages/my-joy-beta/lib/index.js new file mode 100644 index 00000000..7c81bee0 --- /dev/null +++ b/packages/my-joy-beta/lib/index.js @@ -0,0 +1,46 @@ +const Inert = require('inert'); +const Path = require('path'); +const Execa = require('execa'); +const { readFile } = require('mz/fs'); + +exports.register = async server => { + await Execa('npm', ['run', 'build'], { + cwd: Path.join(__dirname, '..'), + stdio: 'inherit' + }); + + const indexFile = await readFile( + Path.join(__dirname, '../build/index.html'), + 'utf-8' + ); + + await server.register(Inert); + + server.route([ + { + method: 'GET', + path: '/static/{path*}', + config: { + auth: false, + handler: { + directory: { + path: Path.join(__dirname, '../build/static/'), + redirectToSlash: true, + index: false + } + } + } + }, + { + method: '*', + path: '/{path*}', + config: { + handler: (request, h) => { + return h.response(indexFile).type('text/html'); + } + } + } + ]); +}; + +exports.pkg = require('../package.json'); diff --git a/packages/my-joy-beta/package.json b/packages/my-joy-beta/package.json index 2dcb5c32..27822d17 100644 --- a/packages/my-joy-beta/package.json +++ b/packages/my-joy-beta/package.json @@ -4,7 +4,7 @@ "license": "MPL-2.0", "private": true, "repository": "github:yldio/joyent-portal", - "main": "build/", + "main": "lib/index.js", "scripts": { "dev": "REACT_APP_GQL_PORT=4000 PORT=3069 REACT_APP_GQL_PROTOCOL=http joyent-react-scripts start", "start": "PORT=3069 joyent-react-scripts start", @@ -24,7 +24,6 @@ "constant-case": "^2.0.0", "date-fns": "^1.29.0", "declarative-redux-form": "^2.0.8", - "joyent-manifest-editor": "^1.4.0", "joyent-ui-toolkit": "^4.4.1", "lodash.find": "^4.6.0", "lodash.get": "^4.4.2", @@ -33,6 +32,7 @@ "lodash.sortby": "^4.7.0", "lodash.uniqby": "^4.7.0", "lunr": "^2.1.5", + "mz": "^2.7.0", "normalized-styled-components": "^1.0.17", "param-case": "^2.1.1", "prop-types": "^15.6.0", @@ -58,7 +58,7 @@ "eslint": "^4.13.1", "eslint-config-joyent-portal": "^3.2.0", "jest-styled-components": "^4.9.0", - "joyent-react-scripts": "^6.5.1", + "joyent-react-scripts": "^7.2.0", "react-test-renderer": "^16.2.0", "redrun": "^5.10.0" } diff --git a/packages/my-joy-beta/public/index.html b/packages/my-joy-beta/public/index.html index ed338049..f9fa69eb 100644 --- a/packages/my-joy-beta/public/index.html +++ b/packages/my-joy-beta/public/index.html @@ -15,6 +15,6 @@
- +