Compare commits

...

6 Commits

Author SHA1 Message Date
210bdcda37 v0.2.2 2019-03-08 15:44:55 +02:00
fac66040c8 Merge pull request #39 from balena-io/add-codeowners
codeowners: Add CODEOWNERS file
2019-03-08 13:42:59 +00:00
85a69c1ef1 codeowners: Add CODEOWNERS file
Add a CODEOWNERS file which includes the main repo owners.

Change-type: patch
Signed-off-by: Rich Bayliss <rich@balena.io>
2019-03-08 13:38:49 +00:00
e37a61e5f0 v0.2.1 2019-03-08 14:31:30 +02:00
0fc85ff5b6 Merge pull request #38 from balena-io/add-acme-support
certs: Add support for an ACME certificate provider
2019-03-08 12:29:44 +00:00
99dd615e55 certs: Add support for an ACME certificate provider
Add a service which will acquire certificates from an ACME cert
provider, such as LetsEncrypt (), to allow an openBalena instance
to use a publicly trusted certificate instead of the self-signed
one it wil generate on setup.

Change-type: patch
Signed-off-by: Rich Bayliss <rich@balena.io>
2019-03-08 12:23:46 +00:00
14 changed files with 377 additions and 38 deletions

2
.github/CODEOWNERS vendored Normal file
View File

@ -0,0 +1,2 @@
# Main repo owners:
* @dfunckt @richbayliss

View File

