diff --git a/Docker/Dockerfile b/Docker/Dockerfile new file mode 100644 index 000000000..59a422226 --- /dev/null +++ b/Docker/Dockerfile @@ -0,0 +1,37 @@ +FROM golang:1.14-alpine as builder + +RUN apk --update --no-cache add \ + build-base \ + git \ + && rm -rf /tmp/* /var/cache/apk/* + +WORKDIR ~ +RUN git clone https://github.com/AdguardTeam/dnsproxy + +WORKDIR dnsproxy/ + +RUN go build -mod=vendor -v + +FROM alpine:3.12 +LABEL maintainer="coolquasar@gmail.com" + +RUN apk update && \ + apk add bash supervisor certbot procps net-tools && \ + rm -rf /tmp/* /var/cache/apk/* + +COPY --from=builder /go/~/dnsproxy/dnsproxy /usr/bin + +COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf + +COPY letsencrypt-wrapper.sh /srv/ +COPY dnsproxy.sh /srv/ +COPY start.sh /srv/ + +VOLUME ["/var/log", "/etc/letsencrypt"] + +EXPOSE "80" +EXPOSE "443" +EXPOSE "784"/udp +EXPOSE "853" + +ENTRYPOINT ["/srv/start.sh"] diff --git a/Docker/README.md b/Docker/README.md new file mode 100644 index 000000000..c9d9ecd32 --- /dev/null +++ b/Docker/README.md @@ -0,0 +1,42 @@ +## dnsproxy - Docker + +This part of the project is to run dnsproxy in docker as server or client. The docker image pulls the current dnsproxy code, builds it and creates an alpine based docker image + +Default version of docker-compose runs dnsproxy as quic server with upstream 1.1.1..1 + 1. Current version of dnsproxy docker supports `DNS-over-TLS, `DNS-over-HTTPS`, `DNSCrypt`, and `DNS-over-QUIC` + 2. Moreover, it can work as a `DNS-over-HTTPS`, `DNS-over-TLS` or `DNS-over-QUIC` server, or a simple passthrough server in a defined port + +Functionalities supported by the docker image will be sub-set of the functionalities supported by dnsproxy current code + +``` +# docker-compose up -d +``` + +### Following changes required in docker-compose.yml. to start dnsproxy as `DNS-over-HTTPS` +``` +SRVPORT: "443" +MODE: "server" +PROTO: "https" +``` + +### Following changes required in docker-compose.yml. to start dnsproxy as `DNS-over-QUIC` +``` +SRVPORT: "784" +MODE: "server" +PROTO: "quic" +``` + + +### Following changes required in docker-compose.yml. to start dnsproxy as `DNS-over-TLS` +``` +SRVPORT: "853" +MODE: "server" +PROTO: "tls" +``` +### Following changes required in docker-compose.yml. to start dnsproxy as `client` +``` +MODE: "client" +LOCALPORT: "1234" # Any local port number +``` + +Remove EDNS flag in docker-compose.yml, if EDNS support is not required diff --git a/Docker/dnsproxy.sh b/Docker/dnsproxy.sh new file mode 100755 index 000000000..58f42be50 --- /dev/null +++ b/Docker/dnsproxy.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +COMMAND="dnsproxy" +LISTENADDR=$LISTEN +SRVPORT=$SRVPORT +CERTPATH=/etc/letsencrypt/live/$DOMAIN/fullchain.pem +KEYPATH=/etc/letsencrypt/live/$DOMAIN/privkey.pem +UPSTREAM=$UPSTREAM_ADDR +RATELIMIT=$RATELIMIT +EDNSFLAG=$EDNS +EDNSIP=$EDNSIP +PORT=$LOCALPORT +PROTOCOL=$PROTO + +case $PROTOCOL in + "quic") SWITCHPORT="-q $SRVPORT --tls-crt=$CERTPATH --tls-key=$KEYPATH";; + "tls") SWITCHPORT="-t $SRVPORT --tls-crt=$CERTPATH --tls-key=$KEYPATH";; + "https") SWITCHPORT="--https-port $SRVPORT --tls-crt=$CERTPATH --tls-key=$KEYPATH";; + "dnscrypt") SWITCHPORT="-y $SRVPORT --tls-crt=$CERTPATH --tls-key=$KEYPATH";; +esac + +case $MODE in + "server") LISTENSWITCH="-l $LISTENADDR"; + if [[ ! -f $CERTPATH ]] + then + echo "Waiting for letsencrypt cert to be created" + sleep 30; + fi;; + + "client") LISTENSWITCH=" "; + SWITCHPORT=" ";; +esac + +if [[ $EDNSFLAG == "" ]] +then + $COMMAND $LISTENSWITCH $SWITCHPORT -u $UPSTREAM -r $RATELIMIT -p $PORT +else + $COMMAND $LISTENSWITCH $SWITCHPORT -u $UPSTREAM -r $RATELIMIT --edns -p $PORT +fi + +#dnsproxy -l 0.0.0.0 -q 785 --tls-crt=/etc/letsencrypt/live/$DOMAIN/fullchain.pem --tls-key=/etc/letsencrypt/live/$DOMAIN/privkey.pem -u 192.168.2.1:53 -r 100 --edns -p $PORT diff --git a/Docker/docker-compose.yml b/Docker/docker-compose.yml new file mode 100644 index 000000000..d6885c25f --- /dev/null +++ b/Docker/docker-compose.yml @@ -0,0 +1,27 @@ +version: '3' + +services: + dnsproxy: + image: dnsproxy/AdguardHome:latest + container_name: dnsproxy + restart: always + #volumes: + #- /etc/letsencrypt:/etc/letsencrypt # Uncomment this line to reuse existing cert and key + environment: + LISTEN: "0.0.0.0" + SRVPORT: "784" + DOMAIN: "example.org" # Your domain name required + EMAIL: "email@example.org" + UPSTREAM_ADDR: "1.1.1.1:53" + RATELIMIT: "30" + EDNS: "ON" + LOCALPORT: "1111" # Any local port # + MODE: "server" + PROTO: "quic" + DRYRUN: "--dry-run" # Uncomment this option to test letsencrypt cert generation process + ports: + - 80:80 # Port 80 is mandatory for letsencrypt to generate cert/keys successfully + - 443:443 + - 784:784/udp + - 853:853 + - 1111:1111/udp diff --git a/Docker/letsencrypt-wrapper.sh b/Docker/letsencrypt-wrapper.sh new file mode 100755 index 000000000..425552379 --- /dev/null +++ b/Docker/letsencrypt-wrapper.sh @@ -0,0 +1,85 @@ +#!/bin/bash + +if [[ $DEBUG ]]; then + set -x +fi + +logger() { + echo "[letsencrypt] :: $(date +%x-%X) :: $@" | tee -a /var/log/letsencrypt-wrapper.log +} + +get_first_time() { + local d=$1 + if certbot certonly $DRYRUN --agree-tos --email $EMAIL \ + -n --standalone -w /etc/letsencrypt -d $d + then + logger "First certificate got for $d" + else + logger "ERROR on first time certificate for $d" + exit 2 + fi +} + +renew_certs() { + certbot renew $DRYRUN -n --standalone -w /etc/letsencrypt -d "${1}" +} + +check_env() { + if ! [[ $DOMAIN ]]; then + logger "ERROR! Missing domains to be used! Set DOMAIN environment variable." + exit 1 + fi + if ! [[ $EMAIL ]]; then + logger "ERROR! Missing email address to use to register the domain(s) certificates." + fi +} + +should_force() { + if ! [[ -f "/etc/letsencrypt/live/${1}/.doh-force" ]]; then + return 0 + fi + logger "Not skipping - found file /etc/letsencrypt/live/${1}/.doh-force" + return 1 +} + +le_vol_mounted() { + if grep "/etc/letsencrypt/live/${1}" <(mount) > /dev/null; then + logger "A letsencrypt volume is mounted for domain ${1}" + should_force $1 && return 0 + elif grep "/etc/letsencrypt" <(mount) > /dev/null; then + logger "The whole /etc/letsencrypt is mounted" + if [[ -d "/etc/letsencrypt/live/${1}" ]]; then + should_force $1 && return 0 + fi + fi + return 1 +} + +main() { + check_env + for d in $DOMAIN; do + if le_vol_mounted ${d}; then + # Assuming that if the volume is mounted from the host + # creation and renewal is in charge of others, + # except if the .doh-force file il present in the + # directory of the certificates. In such case, the container + # will take care of renewing them. + logger "Skipping domain ${1}" + continue + fi + if [[ -f /etc/letsencrypt/live/$d/privkey.pem ]]; then + logger "Renewing certificate for $d" + renew_certs $d + else + logger "Getting first time certificates for $d" + get_first_time $d + fi + done + logger "All done, sleeping for ${WAIT_TIME:-"1d"}." + sleep ${WAIT_TIME:-"1d"} +} + +while true +do + main +done diff --git a/Docker/start.sh b/Docker/start.sh new file mode 100755 index 000000000..274baf031 --- /dev/null +++ b/Docker/start.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +/usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf diff --git a/Docker/supervisord.conf b/Docker/supervisord.conf new file mode 100644 index 000000000..dbb834039 --- /dev/null +++ b/Docker/supervisord.conf @@ -0,0 +1,20 @@ +[supervisord] +nodaemon=true + +[program:letsencrypt] +command=/srv/letsencrypt-wrapper.sh +stdout_logfile=/dev/fd/1 +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/fd/2 +stderr_logfile_maxbytes=0 +username=root +autorestart=false + +[program:dnsproxy] +command=/srv/dnsproxy.sh +stdout_logfile=/dev/fd/1 +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/fd/2 +stderr_logfile_maxbytes=0 +username=root +autorestart=true