@ -4,6 +4,16 @@ All notable changes to this project will be documented in this file
automatically by Versionist. DO NOT EDIT THIS FILE MANUALLY! automatically by Versionist. DO NOT EDIT THIS FILE MANUALLY!
This project adheres to [Semantic Versioning](http://semver.org/). This project adheres to [Semantic Versioning](http://semver.org/).
# v0.2.2
## (2019-03-08)
* codeowners: Add CODEOWNERS file [Rich Bayliss]
# v0.2.1
## (2019-03-08)
* certs: Add support for an ACME certificate provider [Rich Bayliss]
# v0.2.0 # v0.2.0
## (2019-01-25) ## (2019-01-25)

View File

@ -1 +1 @@
0.2.0 0.2.2

20
cert-provider/Dockerfile Normal file
View File

@ -0,0 +1,20 @@
FROM alpine
EXPOSE 80
WORKDIR /usr/src/app
VOLUME [ "/usr/src/app/certs" ]
RUN apk add --update bash curl git openssl ncurses socat
RUN git clone https://github.com/Neilpang/acme.sh.git && \
cd acme.sh && \
git checkout 08357e3cb0d80c84bdaf3e42ce0e439665387f57 . && \
./acme.sh --install \
--cert-home /usr/src/app/certs
COPY entry.sh /entry.sh
COPY cert-provider.sh ./cert-provider.sh
COPY fake-le-bundle.pem ./
ENTRYPOINT [ "/entry.sh" ]
CMD [ "/usr/src/app/cert-provider.sh" ]

181
cert-provider/cert-provider.sh Executable file
View File

@ -0,0 +1,181 @@
#!/bin/bash
# the acme.sh client script, installed via Git in the Dockerfile...
ACME_BIN="$(realpath ~/.acme.sh/acme.sh)"
# the path to a bundle of certs to verify a LetsEncrypt staging certificate until Apr 2036...
ACME_STAGING_CA="/usr/src/app/fake-le-bundle.pem"
# the path to a file which stores the last successful mode of certificate we acquired...
ACME_MODE_FILE="/usr/src/app/certs/last_run_mode"
# colour output helpers...
reset=$(tput -T xterm sgr0)
red=$(tput -T xterm setaf 1)
green=$(tput -T xterm setaf 2)
yellow=$(tput -T xterm setaf 3)
blue=$(tput -T xterm setaf 4)
logError() {
echo "${red}[Error]${reset} $1"
}
logWarn() {
echo "${yellow}[Warn]${reset} $1"
}
logInfo() {
echo "${blue}[Info]${reset} $1"
}
logSuccess() {
echo "${green}[Success]${reset} $1"
}
logErrorAndStop() {
logError "$1 [Stopping]"
while true; do
# do nothing forever...
sleep 60
done
}
retryWithDelay() {
RETRIES=${2:-3}
DELAY=${3:-5}
local ATTEMPT=0
while [ $RETRIES -gt $ATTEMPT ]; do
let "ATTEMPT++"
if $1; then
return $?
fi
echo "($ATTEMPT/$RETRIES) Retrying in ${DELAY} seconds..."
sleep $DELAY
done
return 1
}
waitForOnline() {
ADDRESS="${1,,}"
logInfo "Waiting for ${ADDRESS} to be available via HTTP..."
retryWithDelay "curl --output /dev/null --silent --head --fail http://${ADDRESS}" 6 5
}
isUsingStagingCert() {
HOST="${1,,}"
echo "" | openssl s_client -host "$HOST" -port 443 -showcerts 2>/dev/null | awk '/BEGIN CERT/ {p=1} ; p==1; /END CERT/ {p=0}' | openssl verify -CAfile "$ACME_STAGING_CA" > /dev/null 2>&1
}
pre-flight() {
case "$ACTIVE" in
"true"|"yes")
;;
*)
logError "ACTIVE variable is not enabled. Value should be \"true\" or \"yes\" to continue."
return 1
;;
esac
if [ -z "$DOMAINS" ]; then
logError "DOMAINS must be set. Value should be a comma-delimited string of domains."
return 1
else
IFS=, read -r -a ACME_DOMAINS <<< "$DOMAINS"
IFS=' ' read -r -a ACME_DOMAIN_ARGS <<< "${ACME_DOMAINS[@]/#/-d }"
fi
if [ -z "$VALIDATION" ]; then
logInfo "VALIDATION not set. Using default: http-01"
VALIDATION="http-01"
else
case "$VALIDATION" in
"http-01")
logInfo "Using validation method: $VALIDATION"
;;
*)
logError "VALIDATION is invalid. Use a valid value: http-01"
return 1
;;
esac
fi
if [ -z "$OUTPUT_PEM" ]; then
logError "OUTPUT_PEM must be set. Value should be the path to install your certificate to."
return 1
fi
}
waitToSeeStagingCert() {
logInfo "Waiting for ${ACME_DOMAINS[0]} to use a staging certificate..."
retryWithDelay "isUsingStagingCert ${ACME_DOMAINS[0]}" 3 5
}
lastAcquiredCertFor() {
ACME_MODE="${1:-none}"
ACME_LAST_MODE="$(cat $ACME_MODE_FILE || echo '')"
logInfo "Last acquired certificate for ${ACME_LAST_MODE^^}"
[ "${ACME_LAST_MODE,,}" == "${ACME_MODE,,}" ]
}
acquireCertificate() {
ACME_MODE="${1:-staging}"
ACME_FORCE="${2:-false}"
ACME_OPTS=()
if [ "${ACME_FORCE,,}" == "true" ];then ACME_OPTS+=("--force"); fi
case "$ACME_MODE" in
"production")
logInfo "Using PRODUCTION mode"
;;
*)
logInfo "Using STAGING mode"
ACME_OPTS+=("--staging")
;;
esac
case "$VALIDATION" in
"http-01")
ACME_OPTS+=("--standalone")
;;
*)
logError "VALIDATION is invalid. Use a valid value: http-01"
return 1
;;
esac
if ! waitForOnline "${ACME_DOMAINS[0]}"; then
logError "Unable to access site over HTTP"
return 1
fi
logInfo "Issuing certificates..."
"$ACME_BIN" --issue "${ACME_OPTS[@]}" "${ACME_DOMAIN_ARGS[@]}"
logInfo "Installing certificates..." && \
"$ACME_BIN" --install-cert "${ACME_DOMAIN_ARGS[@]}" \
--cert-file /tmp/cert.pem \
--key-file /tmp/key.pem \
--fullchain-file /tmp/fullchain.pem \
--reloadcmd "cat /tmp/fullchain.pem /tmp/key.pem > $OUTPUT_PEM" && \
echo "${ACME_MODE}" > "${ACME_MODE_FILE}"
}
pre-flight || logErrorAndStop "Unable to continue due to misconfiguration. See errors above."
waitForOnline "${ACME_DOMAINS[0]}" || logErrorAndStop "Unable to access ${ACME_DOMAINS[0]} on port 80. This is needed for certificate validation."
if ! lastAcquiredCertFor "production"; then
acquireCertificate "staging" || logErrorAndStop "Unable to acquire a staging certificate."
waitToSeeStagingCert || logErrorAndStop "Unable to detect certificate change over. Cannot issue a production certificate."
acquireCertificate "production" "true" || logErrorAndStop "Unable to acquire a production certificate."
fi
logSuccess "Done!"
logInfo "Running cron..."
crond -f -d 7

3
cert-provider/entry.sh Executable file
View File

@ -0,0 +1,3 @@
#!/bin/bash
exec "$@"

View File

@ -0,0 +1,56 @@
-----BEGIN CERTIFICATE-----
MIIFATCCAumgAwIBAgIRAKc9ZKBASymy5TLOEp57N98wDQYJKoZIhvcNAQELBQAw
GjEYMBYGA1UEAwwPRmFrZSBMRSBSb290IFgxMB4XDTE2MDMyMzIyNTM0NloXDTM2
MDMyMzIyNTM0NlowGjEYMBYGA1UEAwwPRmFrZSBMRSBSb290IFgxMIICIjANBgkq
hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA+pYHvQw5iU3v2b3iNuYNKYgsWD6KU7aJ
diddtZQxSWYzUI3U0I1UsRPTxnhTifs/M9NW4ZlV13ZfB7APwC8oqKOIiwo7IwlP
xg0VKgyz+kT8RJfYr66PPIYP0fpTeu42LpMJ+CKo9sbpgVNDZN2z/qiXrRNX/VtG
TkPV7a44fZ5bHHVruAxvDnylpQxJobtCBWlJSsbIRGFHMc2z88eUz9NmIOWUKGGj
EmP76x8OfRHpIpuxRSCjn0+i9+hR2siIOpcMOGd+40uVJxbRRP5ZXnUFa2fF5FWd
O0u0RPI8HON0ovhrwPJY+4eWKkQzyC611oLPYGQ4EbifRsTsCxUZqyUuStGyp8oa
aoSKfF6X0+KzGgwwnrjRTUpIl19A92KR0Noo6h622OX+4sZiO/JQdkuX5w/HupK0
A0M0WSMCvU6GOhjGotmh2VTEJwHHY4+TUk0iQYRtv1crONklyZoAQPD76hCrC8Cr
IbgsZLfTMC8TWUoMbyUDgvgYkHKMoPm0VGVVuwpRKJxv7+2wXO+pivrrUl2Q9fPe
Kk055nJLMV9yPUdig8othUKrRfSxli946AEV1eEOhxddfEwBE3Lt2xn0hhiIedbb
Ftf/5kEWFZkXyUmMJK8Ra76Kus2ABueUVEcZ48hrRr1Hf1N9n59VbTUaXgeiZA50
qXf2bymE6F8CAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMB
Af8wHQYDVR0OBBYEFMEmdKSKRKDm+iAo2FwjmkWIGHngMA0GCSqGSIb3DQEBCwUA
A4ICAQBCPw74M9X/Xx04K1VAES3ypgQYH5bf9FXVDrwhRFSVckria/7dMzoF5wln
uq9NGsjkkkDg17AohcQdr8alH4LvPdxpKr3BjpvEcmbqF8xH+MbbeUEnmbSfLI8H
sefuhXF9AF/9iYvpVNC8FmJ0OhiVv13VgMQw0CRKkbtjZBf8xaEhq/YqxWVsgOjm
dm5CAQ2X0aX7502x8wYRgMnZhA5goC1zVWBVAi8yhhmlhhoDUfg17cXkmaJC5pDd
oenZ9NVhW8eDb03MFCrWNvIh89DDeCGWuWfDltDq0n3owyL0IeSn7RfpSclpxVmV
/53jkYjwIgxIG7Gsv0LKMbsf6QdBcTjhvfZyMIpBRkTe3zuHd2feKzY9lEkbRvRQ
zbh4Ps5YBnG6CKJPTbe2hfi3nhnw/MyEmF3zb0hzvLWNrR9XW3ibb2oL3424XOwc
VjrTSCLzO9Rv6s5wi03qoWvKAQQAElqTYRHhynJ3w6wuvKYF5zcZF3MDnrVGLbh1
Q9ePRFBCiXOQ6wPLoUhrrbZ8LpFUFYDXHMtYM7P9sc9IAWoONXREJaO08zgFtMp4
8iyIYUyQAbsvx8oD2M8kRvrIRSrRJSl6L957b4AFiLIQ/GgV2curs0jje7Edx34c
idWw1VrejtwclobqNMVtG3EiPUIpJGpbMcJgbiLSmKkrvQtGng==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEqzCCApOgAwIBAgIRAIvhKg5ZRO08VGQx8JdhT+UwDQYJKoZIhvcNAQELBQAw
GjEYMBYGA1UEAwwPRmFrZSBMRSBSb290IFgxMB4XDTE2MDUyMzIyMDc1OVoXDTM2
MDUyMzIyMDc1OVowIjEgMB4GA1UEAwwXRmFrZSBMRSBJbnRlcm1lZGlhdGUgWDEw
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDtWKySDn7rWZc5ggjz3ZB0
8jO4xti3uzINfD5sQ7Lj7hzetUT+wQob+iXSZkhnvx+IvdbXF5/yt8aWPpUKnPym
oLxsYiI5gQBLxNDzIec0OIaflWqAr29m7J8+NNtApEN8nZFnf3bhehZW7AxmS1m0
ZnSsdHw0Fw+bgixPg2MQ9k9oefFeqa+7Kqdlz5bbrUYV2volxhDFtnI4Mh8BiWCN
xDH1Hizq+GKCcHsinDZWurCqder/afJBnQs+SBSL6MVApHt+d35zjBD92fO2Je56
dhMfzCgOKXeJ340WhW3TjD1zqLZXeaCyUNRnfOmWZV8nEhtHOFbUCU7r/KkjMZO9
AgMBAAGjgeMwgeAwDgYDVR0PAQH/BAQDAgGGMBIGA1UdEwEB/wQIMAYBAf8CAQAw
HQYDVR0OBBYEFMDMA0a5WCDMXHJw8+EuyyCm9Wg6MHoGCCsGAQUFBwEBBG4wbDA0
BggrBgEFBQcwAYYoaHR0cDovL29jc3Auc3RnLXJvb3QteDEubGV0c2VuY3J5cHQu
b3JnLzA0BggrBgEFBQcwAoYoaHR0cDovL2NlcnQuc3RnLXJvb3QteDEubGV0c2Vu
Y3J5cHQub3JnLzAfBgNVHSMEGDAWgBTBJnSkikSg5vogKNhcI5pFiBh54DANBgkq
hkiG9w0BAQsFAAOCAgEABYSu4Il+fI0MYU42OTmEj+1HqQ5DvyAeyCA6sGuZdwjF
UGeVOv3NnLyfofuUOjEbY5irFCDtnv+0ckukUZN9lz4Q2YjWGUpW4TTu3ieTsaC9
AFvCSgNHJyWSVtWvB5XDxsqawl1KzHzzwr132bF2rtGtazSqVqK9E07sGHMCf+zp
DQVDVVGtqZPHwX3KqUtefE621b8RI6VCl4oD30Olf8pjuzG4JKBFRFclzLRjo/h7
IkkfjZ8wDa7faOjVXx6n+eUQ29cIMCzr8/rNWHS9pYGGQKJiY2xmVC9h12H99Xyf
zWE9vb5zKP3MVG6neX1hSdo7PEAb9fqRhHkqVsqUvJlIRmvXvVKTwNCP3eCjRCCI
PTAvjV+4ni786iXwwFYNz8l3PmPLCyQXWGohnJ8iBm+5nk7O2ynaPVW0U2W+pt2w
SVuvdDM5zGv2f9ltNWUiYZHJ1mmO97jSY/6YfdOUH66iRtQtDkHBRdkNBsMbD+Em
2TgBldtHNSJBfB3pm9FblgOcJ0FSWcUDWJ7vO0+NTXlgrRofRT6pVywzxVo6dND0
WzYlTWeUVsO40xJqhgUQRER9YLOLxJ0O6C8i0xFxAMKOtSdodMB3RIwt7RFQ0uyt
n5Z5MqkYhlMI3J1tPRTp1nEt9fyGspBOO05gi148Qasp+3N+svqKomoQglNoAxU=
-----END CERTIFICATE-----

View File

@ -1,10 +1,12 @@
version: '2.1' version: "2.1"
volumes: volumes:
db: certs: {}
registry: cert-provider: {}
s3: db: {}
redis: redis: {}
registry: {}
s3: {}
services: services:
api: api:
@ -20,7 +22,7 @@ services:
API_VPN_SERVICE_API_KEY: ${OPENBALENA_API_VPN_SERVICE_API_KEY} API_VPN_SERVICE_API_KEY: ${OPENBALENA_API_VPN_SERVICE_API_KEY}
BALENA_ROOT_CA: ${OPENBALENA_ROOT_CA} BALENA_ROOT_CA: ${OPENBALENA_ROOT_CA}
COOKIE_SESSION_SECRET: ${OPENBALENA_COOKIE_SESSION_SECRET} COOKIE_SESSION_SECRET: ${OPENBALENA_COOKIE_SESSION_SECRET}
DB_HOST: db.${OPENBALENA_HOST_NAME} DB_HOST: db
DB_PASSWORD: docker DB_PASSWORD: docker
DB_PORT: 5432 DB_PORT: 5432
DB_USER: docker DB_USER: docker
@ -36,10 +38,10 @@ services:
JSON_WEB_TOKEN_EXPIRY_MINUTES: 10080 JSON_WEB_TOKEN_EXPIRY_MINUTES: 10080
JSON_WEB_TOKEN_SECRET: ${OPENBALENA_JWT_SECRET} JSON_WEB_TOKEN_SECRET: ${OPENBALENA_JWT_SECRET}
MIXPANEL_TOKEN: __unused__ MIXPANEL_TOKEN: __unused__
PRODUCTION_MODE: '${OPENBALENA_PRODUCTION_MODE}' PRODUCTION_MODE: "${OPENBALENA_PRODUCTION_MODE}"
PUBNUB_PUBLISH_KEY: __unused__ PUBNUB_PUBLISH_KEY: __unused__
PUBNUB_SUBSCRIBE_KEY: __unused__ PUBNUB_SUBSCRIBE_KEY: __unused__
REDIS_HOST: redis.${OPENBALENA_HOST_NAME} REDIS_HOST: redis
REDIS_PORT: 6379 REDIS_PORT: 6379
REGISTRY2_HOST: registry.${OPENBALENA_HOST_NAME} REGISTRY2_HOST: registry.${OPENBALENA_HOST_NAME}
REGISTRY_HOST: registry.${OPENBALENA_HOST_NAME} REGISTRY_HOST: registry.${OPENBALENA_HOST_NAME}
@ -49,7 +51,7 @@ services:
TOKEN_AUTH_CERT_KEY: ${OPENBALENA_TOKEN_AUTH_KEY} TOKEN_AUTH_CERT_KEY: ${OPENBALENA_TOKEN_AUTH_KEY}
TOKEN_AUTH_CERT_KID: ${OPENBALENA_TOKEN_AUTH_KID} TOKEN_AUTH_CERT_KID: ${OPENBALENA_TOKEN_AUTH_KID}
TOKEN_AUTH_CERT_PUB: ${OPENBALENA_TOKEN_AUTH_PUB} TOKEN_AUTH_CERT_PUB: ${OPENBALENA_TOKEN_AUTH_PUB}
TOKEN_AUTH_JWT_ALGO: 'ES256' TOKEN_AUTH_JWT_ALGO: "ES256"
VPN_HOST: vpn.${OPENBALENA_HOST_NAME} VPN_HOST: vpn.${OPENBALENA_HOST_NAME}
VPN_PORT: 443 VPN_PORT: 443
VPN_SERVICE_API_KEY: ${OPENBALENA_VPN_SERVICE_API_KEY} VPN_SERVICE_API_KEY: ${OPENBALENA_VPN_SERVICE_API_KEY}
@ -73,10 +75,10 @@ services:
BALENA_ROOT_CA: ${OPENBALENA_ROOT_CA} BALENA_ROOT_CA: ${OPENBALENA_ROOT_CA}
BALENA_TOKEN_AUTH_ISSUER: api.${OPENBALENA_HOST_NAME} BALENA_TOKEN_AUTH_ISSUER: api.${OPENBALENA_HOST_NAME}
BALENA_TOKEN_AUTH_REALM: https://api.${OPENBALENA_HOST_NAME}/auth/v1/token BALENA_TOKEN_AUTH_REALM: https://api.${OPENBALENA_HOST_NAME}/auth/v1/token
COMMON_REGION: COMMON_REGION:
REGISTRY2_S3_BUCKET: REGISTRY2_S3_BUCKET:
REGISTRY2_S3_KEY: REGISTRY2_S3_KEY:
REGISTRY2_S3_SECRET: REGISTRY2_S3_SECRET:
REGISTRY2_SECRETKEY: ${OPENBALENA_REGISTRY_SECRET_KEY} REGISTRY2_SECRETKEY: ${OPENBALENA_REGISTRY_SECRET_KEY}
REGISTRY2_STORAGEPATH: /data REGISTRY2_STORAGEPATH: /data
@ -94,10 +96,10 @@ services:
BALENA_API_HOST: api.${OPENBALENA_HOST_NAME} BALENA_API_HOST: api.${OPENBALENA_HOST_NAME}
BALENA_ROOT_CA: ${OPENBALENA_ROOT_CA} BALENA_ROOT_CA: ${OPENBALENA_ROOT_CA}
BALENA_VPN_PORT: 443 BALENA_VPN_PORT: 443
PRODUCTION_MODE: '${OPENBALENA_PRODUCTION_MODE}' PRODUCTION_MODE: "${OPENBALENA_PRODUCTION_MODE}"
RESIN_VPN_GATEWAY: 10.2.0.1 RESIN_VPN_GATEWAY: 10.2.0.1
SENTRY_DSN: SENTRY_DSN:
VPN_HAPROXY_USEPROXYPROTOCOL: 'true' VPN_HAPROXY_USEPROXYPROTOCOL: "true"
VPN_OPENVPN_CA_CRT: ${OPENBALENA_VPN_CA} VPN_OPENVPN_CA_CRT: ${OPENBALENA_VPN_CA}
VPN_OPENVPN_SERVER_CRT: ${OPENBALENA_VPN_SERVER_CRT} VPN_OPENVPN_SERVER_CRT: ${OPENBALENA_VPN_SERVER_CRT}
VPN_OPENVPN_SERVER_DH: ${OPENBALENA_VPN_SERVER_DH} VPN_OPENVPN_SERVER_DH: ${OPENBALENA_VPN_SERVER_DH}
@ -135,11 +137,12 @@ services:
build: ../haproxy build: ../haproxy
depends_on: depends_on:
- api - api
- registry - cert-provider
- vpn
- db - db
- s3 - s3
- redis - redis
- registry
- vpn
ports: ports:
- "80:80" - "80:80"
- "443:443" - "443:443"
@ -162,3 +165,15 @@ services:
BALENA_HAPROXY_KEY: ${OPENBALENA_ROOT_KEY} BALENA_HAPROXY_KEY: ${OPENBALENA_ROOT_KEY}
BALENA_ROOT_CA: ${OPENBALENA_ROOT_CA} BALENA_ROOT_CA: ${OPENBALENA_ROOT_CA}
HAPROXY_HOSTNAME: ${OPENBALENA_HOST_NAME} HAPROXY_HOSTNAME: ${OPENBALENA_HOST_NAME}
volumes:
- certs:/certs:ro
cert-provider:
build: ../cert-provider
volumes:
- certs:/certs
- cert-provider:/usr/src/app/certs
environment:
ACTIVE: ${OPENBALENA_ACME_CERT_ENABLED}
DOMAINS: "api.${OPENBALENA_HOST_NAME},registry.${OPENBALENA_HOST_NAME},s3.${OPENBALENA_HOST_NAME},vpn.${OPENBALENA_HOST_NAME}"
OUTPUT_PEM: /certs/open-balena.pem

View File

@ -1,6 +1,10 @@
FROM haproxy:1.8-alpine FROM haproxy:1.9-alpine
VOLUME [ "/certs" ]
RUN apk add --update inotify-tools
COPY haproxy.cfg /usr/local/etc/haproxy/haproxy.cfg COPY haproxy.cfg /usr/local/etc/haproxy/haproxy.cfg
COPY entry.sh /open-balena-entry COPY start-haproxy.sh /start-haproxy
CMD /open-balena-entry CMD /start-haproxy

View File

@ -1,11 +0,0 @@
#!/bin/sh
set -e
HAPROXY_CHAIN=/etc/ssl/private/open-balena.pem
mkdir -p "$(dirname "${HAPROXY_CHAIN}")"
(
echo "${BALENA_HAPROXY_CRT}" | base64 -d
echo "${BALENA_HAPROXY_KEY}" | base64 -d
echo "${BALENA_ROOT_CA}" | base64 -d
) > "${HAPROXY_CHAIN}"
exec haproxy -f /usr/local/etc/haproxy/haproxy.cfg

View File

@ -12,6 +12,9 @@ frontend http-in
bind *:80 bind *:80
reqadd X-Forwarded-Proto:\ http reqadd X-Forwarded-Proto:\ http
acl is_cert_validation path -i -m beg "/.well-known/acme-challenge/"
use_backend cert-provider if is_cert_validation
acl host_api hdr_dom(host) -i "api.${HAPROXY_HOSTNAME}" acl host_api hdr_dom(host) -i "api.${HAPROXY_HOSTNAME}"
use_backend backend_api if host_api use_backend backend_api if host_api
@ -80,6 +83,12 @@ backend backend_s3
option forwardfor option forwardfor
balance roundrobin balance roundrobin
backend cert-provider
mode http
option forwardfor
balance roundrobin
server resin_cert-provider_1 cert-provider:80 no-check
backend vpn-devices backend vpn-devices
mode tcp mode tcp
server resin_vpn_1 vpn:443 send-proxy-v2 check-send-proxy port 443 server resin_vpn_1 vpn:443 send-proxy-v2 check-send-proxy port 443

32
haproxy/start-haproxy.sh Executable file
View File

@ -0,0 +1,32 @@
#!/bin/sh
OPENBALENA_CERT=/etc/ssl/private/open-balena.pem
mkdir -p "$(dirname "${OPENBALENA_CERT}")"
if [ -f "/certs/open-balena.pem" ]; then
echo "Using certificate from cert-provider..."
cp /certs/open-balena.pem "${OPENBALENA_CERT}"
else
echo "Building certificate from environment variables..."
(
echo "${BALENA_HAPROXY_CRT}" | base64 -d
echo "${BALENA_HAPROXY_KEY}" | base64 -d
echo "${BALENA_ROOT_CA}" | base64 -d
) > "${OPENBALENA_CERT}"
fi
haproxy -f /usr/local/etc/haproxy/haproxy.cfg -W &
HAPROXY_PID=$!
while true; do
inotifywait -r -e create -e modify -e delete /certs
if [ -f "/certs/open-balena.pem" ]; then
echo "Updating certificate from cert-provider..."
cp /certs/open-balena.pem "${OPENBALENA_CERT}"
fi
echo "Certificate change detected. Reloading..."
kill -SIGUSR2 $HAPROXY_PID
sleep 1;
done

View File

@ -94,4 +94,5 @@ export OPENBALENA_REGISTRY_SECRET_KEY=$(randstr 32)
export OPENBALENA_SSH_AUTHORIZED_KEYS= export OPENBALENA_SSH_AUTHORIZED_KEYS=
export OPENBALENA_SUPERUSER_EMAIL=$SUPERUSER_EMAIL export OPENBALENA_SUPERUSER_EMAIL=$SUPERUSER_EMAIL
export OPENBALENA_SUPERUSER_PASSWORD=$(printf "%q" "${SUPERUSER_PASSWORD}") export OPENBALENA_SUPERUSER_PASSWORD=$(printf "%q" "${SUPERUSER_PASSWORD}")
export OPENBALENA_ACME_CERT_ENABLED=${ACME_CERT_ENABLED:-false}
STR STR

View File

@ -21,6 +21,10 @@ fi
source "${BASH_SOURCE%/*}/_realpath" source "${BASH_SOURCE%/*}/_realpath"
domainResolves() {
getent hosts "$1" > /dev/null 2>&1
}
CMD="$(realpath "$0")" CMD="$(realpath "$0")"
DIR="$(dirname "${CMD}")" DIR="$(dirname "${CMD}")"
BASE_DIR="$(dirname "${DIR}")" BASE_DIR="$(dirname "${DIR}")"
@ -30,8 +34,9 @@ CERTS_DIR="${CONFIG_DIR}/certs"
DOMAIN=openbalena.local DOMAIN=openbalena.local
usage() { usage() {
echo "usage: $0 [-h] [-p] [-d DOMAIN] -U EMAIL -P PASSWORD" echo "usage: $0 [-c] [-h] [-p] [-d DOMAIN] -U EMAIL -P PASSWORD"
echo echo
echo " -c enable the ACME certificate service in staging or production mode."
echo " -p patch hosts - patch the host /etc/hosts file" echo " -p patch hosts - patch the host /etc/hosts file"
echo " -d DOMAIN the domain name this deployment will run as, eg. example.com. Default is 'openbalena.local'" echo " -d DOMAIN the domain name this deployment will run as, eg. example.com. Default is 'openbalena.local'"
echo " -U EMAIL the email address of the superuser account, used to login to your install from the Balena CLI" echo " -U EMAIL the email address of the superuser account, used to login to your install from the Balena CLI"
@ -41,7 +46,7 @@ usage() {
show_help=false show_help=false
patch_hosts=false patch_hosts=false
while getopts ":hpxd:U:P:" opt; do while getopts ":chpxd:U:P:" opt; do
case "${opt}" in case "${opt}" in
h) show_help=true;; h) show_help=true;;
p) patch_hosts=true;; p) patch_hosts=true;;
@ -49,6 +54,7 @@ while getopts ":hpxd:U:P:" opt; do
d) DOMAIN="${OPTARG}";; d) DOMAIN="${OPTARG}";;
U) SUPERUSER_EMAIL="${OPTARG}";; U) SUPERUSER_EMAIL="${OPTARG}";;
P) SUPERUSER_PASSWORD="${OPTARG}";; P) SUPERUSER_PASSWORD="${OPTARG}";;
c) ACME_CERT_ENABLED="true";;
*) *)
echo "Invalid argument: -${OPTARG}" echo "Invalid argument: -${OPTARG}"
usage usage
@ -68,8 +74,17 @@ if [ "$show_help" = "true" ]; then
exit 1 exit 1
fi fi
if [ ! -z "$ACME_CERT_ENABLED" ]; then
echo "${BLUE}[INFO]${RESET} ACME Certificate request is ${BOLD}ENABLED${RESET}."
if ! domainResolves "api.${DOMAIN}"; then
echo "${YELLOW}[WARN]${RESET} Unable to resolve \"api.${DOMAIN}\"!"
echo "${YELLOW}[WARN]${RESET} This might mean that you cannot use an ACME issued certificate."
fi
fi
echo_bold() { echo_bold() {
printf "\\033[1m%s\\033[0m\\n" "${@}" echo "${BOLD}${@}${RESET}"
} }
echo_bold "==> Creating new configuration at: $CONFIG_DIR" echo_bold "==> Creating new configuration at: $CONFIG_DIR"
@ -110,5 +125,7 @@ fi
echo_bold "==> Success!" echo_bold "==> Success!"
echo ' - Start the instance with: ./scripts/compose up -d' echo ' - Start the instance with: ./scripts/compose up -d'
echo ' - Stop the instance with: ./scripts/compose stop' echo ' - Stop the instance with: ./scripts/compose stop'
echo ' - To create the superuser, see: ./scripts/create-superuser -h'
echo " - Use the following certificate with Balena CLI: ${CERTS_DIR}/root/ca.crt" if [ -z "${ACME_CERT_ENABLED}" ]; then
echo " - Use the following certificate with Balena CLI: ${CERTS_DIR}/root/ca.crt"
fi