├── .gitignore ├── README.md ├── ddclient ├── README.md ├── config │ ├── ddclient.conf.org │ └── ddclient.working.model └── docker-compose.yml ├── dumpcert └── docker-compose.yml ├── jaeger ├── .env_sample └── docker-compose.yaml ├── traefik ├── .env_sample ├── conf │ └── .gitignore ├── confModel_renamethisfolder │ ├── exampleservice.yml │ ├── traefik2.yml │ ├── users.yml │ └── usersfile.htpasswd ├── docker-compose.yaml └── letsencrypt │ └── .gitignore ├── traefikAtHome.png ├── ubuntu └── docker-compose.yml ├── whoami2 ├── .env_sample └── docker-compose.yml ├── whoami3 ├── .env_sample └── docker-compose.yml └── whoamitcp ├── .env_sample └── docker-compose.yml /.gitignore: -------------------------------------------------------------------------------- 1 | /traefik/letsencrypt/acme.json 2 | /traefik/conf/.* 3 | /traefik/conf/* 4 | /prometheus 5 | .env 6 | /keycloakBackup/* 7 | *.code-workspace 8 | .vscode 9 | DONTREADME.md 10 | docker-compose.override.yml 11 | /ddclient/.run 12 | /ddclient/config/ddclient.conf 13 | dumpcert/docker-compose.yml.xxx 14 | dumpcert/letsencrypt/acme.json 15 | dumpcert/letsencrypt/certificate.pem 16 | dumpcert/letsencrypt/privatekey.pem 17 | dumpcert/letsencrypt/dump/certs/grooms.page.crt 18 | dumpcert/letsencrypt/dump/private/grooms.page.key 19 | dumpcert/letsencrypt/dump/private/letsencrypt.key 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Traefik setup with security first - Main Repo 2 | 3 | 4 | 5 | ## Introduction 6 | 7 | This repo is the result of trying to secure aspects of my home network such as: 8 | 9 | - Secure access to my home automation tools 10 | - Developing services when outside my home 11 | - Sharing services to others 12 | - Using cloud based devops services to trigger builds on premises 13 | 14 | I decided to use Traefik but needed to add some other services to complete the setup. Most of this work is not original, but pulling it all into a single solution took me quite a lot of time, so hopefully this will help some of you. 15 | 16 | ![Traefik in my home network](./traefikAtHome.png) 17 | 18 | ### Exposing services 19 | 20 | Most small networks use private ip address ranges (typically 192.168.1/24) and only allow ingress via a port forwarding mechanism. Traefik is designed to sit behind that one forwarded port and to provide access to other services via virtual hostnames. Define a wildcard domain to send all requests to your router's public address i.e. ```CNAME * myrouter.mydomain``` 21 | 22 | Once this is setup _anyservicename_.myrouter.mydomain will be addressed to the routers public address. Then use the portforwarding function of the router to send the traffic to Traefik! 23 | 24 | So far so good. But, the most basic Traefik config will allow you to publish services without any additional security! - Before putting _```http://myhomeautomationpage.mydomain```_ on the internet for everybody to access, some additional setup should be prepared. 25 | 26 | Encryption, Authentication, Authorization. 27 | 28 | __Encryption__ - __Let's Encrypt__ is a free service that can be automatically provision TLS certificates to protect your services. 29 | 30 | __Authentication__ - In this case I wanted to have a userid / password control system. A __basic auth__ middleware using .htaccess is one way to achieve this. I also wanted to be able to study openid / oauth / SAML sign on techniques - I opted to use __KeyCloak__ to provide SSO (single sign-on). If you publish multiple sites with the same SSO configuration, only the first one will request a user / password. The others will inherit the active security session. 31 | 32 | __Authorization__ - Defining which parts of a service can be accessed by a user. Keycloak supports authentication, but the service you are protecting will also need to support OpenID or SAML to achieve this. Added to the to do list. 33 | 34 | __Tracing requests__ - Traefik allows for requests to be captured by __Jaeger__ so you can see the history of network traffic passing through Traefik. 35 | 36 | ## Preparation 37 | 38 | Before you try to start these services, several small preparation steps need to be complete. 39 | 40 | Follow these steps in order to set this up. Traefik will not work until these are in place. 41 | 42 | - Choose domain name and register if you do not already own it 43 | - Register a cloudflare account 44 | - Delegate the responsibility to manage your chosen subdomain to cloudflare 45 | - Generate cloudflare access tokens 46 | 47 | ### Domain Name Registrar 48 | 49 | Decide on a domain name to use for all the incoming requests. For simplicty I registered a new domain in order that I could use ```*.mydomain CNAME router.mydomain```. Look out for conflicts for existing DNS entries. 50 | 51 | ### DNS Server 52 | 53 | My Domain Registrar does not offer an API to manage the DNS records. I found and set up a free account on cloudflare to manage mydomain. I then updated my Domain Registrar nameserver entries to point to the assigned cloudflare DNS servers. 54 | 55 | ### DNS API Key 56 | 57 | Why a DNS API? Well in order for Let's Encrypt (LE) to create a certificate for your domain it must ascertain that you 58 | control that domain. LE will use the API to create an entry in your DNS domain and if successful it will issue a certificate. 59 | 60 | On cloudflare you create an __API access key__ with DNS:Edit and ZONE:Read access for your domains. 61 | 62 | ### Let's Encrypt 63 | 64 | I don't think anything needs to be done here - it handles the requests per domain and is rate capped to 50 certificates per week. Uncomment the acme-staging entry during your initial setup to avoid hitting this cap. This site uses a single wildcard domain name. 65 | 66 | ```text 67 | Certificate Name: yourdomain 68 | Subject Alternate Name: *.yourdomain 69 | ``` 70 | 71 | This certificate can be used to secure every web service that is a subdomain of yourdomain. This may be considered a 72 | risk for more secure applications, so may want some dedicated certificates for more critical services. 73 | 74 | ## Installation 75 | 76 | - Define your domain name 77 | - Register the domain name 78 | - Use cloudflare to manage the DNS entries 79 | - Create a CloudFlare access token 80 | - ```git clone https://github.com/stevegroom/traefikGateway.git``` 81 | - Copy each of the .env_sample files at .env 82 | - Edit each .env file with your values 83 | 84 | For the first execution, keycloak is not yet set up so cannot be used for logon. 85 | Edit ```traefik/docker-compose.yml``` lines 377 / 379 uncomment 86 | ```traefik.http.routers.traefik.middlewares=users@file``` and comment out 87 | ```traefik.http.routers.traefik.middlewares=keycloakForwardAuth@docker``` 88 | 89 | - Copy usersfile.htpasswd from confModel to conf 90 | - Create an .htpassword format user record in this file (google 'create .htpasswd webpage') 91 | 92 | The services should be ready to start 93 | 94 | ```bash 95 | cd traefikGateway/jaeger 96 | docker-compose up --detach;docker-compose logs --tail 0 --follow 97 | ^C to exit 98 | cd ../traefik 99 | docker-compose up --detach;docker-compose logs --tail 0 --follow 100 | ``` 101 | 102 | Note, as docker compose does not wait on services to be operational, there are several restarts obseved during the startup. It normally settles down after five minutes. 103 | 104 | You should be able to access https://traefik.yourdomain and be requested userid / password via basic auth. 105 | 106 | https://keycloak.yourdomain will also be running, so be sure to set up the initial password before anyone else does! 107 | 108 | ## Drilling deeper 109 | 110 | I needed to route queries from the internet through _two_ traefik instances, this repo creates the secure ingress point, and a second repo runs on the inner subnet to route those services. 111 | 112 | Manual yml conf entries are created in this repo to map to the services that are exposed on TraefikGatwayInner. 113 | 114 | ## Odds 'n Ends 115 | 116 | ### Let's Encrypt certificates 117 | 118 | As Traefik acts as the secure proxy for your services, it reqests the certificate to be issued by Let's Encrypt and stores the certificate in an ```acme.json``` file. If you need a ```.key``` and a ```.cert``` file for another service you can use the following tool to extract the keys. 119 | 120 | 121 | 122 | ```danielhuisman/traefik-certificate-extractor``` 123 | 124 | ```bash 125 | docker run --name extractor -d -v /srv/traefik/acme:/app/data -v /srv/extractor/certs:/app/certs danielhuisman/traefik-certificate-extractor 126 | docker ps | grep extractor 127 | docker exec -it extractor /bin/sh 128 | cat README.md 129 | python3 extractor.py ./data 130 | ^d 131 | docker stop extractor 132 | docker rm extractor 133 | ``` 134 | 135 | ## Traefik messages 136 | 137 | ### Skipping same configuration 138 | 139 | ```log 140 | traefik | time="2020-10-31T12:04:52+01:00" level=info msg="Skipping same configuration" providerName=file 141 | traefik | time="2020-10-31T12:04:52+01:00" level=info msg="Skipping same configuration" providerName=file 142 | ``` 143 | 144 | This means that some config elements found in a file provider are already defined, but are not 145 | contradicting what is defined previously. 146 | 147 | ## Extract the cert from the acme.json file 148 | 149 | ```bash 150 | cd letsencrypt 151 | cat acme.json | jq -r '.basic.Certificates[] | select(.domain.main=="'grooms.page'") | .certificate' | base64 -d > grooms.page.crt 152 | cat acme.json | jq -r '.basic.Certificates[] | select(.domain.main=="'grooms.page'") | .key' | base64 -d > grooms.page.key 153 | 154 | ``` 155 | 156 | Results in: 157 | 158 | ```bash 159 | steve@nucnuc2:~/traefikGatewayOuter/traefik/letsencrypt$ lltotal 44 160 | drwx------ 2 steve steve 4096 Feb 1 15:29 ./ 161 | drwx------ 6 steve steve 4096 Jan 8 17:12 ../ 162 | -rwx------ 1 steve steve 26398 Dec 7 12:17 acme.json* 163 | -rwx------ 1 steve steve 23 Feb 19 2020 .gitignore* 164 | -rw-rw-r-- 1 steve steve 3917 Feb 1 15:38 grooms.page.crt 165 | ``` 166 | -------------------------------------------------------------------------------- /ddclient/README.md: -------------------------------------------------------------------------------- 1 | # ddclient 2 | 3 | Config file at ```/etc/ddclient/ddclient.conf``` : 4 | 5 | ```text 6 | #How often to check ip address 7 | daemon=1800 8 | 9 | #Use the Cloudflare protocol 10 | protocol=cloudflare 11 | 12 | #Tell ddclient to get real ip address 13 | use=web 14 | #web=checkip-dyndns-org/ 15 | web-skip='IP Address' 16 | #Credentials for Cloudflare api (Global API key) 17 | ssl=yes 18 | #server=www.cloudflare.com 19 | login=steve@groom.ch 20 | password=<38 char key from Global API key> 21 | 22 | zone=grooms.page 23 | 24 | #Domain for update 25 | grooms.page,home.grooms.page 26 | 27 | ``` 28 | 29 | ddclient checks the local ISP's assigned IP address matches the defined DNS entry. If they differ, then __ddclient__ will use the cloudflare api to update the defined DNS. 30 | -------------------------------------------------------------------------------- /ddclient/config/ddclient.conf.org: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | ## 3 | ## Define default global variables with lines like: 4 | ## var=value [, var=value]* 5 | ## These values will be used for each following host unless overridden 6 | ## with a local variable definition. 7 | ## 8 | ## Define local variables for one or more hosts with: 9 | ## var=value [, var=value]* host.and.domain[,host2.and.domain...] 10 | ## 11 | ## Lines can be continued on the following line by ending the line 12 | ## with a \ 13 | ## 14 | ## 15 | ## Warning: not all supported routers or dynamic DNS services 16 | ## are mentioned here. 17 | ## 18 | ###################################################################### 19 | daemon=1800 # check every 300 seconds 20 | syslog=yes # log update msgs to syslog 21 | mail=root # mail all msgs to root 22 | mail-failure=root # mail failed update msgs to root 23 | pid=@runstatedir@/ddclient.pid # record PID in file. 24 | ssl=yes # use ssl-support. Works with 25 | # ssl-library 26 | # postscript=script # run script after updating. The 27 | # new IP is added as argument. 28 | # 29 | #use=watchguard-soho, fw=192.168.111.1:80 # via Watchguard's SOHO FW 30 | #use=netopia-r910, fw=192.168.111.1:80 # via Netopia R910 FW 31 | #use=smc-barricade, fw=192.168.123.254:80 # via SMC's Barricade FW 32 | #use=netgear-rt3xx, fw=192.168.0.1:80 # via Netgear's internet FW 33 | #use=linksys, fw=192.168.1.1:80 # via Linksys's internet FW 34 | #use=maxgate-ugate3x00, fw=192.168.0.1:80 # via MaxGate's UGATE-3x00 FW 35 | #use=elsa-lancom-dsl10, fw=10.0.0.254:80 # via ELSA LanCom DSL/10 DSL Router 36 | #use=elsa-lancom-dsl10-ch01, fw=10.0.0.254:80 # via ELSA LanCom DSL/10 DSL Router 37 | #use=elsa-lancom-dsl10-ch02, fw=10.0.0.254:80 # via ELSA LanCom DSL/10 DSL Router 38 | #use=alcatel-stp, fw=10.0.0.138:80 # via Alcatel Speed Touch Pro 39 | #use=xsense-aero, fw=192.168.1.1:80 # via Xsense Aero Router 40 | #use=allnet-1298, fw=192.168.1.1:80 # via AllNet 1298 DSL Router 41 | #use=3com-oc-remote812, fw=192.168.0.254:80 # via 3com OfficeConnect Remote 812 42 | #use=e-tech, fw=192.168.1.1:80 # via E-tech Router 43 | #use=cayman-3220h, fw=192.168.0.1:1080 # via Cayman 3220-H DSL Router 44 | # 45 | #fw-login=admin, fw-password=XXXXXX # FW login and password 46 | # 47 | ## To obtain an IP address from FW status page (using fw-login, fw-password) 48 | #use=fw, fw=192.168.1.254/status.htm, fw-skip='IP Address' # found after IP Address 49 | # 50 | ## To obtain an IP address from Web status page (using the proxy if defined) 51 | ## by default, checkip.dyndns.org is used if you use the dyndns protocol. 52 | ## Using use=web is enough to get it working. 53 | ## WARNING: set deamon at least to 600 seconds if you use checkip or you could 54 | ## get banned from their service. 55 | #use=web, web=checkip.dyndns.org/, web-skip='IP Address' # found after IP Address 56 | use=web, web=checkip.dyndns.org/, 57 | # 58 | #use=ip, ip=127.0.0.1 # via static IP's 59 | #use=if, if=eth0 # via interfaces 60 | #use=web # via web 61 | # 62 | protocol=dyndns2 # default protocol 63 | #proxy=fasthttp.sympatico.ca:80 # default proxy 64 | #server=members.dyndns.org # default server 65 | #server=members.dyndns.org:8245 # default server (bypassing proxies) 66 | 67 | #login=your-login # default login 68 | #password=test # default password 69 | #mx=mx.for.your.host # default MX 70 | #backupmx=yes|no # host is primary MX? 71 | #wildcard=yes|no # add wildcard CNAME? 72 | 73 | ## 74 | ## dyndns.org dynamic addresses 75 | ## 76 | ## (supports variables: wildcard,mx,backupmx) 77 | ## 78 | # server=members.dyndns.org, \ 79 | # protocol=dyndns2 \ 80 | # your-dynamic-host.dyndns.org 81 | 82 | ## 83 | ## dyndns.org static addresses 84 | ## 85 | ## (supports variables: wildcard,mx,backupmx) 86 | ## 87 | # static=yes, \ 88 | # server=members.dyndns.org, \ 89 | # protocol=dyndns2 \ 90 | # your-static-host.dyndns.org 91 | 92 | ## 93 | ## 94 | ## dyndns.org custom addresses 95 | ## 96 | ## (supports variables: wildcard,mx,backupmx) 97 | ## 98 | # custom=yes, \ 99 | # server=members.dyndns.org, \ 100 | # protocol=dyndns2 \ 101 | # your-domain.top-level,your-other-domain.top-level 102 | 103 | ## 104 | ## ZoneEdit (zoneedit.com) 105 | ## 106 | # server=dynamic.zoneedit.com, \ 107 | # protocol=zoneedit1, \ 108 | # login=your-zoneedit-login, \ 109 | # password=your-zoneedit-password \ 110 | # your.any.domain,your-2nd.any.dom 111 | 112 | ## 113 | ## EasyDNS (easydns.com) 114 | ## 115 | # server=members.easydns.com, \ 116 | # protocol=easydns, \ 117 | # login=your-easydns-login, \ 118 | # password=your-easydns-password \ 119 | # your.any.domain,your-2nd.any.domain 120 | 121 | ## 122 | ## dslreports.com dynamic-host monitoring 123 | ## 124 | # server=members.dslreports.com \ 125 | # protocol=dslreports1, \ 126 | # login=dslreports-login, \ 127 | # password=dslreports-password \ 128 | # dslreports-unique-id 129 | 130 | ## 131 | ## OrgDNS.org account-configuration 132 | ## 133 | # use=web, web=members.orgdns.org/nic/ip 134 | # server=www.orgdns.org \ 135 | # protocol=dyndns2 \ 136 | # login=yourLoginName \ 137 | # password=yourPassword \ 138 | # yourSubdomain.orgdns.org 139 | 140 | ## 141 | ## NameCheap (namecheap.com) 142 | ## 143 | # protocol=namecheap, \ 144 | # server=dynamicdns.park-your-domain.com, \ 145 | # login=my-namecheap.com-login, \ 146 | # password=my-namecheap.com-password \ 147 | # fully.qualified.host 148 | 149 | ## 150 | ## NearlyFreeSpeech.NET (nearlyfreespeech.net) 151 | ## 152 | # protocol = nfsn, \ 153 | # login=member-login, \ 154 | # password=api-key, \ 155 | # zone=example.com \ 156 | # example.com,subdomain.example.com 157 | 158 | ## 159 | ## 160 | ## Loopia (loopia.se) 161 | ## 162 | # use=web 163 | # web=loopia 164 | # protocol=dyndns2 165 | # server=dns.loopia.se 166 | # script=/XDynDNSServer/XDynDNS.php 167 | # login=my-loopia.se-login 168 | # password=my-loopia.se-password 169 | # my.domain.tld,other.domain.tld 170 | 171 | ## 172 | ## NoIP (noip.com) 173 | ## 174 | # protocol=noip, \ 175 | # ssl=yes, \ 176 | # server=dynupdate.no-ip.com, \ 177 | # login=your-noip-login, \ 178 | # password=your-noip-password, \ 179 | # your-host.domain.com, your-2nd-host.domain.com 180 | 181 | ## 182 | ## ChangeIP (changeip.com) 183 | ## 184 | ## single host update 185 | # protocol=changeip, \ 186 | # login=my-my-changeip.com-login, \ 187 | # password=my-changeip.com-password \ 188 | # myhost.changeip.org 189 | 190 | ## 191 | ## CloudFlare (www.cloudflare.com) 192 | ## 193 | #protocol=cloudflare, \ 194 | #zone=, \ 195 | #ttl=1, \ 196 | #login=, \ # Only needed if you are using your global API key. 197 | #password=, \ # This is either your global API key, or an API token. If you are using an API token, it must have the permissions "Zone - DNS - Edit" and "Zone - Zone - Read". The Zone resources must be "Include - All zones". 198 | #domain.tld,my.domain.tld 199 | 200 | 201 | ## 202 | ## Gandi (gandi.net) 203 | ## 204 | ## Single host update 205 | # protocol=gandi, \ 206 | # zone=example.com, \ 207 | # password=my-gandi-api-key, \ 208 | # ttl=3h \ 209 | # myhost.example.com 210 | 211 | ## 212 | ## Google Domains (www.google.com/domains) 213 | ## 214 | # protocol=googledomains, 215 | # login=my-auto-generated-username, 216 | # password=my-auto-generated-password 217 | # my.domain.tld, otherhost.domain.tld 218 | 219 | ## 220 | ## Duckdns (http://www.duckdns.org/) 221 | ## 222 | # 223 | # password=my-auto-generated-password 224 | # protocol=duckdns hostwithoutduckdnsorg 225 | 226 | ## 227 | ## Freemyip (http://freemyip.com/) 228 | ## 229 | # 230 | # protocol=freemyip, 231 | # password=my-token 232 | # myhost 233 | 234 | ## 235 | ## MyOnlinePortal (http://myonlineportal.net) 236 | ## 237 | # protocol=dyndns2 238 | # ssl=yes 239 | # # ipv6=yes # optional 240 | # use=web, web=myonlineportal.net/checkip 241 | # # use=if, if=eth0 # alternative to use=web 242 | # # if-skip=Scope:Link # alternative to use=web 243 | # login=your-myonlineportal-username 244 | # password=your-myonlineportal-password 245 | # domain.myonlineportal.net 246 | 247 | ## 248 | ## nsupdate.info IPV4(https://www.nsupdate.info) 249 | ## 250 | #protocol=dyndns2 251 | #use=web, web=http://ipv4.nsupdate.info/myip 252 | #server=ipv4.nsupdate.info 253 | #login=domain.nsupdate.info 254 | #password='123' 255 | #domain.nsupdate.info 256 | 257 | ## 258 | ## nsupdate.info IPV6 (https://www.nsupdate.info) 259 | ## ddclient releases <= 3.8.1 do not support IPv6 260 | ## 261 | #protocol=dyndns2 262 | #usev6=if, if=eth0 263 | #server=ipv6.nsupdate.info 264 | #login=domain.nsupdate.info 265 | #password='123' 266 | #domain.nsupdate.info 267 | 268 | ## 269 | ## Yandex.Mail for Domain (domain.yandex.com) 270 | ## 271 | # protocol=yandex, \ 272 | # login=domain.tld, \ 273 | # password=yandex-pdd-token \ 274 | # my.domain.tld,other.domain.tld \ 275 | 276 | ## 277 | ## DNS Made Easy (https://dnsmadeeasy.com) 278 | ## 279 | # protocol=dnsmadeeasy, 280 | # login=your-account-email-address 281 | # password=your-generated-password 282 | # your-numeric-record-id-1,your-numeric-record-id-2,... 283 | 284 | ## 285 | ## OVH DynHost (https://ovh.com) 286 | ## 287 | # protocol=ovh, 288 | # login=example.com-dynhostuser, 289 | # password=your_password 290 | # test.example.com 291 | 292 | ## 293 | ## ClouDNS (https://www.cloudns.net) 294 | ## 295 | # protocol=cloudns, \ 296 | # dynurl=https://ipv4.cloudns.net/api/dynamicURL/?q=Njc1OTE2OjY3Njk0NDM6YTk2, \ 297 | # myhost.example.com 298 | 299 | ## 300 | ## dinahosting (https://dinahosting.com) 301 | ## 302 | # protocol=dinahosting, \ 303 | # login=myusername, \ 304 | # password=mypassword \ 305 | # myhost.mydomain.com 306 | 307 | -------------------------------------------------------------------------------- /ddclient/config/ddclient.working.model: -------------------------------------------------------------------------------- 1 | use=web 2 | protocol=cloudflare ## 3 | 4 | #server=fqdn.of.api.service ## defaults to api.cloudflare.com/client/v4 5 | server=api.cloudflare.com/client/v4 6 | 7 | #login ## login name and password registered with the service 8 | login= 9 | 10 | #password from:: My Profile -> API Tokens -> Global API Key (38 chars long) 11 | password=<38charglobalapikeyhere> 12 | 13 | #fully.qualified.host ## the host registered with the service. 14 | zone=grooms.page 15 | 16 | # This (these) must be A-records. 17 | home.grooms.page 18 | -------------------------------------------------------------------------------- /ddclient/docker-compose.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: "2.1" 3 | services: 4 | ddclient: 5 | image: linuxserver/ddclient 6 | container_name: ddclient 7 | environment: 8 | - PUID=1000 9 | - PGID=1000 10 | - TZ=Europe/London 11 | volumes: 12 | - ./config:/etc/ddclient 13 | restart: unless-stopped 14 | 15 | command: ddclient -daemon=0 -debug -verbose -noquiet 16 | -------------------------------------------------------------------------------- /dumpcert/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | 3 | services: 4 | 5 | traefik-certs-dumper: 6 | image: ldez/traefik-certs-dumper:v2.7.4 7 | entrypoint: sh -c ' 8 | apk add jq 9 | ; while ! [ -e /data/acme.json ] 10 | || ! [ `jq ".[] | .Certificates | length" /data/acme.json` != 0 ]; do 11 | sleep 1 12 | ; done 13 | && traefik-certs-dumper file --version v2 --watch 14 | --source /data/acme.json --dest /data/certs' 15 | volumes: 16 | - ./letsencrypt:/data 17 | 18 | -------------------------------------------------------------------------------- /jaeger/.env_sample: -------------------------------------------------------------------------------- 1 | 2 | # Variables to configure the traefik bundle 3 | 4 | 5 | DOMAINNAME= 6 | -------------------------------------------------------------------------------- /jaeger/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | 3 | networks: 4 | traefik: 5 | external: true 6 | name: traefik 7 | 8 | services: 9 | 10 | ############################################################################################# 11 | ############################################################################################# 12 | ## JAEGER-COLLECTOR 13 | ## 14 | ## https://www.jaegertracing.io/ 15 | ## https://hub.docker.com/r/jaegertracing/jaeger-collector/ 16 | ## "Monitor and troubleshoot transactions in complex distributed systems" 17 | ## 18 | ############################################################################################# 19 | ############################################################################################# 20 | 21 | jaeger-collector: 22 | ############################################################################################# 23 | ############################################################################################# 24 | ## JAEGER-COLLECTOR 25 | ## 26 | ## https://www.jaegertracing.io/ 27 | ## https://hub.docker.com/r/jaegertracing/jaeger-collector/ 28 | ## 29 | ## Jaeger Collector (Queue, Workers) - Similar to the Agent, the Collector is able to 30 | ## receive spans and place them in an internal queue for processing. This allows the 31 | ## collector to return immediately to the client/agent instead of waiting for the span 32 | ## to make its way to the storage. 33 | ## 34 | ############################################################################################# 35 | ############################################################################################# 36 | image: jaegertracing/jaeger-collector 37 | networks: 38 | - traefik 39 | command: ["--cassandra.keyspace=jaeger_v1_dc1", "--cassandra.servers=cassandra", "--collector.zipkin.http-port=9411"] 40 | ports: 41 | - "14269" 42 | - "14268:14268" 43 | - "14267" 44 | - "14250" 45 | - "9411:9411" 46 | restart: on-failure 47 | depends_on: 48 | - cassandra-schema 49 | logging: 50 | options: 51 | max-size: "10m" 52 | max-file: "3" 53 | 54 | jaeger-query: 55 | ############################################################################################# 56 | ############################################################################################# 57 | ## JAEGER-QUERY 58 | ## 59 | ## https://www.jaegertracing.io/ 60 | ## 61 | ## Query (Query Service) - Query is a service that retrieves traces from storage. 62 | ## 63 | ############################################################################################# 64 | ############################################################################################# 65 | image: jaegertracing/jaeger-query 66 | logging: 67 | options: 68 | max-size: "10m" 69 | max-file: "3" 70 | networks: 71 | - traefik 72 | command: ["--cassandra.keyspace=jaeger_v1_dc1", "--cassandra.servers=cassandra"] 73 | ports: 74 | - "16686:16686" 75 | - "16687" 76 | restart: on-failure 77 | depends_on: 78 | - cassandra-schema 79 | labels: 80 | - "traefik.enable=true" 81 | - "traefik.http.routers.jaeger.rule=Host(`jaeger.${DOMAINNAME}`)" 82 | - "traefik.http.routers.jaeger.entrypoints=websecure" 83 | - "traefik.port=16686" 84 | 85 | - "traefik.http.routers.jaeger.tls=true" 86 | - "traefik.http.routers.jaeger.tls.certresolver=leresolver" 87 | - "traefik.http.routers.jaeger.tls.domains[0].main=${DOMAINNAME}" 88 | - "traefik.http.routers.jaeger.tls.domains[0].sans=*.${DOMAINNAME}" 89 | 90 | # Forward authentication to keycloak 91 | - "traefik.http.routers.jaeger.middlewares=keycloakForwardAuth" 92 | 93 | - "traefik.docker.network=traefik" 94 | 95 | jaeger-agent: 96 | ############################################################################################# 97 | ############################################################################################# 98 | ## JAEGER-AGENT 99 | ## 100 | ## https://www.jaegertracing.io/ 101 | ## 102 | ## Jaeger Agent (Server Queue, Processor Workers) - The Jaeger agent is a network daemon that 103 | ## listens for spans sent over User Datagram Protocol (UDP), which it batches and sends to the 104 | ## collector. The agent is meant to be placed on the same host as the instrumented 105 | ## application. This is typically accomplished by having a sidecar in container environments 106 | ## like Kubernetes 107 | ## 108 | ############################################################################################# 109 | ############################################################################################# 110 | image: jaegertracing/jaeger-agent 111 | logging: 112 | options: 113 | max-size: "10m" 114 | max-file: "3" 115 | networks: 116 | - traefik 117 | command: ["--reporter.grpc.host-port=jaeger-collector:14250"] 118 | ports: 119 | - "5775:5775/udp" 120 | - "6831:6831/udp" 121 | - "6832:6832/udp" 122 | - "5778:5778" 123 | restart: on-failure 124 | depends_on: 125 | - jaeger-collector 126 | 127 | cassandra: 128 | ############################################################################################# 129 | ############################################################################################# 130 | ## CASSANDRA 131 | ## 132 | ## https://www.jaegertracing.io/ 133 | ## 134 | ## Persistance layer 135 | ## 136 | ############################################################################################# 137 | ############################################################################################# 138 | image: cassandra:3.9 139 | logging: 140 | options: 141 | max-size: "10m" 142 | max-file: "3" 143 | networks: 144 | - traefik 145 | 146 | cassandra-schema: 147 | ############################################################################################# 148 | ############################################################################################# 149 | ## CASSANDRA-SCHEMA 150 | ## 151 | ## https://www.jaegertracing.io/ 152 | ## 153 | ## Persistance layer schema creation 154 | ## 155 | ############################################################################################# 156 | ############################################################################################# 157 | image: jaegertracing/jaeger-cassandra-schema 158 | logging: 159 | options: 160 | max-size: "10m" 161 | max-file: "3" 162 | networks: 163 | - traefik 164 | depends_on: 165 | - cassandra 166 | -------------------------------------------------------------------------------- /traefik/.env_sample: -------------------------------------------------------------------------------- 1 | 2 | # Variables to configure the traefik bundle 3 | 4 | # Letsencrypt for TLS security 5 | ## Letsencrypt - using Cloud Flare DNS Challenge 6 | CF_API_EMAIL=your@email.address 7 | CF_DNS_API_TOKEN=<40 character token generated on Clodeflare> 8 | CF_ZONE_API_TOKEN=<40 character token generated on Clodeflare> 9 | 10 | # Keycloak Identity provider 11 | 12 | ## Keycloak database (postgresql) 13 | KEYCLOAKBACKUP=/absoluepathforkeycloakdatabasebackups 14 | DOMAINNAME= 15 | DB_USER=keycloak 16 | DB_PASSWORD=password 17 | 18 | ## Keycloak master password 19 | 20 | KEYCLOAK_USER=admin 21 | KEYCLOAK_PASSWORD=ThisPasswordWillBeUsedToSetTheInitialValueToo 22 | 23 | # OpenID Connect details 24 | # This is based on using the Master realm. 25 | # Create a new client in Keycloak - i.e. 'generic', 26 | # this will go into your CLIENT_ID, CLIENT_SECRET details. 27 | 28 | PROVIDERS_OIDC_ISSUER_URL=https://keycloak./auth/realms/master 29 | PROVIDERS_OIDC_CLIENT_ID=generic 30 | PROVIDERS_OIDC_CLIENT_SECRET= 31 | 32 | # Add extra hosts for the sshd service to simplify the ingress 33 | # See the SSHD service extra_hosts: 34 | SSHD_EXTRA_HOST1="host1.local:192.168.1.2" 35 | SSHD_EXTRA_HOST2="host2.local:192.168.1.77" 36 | SSHD_EXTRA_HOST3="host3.local:192.168.1.121" 37 | SSHD_EXTRA_HOST4="host4.local:192.168.1.18" -------------------------------------------------------------------------------- /traefik/conf/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /traefik/confModel_renamethisfolder/exampleservice.yml: -------------------------------------------------------------------------------- 1 | http: 2 | # Example setup of an http web service behind a simple router. 3 | # 4 | # traefik.yourdomain is running on 192.168.1.nn:443 5 | # It only allows https inbound connections. 6 | # 7 | # A second instance of traefik is requred to access resources 8 | # on a different subnet only accessible via port forwarding. 9 | # In my router (192.168.1.mm) I port forward to 81 to 192.168.86.nn:80 10 | # (The router is using port 80 already) 11 | # All services I want to publish on the 192.168.86/24 network are configured 12 | # traefikinner. 13 | # 14 | # Create this filebased router and service to reach the related router and service 15 | # running on the traefikinner instance. 16 | 17 | routers: 18 | yourpublishedname: 19 | entryPoints: 20 | # Only allow https 21 | - websecure 22 | middlewares: 23 | # Authentication via OpenIDConnect 24 | - keycloakForwardAuth@docker 25 | service: yourservicename 26 | rule: Host(`yourpublishedname.yourdomain`) 27 | tls: 28 | certResolver: leresolver 29 | domains: 30 | - main: yourdomain 31 | sans: 32 | - "*.yourdomain" 33 | 34 | # Add the service 35 | services: 36 | yourservicename: 37 | loadBalancer: 38 | servers: 39 | # The router and port that is forwarded to traefikinner's http 40 | - url: http://192.168.1.mm:81 41 | passHostHeader: true 42 | hostname: yourpublishedname.yourdomain -------------------------------------------------------------------------------- /traefik/confModel_renamethisfolder/traefik2.yml: -------------------------------------------------------------------------------- 1 | http: 2 | # Example setup of second traefik instance behind another router. 3 | # 4 | # traefik.yourdomain is running on 192.168.1.nn:443 5 | # It only allows https inbound connections. 6 | # A second instance of traefik is requred to access resources 7 | # on a different subnet only accessible via port forwarding. 8 | # In my router (192.168.1.mm) I port forward to 81 to 192.168.86.nn:80 9 | # (The router is using port 80 already) 10 | 11 | routers: 12 | traefikinner: 13 | entryPoints: 14 | # Only allow https 15 | - websecure 16 | middlewares: 17 | # Authentication via OpenIDConnect 18 | - keycloakForwardAuth@docker 19 | service: traefik2 20 | # Publish the inner trafik router 21 | rule: Host(`traefikinner.yourdomain`) 22 | tls: 23 | certResolver: leresolver 24 | domains: 25 | - main: yourdomain 26 | sans: 27 | - "*.yourdomain" 28 | 29 | # Add the service 30 | services: 31 | traefik2: 32 | loadBalancer: 33 | servers: 34 | # IP address of the router and the forwarded port 35 | - url: http://192.168.1.mm:81 36 | passHostHeader: true 37 | hostname: traefikinner.yourdomain 38 | -------------------------------------------------------------------------------- /traefik/confModel_renamethisfolder/users.yml: -------------------------------------------------------------------------------- 1 | # Dynamic conf to create a basicAuth middleware using .htpassword encoded users and passwords. 2 | http: 3 | # Add the middleware 4 | middlewares: 5 | users: 6 | basicAuth: 7 | usersFile: /conf/usersfile.htpasswd 8 | -------------------------------------------------------------------------------- /traefik/confModel_renamethisfolder/usersfile.htpasswd: -------------------------------------------------------------------------------- 1 | user1:$passwordhash 2 | user2:$use .htpassword generator to create these 3 | -------------------------------------------------------------------------------- /traefik/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | 3 | ############################################################################################# 4 | ############################################################################################# 5 | ## 6 | ## Traefik/LetsEncrypt/KeyCloak/Jaeger (OUTER) 7 | ## 8 | ## .env You have to create .env files with 9 | ## all the parameters pre-filled before starting 10 | ## this docker-compose.yml file. 11 | ## 12 | ## .env in the same directory as docker-compose.yml 13 | ############################################################################################## 14 | ## # Variables to configure the traefik bundle 15 | ## 16 | ## # Letsencrypt for TLS security 17 | ## ## Letsencrypt - using Cloud Flare DNS Challenge 18 | ## CF_API_EMAIL= 19 | ## You need a zone token to enable Let's Encrypt to automate the DNS Challenge 20 | ## CF_DNS_API_TOKEN= 21 | ## CF_ZONE_API_TOKEN= 22 | ## 23 | ## # Keycloak Identity provider 24 | ## 25 | ## ## Keycloak database (postgresql) 26 | ## KEYCLOAKBACKUP=/home/steve/traefikGateway/keycloak 27 | ## DOMAINNAME= 28 | ## DB_USER=keycloak 29 | ## DB_PASSWORD=password 30 | ## 31 | ## ## Keycloak master password 32 | ## KEYCLOAK_USER=admin 33 | ## KEYCLOAK_PASSWORD=Pa55w0rd 34 | ## 35 | ## # OpenID Connect details 36 | ## # This is based on using the Master realm. Create a new client, this will go into your CLIENT_ID, CLIENT_SECRET details. 37 | ## PROVIDERS_OIDC_ISSUER_URL=https://keycloak./auth/realms/master 38 | ## PROVIDERS_OIDC_CLIENT_ID= 39 | ## PROVIDERS_OIDC_CLIENT_SECRET= 40 | ## 41 | ## 42 | ## 43 | ## 44 | ############################################################################################# 45 | ############################################################################################# 46 | 47 | networks: 48 | traefik: 49 | external: true 50 | name: traefik 51 | keycloak: 52 | name: keycloak 53 | 54 | # volumes: 55 | # postgres_data: 56 | # driver: local 57 | 58 | services: 59 | 60 | ############################################## 61 | ############################################## 62 | ## 63 | ## KEYCLOAK-DB 64 | ## PostgreSQL database to persist the IDP data 65 | ## 66 | ############################################## 67 | ############################################## 68 | keycloak-db: 69 | image: postgres 70 | restart: unless-stopped 71 | logging: 72 | options: 73 | max-size: "10m" 74 | max-file: "3" 75 | container_name: keycloak-db 76 | networks: 77 | - keycloak 78 | volumes: 79 | - ./postgres_data:/var/lib/postgresql/data 80 | # - /etc/localtime:/etc/localtime:ro 81 | ports: 82 | - 5432:5432 83 | environment: 84 | - LOG_MIN_MESSAGES=INFO 85 | - DB_VENDOR=postgres 86 | - DB_DATABASE=keycloak 87 | - DB_ADDR=keycloak-db 88 | - DB_USER=${DB_USER} 89 | - DB_PASSWORD=${DB_PASSWORD} 90 | # This is required to run keycloak behind traefik 91 | - PROXY_ADDRESS_FORWARDING=true 92 | - KEYCLOAK_HOSTNAME=keycloak.${DOMAINNAME} 93 | # Tell Postgress what user/password to create 94 | - POSTGRES_USER=${DB_USER} 95 | - POSTGRES_PASSWORD=${DB_PASSWORD} 96 | 97 | command: ["postgres", "-c", "log_min_messages=INFO"] 98 | 99 | ######################################################### 100 | ######################################################### 101 | ## 102 | ## KEYCLOAK-DB-BACKUP 103 | ## Script to backup the Keycloak DB on startup and daily 104 | ## 105 | ######################################################### 106 | ######################################################### 107 | keycloak-db-backup: 108 | image: postgres 109 | logging: 110 | options: 111 | max-size: "10m" 112 | max-file: "3" 113 | container_name: keycloak-db-backup 114 | networks: 115 | - keycloak 116 | volumes: 117 | - ${KEYCLOAKBACKUP}:/dump 118 | # - /etc/localtime:/etc/localtime:ro 119 | environment: 120 | - PGHOST=keycloak-db 121 | - PGUSER=${DB_USER} 122 | - PGPASSWORD=${DB_PASSWORD} 123 | - BACKUP_NUM_KEEP=7 124 | - BACKUP_FREQUENCY=1d 125 | entrypoint: | 126 | bash -c 'bash -s < /dump/dump_\`date +%d-%m-%Y"_"%H_%M_%S\`.psql 131 | (ls -t /dump/dump*.psql|head -n $$BACKUP_NUM_KEEP;ls /dump/dump*.psql)|sort|uniq -u|xargs rm -- {} 132 | sleep $$BACKUP_FREQUENCY 133 | done 134 | EOF' 135 | # restart: always 136 | depends_on: 137 | - keycloak-db 138 | 139 | 140 | ######################################################### 141 | ######################################################### 142 | ## 143 | ## KEYCLOAK 144 | ## Identity provider 145 | ## Official Image 146 | ######################################################### 147 | ######################################################### 148 | keycloak: 149 | # image: quay.io/keycloak/keycloak:latest 150 | image: jboss/keycloak 151 | restart: unless-stopped 152 | logging: 153 | options: 154 | max-size: "10m" 155 | max-file: "3" 156 | container_name: keycloak 157 | domainname: ${DOMAINNAME} 158 | # ports: 159 | # - 8080:8080 160 | networks: 161 | - keycloak 162 | - traefik 163 | # volumes: 164 | # - ${KEYCLOAKBACKUP}/keycloak/config.json:/config.json 165 | # - /etc/localtime:/etc/localtime:ro 166 | environment: 167 | - PUID=1000 168 | - PGID=1000 169 | - KEYCLOAK_LOGLEVEL=WARNING 170 | # https://geek-cookbook.funkypenguin.co.nz/recipes/keycloak/setup-oidc-provider/ 171 | - KEYCLOAK_USER=${KEYCLOAK_USER} 172 | - KEYCLOAK_PASSWORD=${KEYCLOAK_PASSWORD} 173 | # - KEYCLOAK_IMPORT=/config.json 174 | - DB_VENDOR=postgres 175 | - DB_DATABASE=keycloak 176 | - DB_ADDR=keycloak-db 177 | - DB_USER=${DB_USER} 178 | - DB_PASSWORD=${DB_PASSWORD} 179 | # This is required to run keycloak behind traefik 180 | - PROXY_ADDRESS_FORWARDING=true 181 | - KEYCLOAK_HOSTNAME=keycloak.${DOMAINNAME} 182 | # Tell Postgress what user/password to create 183 | - POSTGRES_USER=${DB_USER} 184 | - POSTGRES_PASSWORD=${DB_PASSWORD} 185 | labels: 186 | - "traefik.enable=true" 187 | - "traefik.docker.network=traefik" 188 | - "traefik.port=8080" 189 | 190 | - "traefik.backend=traefik" 191 | 192 | # Define the URL to access this app 193 | - "traefik.http.routers.keycloak.rule=Host(`keycloak.${DOMAINNAME}`)" 194 | 195 | # Access via HTTPS only 196 | - "traefik.http.routers.keycloak.entrypoints=websecure" 197 | 198 | # The application (i.e. this container) 199 | - "traefik.http.routers.keycloak.service=keycloak" 200 | 201 | # Select middleware chain https-only and keycloakforwardauth 202 | #- "traefik.http.routers.keycloak.middlewares=keycloakForwardAuth" 203 | #- "traefik.http.routers.keycloak.middlewares=secured" 204 | 205 | - "traefik.http.services.keycloak.loadbalancer.server.port=8080" 206 | 207 | # TLS is used to protect the domain 208 | - "traefik.http.routers.keycloak.tls=true" 209 | - "traefik.http.routers.keycloak.tls.certresolver=leresolver" 210 | - "traefik.http.routers.keycloak.tls.domains[0].main=${DOMAINNAME}" 211 | - "traefik.http.routers.keycloak.tls.domains[0].sans=*.${DOMAINNAME}" 212 | 213 | depends_on: 214 | - keycloak-db 215 | 216 | # The auth gate for SSO 217 | # https://geek-cookbook.funkypenguin.co.nz/ha-docker-swarm/traefik-forward-auth/ 218 | 219 | 220 | ######################################################### 221 | ######################################################### 222 | ## 223 | ## TRAEFIK-FORWARD-AUTH 224 | ## Traefik middleware providing OpenID connect support 225 | ## through Keycloak IDP 226 | ######################################################### 227 | ######################################################### 228 | traefik-forward-auth: 229 | image: thomseddon/traefik-forward-auth:2.2 230 | restart: unless-stopped 231 | logging: 232 | options: 233 | max-size: "10m" 234 | max-file: "3" 235 | 236 | # image: thomseddon/traefik-forward-auth 237 | # image: funkypenguin/traefik-forward-auth 238 | container_name: traefik-forward-auth 239 | networks: 240 | - traefik 241 | ports: 242 | - 4181:4181 243 | environment: 244 | - DEFAULT_PROVIDER=oidc 245 | - DEFAULT_ACTION=auth 246 | # This is based on using the selected realm. Create a new client, this will go into your CLIENT_ID, CLIENT_SECRET details. 247 | - PROVIDERS_OIDC_ISSUER_URL=${PROVIDERS_OIDC_ISSUER_URL} 248 | - PROVIDERS_OIDC_CLIENT_ID=${PROVIDERS_OIDC_CLIENT_ID} 249 | - PROVIDERS_OIDC_CLIENT_SECRET=${PROVIDERS_OIDC_CLIENT_SECRET} 250 | - SECRET=THISISASECRET 251 | # - INSECURE_COOKIE=true 252 | - AUTH_HOST=auth.${DOMAINNAME} 253 | - URL_PATH=/_oauth 254 | - COOKIE_DOMAIN=${DOMAINNAME} 255 | - COOKIE_NAME=_forward_auth 256 | - CSRF_COOKIE_NAME=_forward_auth_csrf 257 | - LOG_LEVEL=warn 258 | # https://github.com/thomseddon/traefik-forward-auth/blob/master/examples/docker-compose-oidc.yml 259 | # https://github.com/thomseddon/traefik-forward-auth#user-restriction 260 | # WHITELIST: ${EMAIL} 261 | # COOKIE_SECURE: "true" 262 | - LIFETIME=2592000 263 | # - LIFETIME=43200 # 12 hours 264 | labels: 265 | - "traefik.enable=true" 266 | - "traefik.docker.network=traefik" 267 | - "traefik.port=4181" 268 | 269 | # Define the URL to access this app 270 | - "traefik.http.routers.traefik-forward-auth.rule=Host(`auth.${DOMAINNAME}`)" 271 | 272 | # Access via HTTPS only 273 | - "traefik.http.routers.traefik-forward-auth.entrypoints=websecure" 274 | 275 | - "traefik.frontend.rule=Host:auth.${DOMAINNAME}," 276 | - "traefik.frontend.headers.SSLHost=oauth.${DOMAINNAME}" 277 | 278 | - "traefik.frontend.passHostHeader=true" 279 | - "traefik.frontend.headers.SSLForceHost=true" 280 | - "traefik.frontend.headers.customResponseHeaders=X-Robots-Tag:noindex,nofollow,nosnippet,noarchive,notranslate,noimageindex" 281 | - "traefik.frontend.headers.SSLRedirect=true" 282 | - "traefik.frontend.headers.browserXSSFilter=true" 283 | - "traefik.frontend.headers.contentTypeNosniff=true" 284 | - "traefik.frontend.headers.forceSTSHeader=true" 285 | - "traefik.frontend.headers.STSSeconds=315360000" 286 | - "traefik.frontend.headers.STSIncludeSubdomains=true" 287 | - "traefik.frontend.headers.STSPreload=true" 288 | - "traefik.frontend.headers.frameDeny=true" 289 | 290 | - "traefik.backend=traefik-forward-auth" 291 | 292 | - "traefik.frontend.auth.forward.address=http://traefik-forward-auth:4181" 293 | - "traefik.frontend.auth.forward.trustForwardHeader=true" 294 | - "traefik.frontend.auth.forward.authResponseHeaders=X-Forwarded-User" 295 | 296 | - "traefik.http.routers.traefik-forward-auth.service=forward-auth-svc" 297 | - "traefik.http.services.forward-auth-svc.loadbalancer.server.port=4181" 298 | 299 | # TLS is used to protect the domain 300 | - "traefik.http.routers.traefik-forward-auth.tls=true" 301 | - "traefik.http.routers.traefik-forward-auth.tls.certresolver=leresolver" 302 | - "traefik.http.routers.traefik-forward-auth.tls.domains[0].main=${DOMAINNAME}" 303 | - "traefik.http.routers.traefik-forward-auth.tls.domains[0].sans=*.${DOMAINNAME}" 304 | 305 | # Forward authentication to keycloak 306 | ##- "traefik.http.routers.traefik-forward-auth.middlewares=keycloakForwardAuth" 307 | - "traefik.http.routers.traefik-forward-auth.middlewares=myForwardAuth@file" 308 | 309 | - "traefik.http.middlewares.keycloakForwardAuth.forwardauth.address=http://traefik-forward-auth:4181/auth" 310 | # - "traefik.http.routers.traefik-forward-auth.middlewares=secured" 311 | # - traefik.http.middlewares.secured.chain.middlewares=https-only@docker,keycloakForwardAuth@docker 312 | # - traefik.http.middlewares.https-only.redirectscheme.scheme=https 313 | - "traefik.http.middlewares.keycloakForwardAuth.forwardauth.trustForwardHeader=true" 314 | - "traefik.http.middlewares.keycloakForwardAuth.forwardauth.authResponseHeaders=X-Auth-User, X-Secret" 315 | # - "traefik.http.middlewares.keycloakForwardAuth.forwardauth.authResponseHeaders=X-Forwarded-User, X-WebAuth-User" 316 | - "traefik.http.middlewares.keycloakForwardAuth.forwardauth.tls.insecureSkipVerify=true" 317 | depends_on: 318 | - keycloak 319 | - traefik 320 | 321 | 322 | ######################################################### 323 | ######################################################### 324 | ## 325 | ## TRAEFIK 326 | ## The official v2.4 Traefik docker image 327 | ## The Cloud Native Edge Router 328 | ## 329 | ######################################################### 330 | ######################################################### 331 | traefik: 332 | 333 | image: traefik:v2.6 334 | restart: unless-stopped 335 | logging: 336 | options: 337 | max-size: "10m" 338 | max-file: "3" 339 | container_name: traefik 340 | command: 341 | - "--accesslog=true" 342 | - "--log.level=WARNING" 343 | - "--ping=true" 344 | # - "--ping.entrypoint=pingport" 345 | 346 | - "--api" 347 | - "--api.debug=true" 348 | # - "--api.insecure=true # Enables the web UI" 349 | 350 | # Define the ports Traefik listens on 351 | - "--entrypoints.web.address=:80" #Declares the web entrypoint in Traefik 352 | - "--entrypoints.websecure.address=:443" #Declares the websecure entrypoint in Traefik 353 | #- "--entrypoints.apiport.address=:10080" #Declares the webapi entrypoint in Traefik 354 | #- "--entrypoints.pingport.address=:10081" 355 | - "--entrypoints.httpauthforward.address=:4181" 356 | 357 | - "--entrypoints.ssh.address=:10022/tcp" 358 | - "--entrypoints.ssh.proxyProtocol.insecure=true" 359 | - "--entrypoints.ssh.forwardedHeaders.insecure=true" 360 | 361 | # Enable a tls challenge named "leresolver" 362 | - "--certificatesresolvers.leresolver.acme.email=${CF_API_EMAIL}" 363 | - "--certificatesresolvers.leresolver.acme.storage=/letsencrypt/acme.json" 364 | - "--certificatesresolvers.leresolver.acme.dnschallenge.provider=cloudflare" 365 | # - "--certificatesresolvers.leresolver.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory" 366 | 367 | # - "--file" 368 | # - "--resolvers=[192.168.1.1:53,1.1.1.1:53,]" 369 | 370 | - "--global.sendAnonymousUsage=true" 371 | 372 | # Providers 373 | # Tell Traefik to listen to docker 374 | - "--providers.docker=true" 375 | - "--providers.docker.exposedbydefault=false" 376 | 377 | - "--providers.file.directory=/conf" 378 | - "--providers.file.watch=true" 379 | 380 | - "--tracing=false" 381 | - "--tracing.serviceName=traefik" 382 | - "--tracing.jaeger=true" 383 | - "--tracing.jaeger.samplingServerURL=http://localhost:5778/sampling" 384 | - "--tracing.jaeger.samplingType=const" 385 | - "--tracing.jaeger.samplingParam=1.0" 386 | - "--tracing.jaeger.traceContextHeaderName=uber-trace-id" 387 | - "--tracing.jaeger.collector.endpoint=http://jaeger-collector:14268/api/traces?format=jaeger.thrift" 388 | 389 | - "--pilot.token=024ce057-f4da-46df-a406-417cf0bf2679" 390 | #- "--pilot.token=7dfa8bcd-95a5-4140-bef3-8ec0fdfbe004" 391 | #- "--pilot.token=5b168ddb-e421-4ddc-a763-8c1bec02ca61" 392 | 393 | 394 | environment: 395 | TZ: "Europe/Zurich" 396 | CF_API_EMAIL: ${CF_API_EMAIL} 397 | CF_DNS_API_TOKEN: ${CF_DNS_API_TOKEN} 398 | CF_ZONE_API_TOKEN: ${CF_ZONE_API_TOKEN} 399 | 400 | ports: 401 | - "80:80" # HTTP Port - web 402 | - "443:443" # HTTPS Port - websecure 403 | - "10022:10022" # TCP Port - ssh 404 | - "10080:10080" # API Port - apiport --api.insecure=true) 405 | - "10081:10081" # Ping port - pingport 406 | 407 | volumes: 408 | # So that Traefik can listen to the Docker events 409 | - "/var/run/docker.sock:/var/run/docker.sock:ro" 410 | - "./letsencrypt:/letsencrypt" 411 | - "./conf:/conf" 412 | 413 | networks: 414 | - traefik 415 | 416 | labels: 417 | - "traefik.enable=true" 418 | - "traefik.docker.network=traefik" 419 | 420 | # Define the URL to access this app 421 | - "traefik.http.routers.traefik.rule=Host(`traefik.${DOMAINNAME}`)" 422 | 423 | # Define the URL to access this app - split API and dashboard as different routes 424 | # - "traefik.http.routers.traefik.rule`(Host(`traefik.${DOMAINNAME}`) && PathPrefix(`/dashboard`) )" 425 | # - "traefik.http.routers.api.rule`(Host(`traefik.${DOMAINNAME}`) && PathPrefix(`/api`) )" 426 | 427 | - "traefik.http.routers.traefik.service=api@internal" 428 | - "traefik.http.routers.traefik.entrypoints=websecure" 429 | 430 | # Expose Traefik api to APIPort only. 431 | - "traefik.http.routers.api.entrypoints=websecure" 432 | #- "traefik.http.routers.api.middlewares=websecure" 433 | 434 | # Basic authentication by file 435 | - "traefik.http.routers.traefik.middlewares=users@file" 436 | # Forward authentication to keycloak for logon 437 | #- "traefik.http.routers.traefik.middlewares=myForwardAuth@file" 438 | 439 | # - "traefik.http.routers.traefik.service=traefik" 440 | # - "traefik.http.services.traefik.loadbalancer.server.port=4181" 441 | 442 | # Global Redirect HTTP to HTTPS 443 | - "traefik.http.routers.http-catchall.rule=hostregexp(`{host:.+}`)" 444 | - "traefik.http.routers.http-catchall.entrypoints=websecure" 445 | - "traefik.http.routers.http-catchall.middlewares=redirect-to-https" 446 | - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https" 447 | 448 | # Certificate name and san 449 | - "traefik.http.routers.traefik.tls=true" 450 | - "traefik.http.routers.traefik.tls.certresolver=leresolver" 451 | - "traefik.http.routers.traefik.tls.domains[0].main=${DOMAINNAME}" 452 | - "traefik.http.routers.traefik.tls.domains[0].sans=*.${DOMAINNAME}" 453 | 454 | 455 | ######################################################### 456 | ######################################################### 457 | ## 458 | ## Ingress sshd 459 | ## 460 | ## 461 | ## 462 | ######################################################### 463 | ######################################################### 464 | sshd: 465 | 466 | image: ghcr.io/stevegroom/sshd:latest 467 | restart: unless-stopped 468 | 469 | ports: 470 | - "122:22" # TCP Port - ssh 471 | 472 | volumes: 473 | # So that Traefik can listen to the Docker events 474 | - "./conf/sshd/sshsavedhostkeys:/etc/sshsavedhostkeys:rw" 475 | - "./conf/sshd/shelluserssh:/home/shelluser/.ssh:rw" 476 | 477 | networks: 478 | - traefik 479 | 480 | # Add physical hosts that to the sshd service to facilitate tunnelling 481 | # Variables are stored in .env 482 | extra_hosts: 483 | - ${SSHD_EXTRA_HOST1} 484 | - ${SSHD_EXTRA_HOST2} 485 | - ${SSHD_EXTRA_HOST3} 486 | - ${SSHD_EXTRA_HOST4} 487 | 488 | logging: 489 | options: 490 | max-size: "10m" 491 | max-file: "3" 492 | 493 | container_name: sshd 494 | 495 | entrypoint: /entrypoint.sh 496 | 497 | labels: 498 | - "traefik.enable=true" 499 | - "traefik.docker.network=traefik" 500 | - "traefik.port=80" 501 | - "traefik.tcp.routers.sshd.entrypoints=sshd" 502 | - "traefik.tcp.routers.sshd.service=sshd" 503 | - "traefik.tcp.routers.sshd.rule=HostSNI(`*`)" 504 | 505 | ######################################################### 506 | ######################################################### 507 | ## 508 | ## WHOAMI1 509 | ## Test app to show IP address and other discovered 510 | ## information 511 | ######################################################### 512 | ######################################################### 513 | whoami1: 514 | # A container that exposes an API to show its IP address 515 | image: containous/whoami 516 | restart: unless-stopped 517 | 518 | logging: 519 | options: 520 | max-size: "10m" 521 | max-file: "3" 522 | networks: 523 | - traefik 524 | 525 | labels: 526 | - "traefik.enable=true" 527 | - "traefik.docker.network=traefik" 528 | - "traefik.port=80" 529 | 530 | # Define the URL to access this app 531 | - "traefik.http.routers.whoami1.rule=Host(`whoami1.${DOMAINNAME}`)" 532 | 533 | # Access via HTTPS only 534 | - "traefik.http.routers.whoami1.entrypoints=websecure" 535 | 536 | # Forward authentication to keycloak for logon 537 | - "traefik.http.routers.whoami1.middlewares=myForwardAuth@file" 538 | 539 | # The application (i.e. this container) 540 | - "traefik.http.routers.whoami1.service=whoami1" 541 | - "traefik.http.services.whoami1.loadbalancer.server.port=80" 542 | 543 | # TLS is used to protect the domain 544 | - "traefik.http.routers.whoami1.tls=true" 545 | - "traefik.http.routers.whoami1.tls.certresolver=leresolver" 546 | - "traefik.http.routers.whoami1.tls.domains[0].main=${DOMAINNAME}" 547 | - "traefik.http.routers.whoami1.tls.domains[0].sans=*.${DOMAINNAME}" 548 | -------------------------------------------------------------------------------- /traefik/letsencrypt/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | acme.json -------------------------------------------------------------------------------- /traefikAtHome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stevegroom/traefikGateway/2d44a5e7bc3fc1d66c6c9cc253373af0d6cd5fb8/traefikAtHome.png -------------------------------------------------------------------------------- /ubuntu/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | ######################################################### 3 | ######################################################### 4 | ## 5 | ## UBUNTU 6 | ## Testing ssh ingress to Ubuntu 7 | ## information 8 | ######################################################### 9 | ######################################################### 10 | services: 11 | ubuntu: 12 | # A container that exposes an API to show its IP address 13 | image: ubuntu 14 | 15 | # networks: 16 | # - traefik 17 | 18 | # command: 19 | # - "--banner=true" 20 | 21 | 22 | ports: 23 | - "10023:22" 24 | 25 | labels: 26 | - "traefik.enable=true" 27 | - "traefik.docker.network=traefik" 28 | - "traefik.port=10023/tcp" 29 | 30 | # Define the URL to access this app 31 | - "traefik.tcp.routers.ubuntu.rule=HostSNI(`*`)" 32 | 33 | # Access via TCP only 34 | - "traefik.tcp.routers.ubuntu.entrypoints=ssh" 35 | 36 | # Forward authentication to keycloak for logon 37 | #- "traefik.tcp.routers.ubuntu.middlewares=keycloakForwardAuth@docker" 38 | 39 | # The application (i.e. this container) 40 | - "traefik.tcp.routers.ubuntu.service=ubuntu" 41 | - "traefik.tcp.services.ubuntu.loadbalancer.server.port=22" 42 | 43 | # TLS is used to protect the domain 44 | - "traefik.tcp.routers.ubuntu.tls=true" 45 | - "traefik.tcp.routers.ubuntu.tls.certresolver=leresolver" 46 | - "traefik.tcp.routers.ubuntu.tls.domains[0].main=${DOMAINNAME}" 47 | - "traefik.tcp.routers.ubuntu.tls.domains[0].sans=*.${DOMAINNAME}" 48 | - "traefik.tcp.routers.ubuntu.tls.passthrough=true" 49 | 50 | networks: 51 | default: 52 | external: 53 | name: traefik -------------------------------------------------------------------------------- /whoami2/.env_sample: -------------------------------------------------------------------------------- 1 | # 2 | DOMAINNAME=yourdomain -------------------------------------------------------------------------------- /whoami2/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | 3 | networks: 4 | traefik: 5 | external: true 6 | name: traefik 7 | 8 | services: 9 | whoami2: 10 | # A container that exposes an API to show its IP address 11 | image: containous/whoami 12 | networks: 13 | traefik: 14 | 15 | labels: 16 | - "traefik.enable=true" 17 | - "traefik.docker.network=traefik" 18 | - "traefik.port=80" 19 | 20 | # Define the URL to access this app 21 | - "traefik.http.routers.whoami2.rule=Host(`whoami2.${DOMAINNAME}`)" 22 | 23 | # Access via HTTPS only 24 | - "traefik.http.routers.whoami2.entrypoints=websecure" 25 | 26 | # Forward authentication to keycloak for logon 27 | #- "traefik.http.routers.whoami2.middlewares=keycloakForwardAuth@docker" 28 | 29 | # The application (i.e. this container) 30 | - "traefik.http.routers.whoami2.service=whoami2" 31 | - "traefik.http.services.whoami2.loadbalancer.server.port=80" 32 | 33 | # TLS is used to protect the domain 34 | - "traefik.http.routers.whoami2.tls=true" 35 | - "traefik.http.routers.whoami2.tls.certresolver=leresolver" 36 | - "traefik.http.routers.whoami2.tls.domains[0].main=${DOMAINNAME}" 37 | - "traefik.http.routers.whoami2.tls.domains[0].sans=*.${DOMAINNAME}" 38 | -------------------------------------------------------------------------------- /whoami3/.env_sample: -------------------------------------------------------------------------------- 1 | # 2 | DOMAINNAME=yourdomain -------------------------------------------------------------------------------- /whoami3/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | 3 | networks: 4 | traefik: 5 | external: true 6 | name: traefik 7 | 8 | services: 9 | whoami3: 10 | # A container that exposes an API to show its IP address 11 | image: containous/whoami 12 | networks: 13 | traefik: 14 | 15 | labels: 16 | - "traefik.enable=true" 17 | - "traefik.docker.network=traefik" 18 | - "traefik.port=80" 19 | 20 | # Define the URL to access this app 21 | - "traefik.http.routers.whoami3.rule=Host(`whoami3.${DOMAINNAME}`)" 22 | 23 | # Access via HTTPS only 24 | - "traefik.http.routers.whoami3.entrypoints=websecure" 25 | 26 | # Forward authentication to keycloak for logon 27 | - "traefik.http.routers.whoami3.middlewares=keycloakForwardAuth@docker" 28 | 29 | # The application (i.e. this container) 30 | - "traefik.http.routers.whoami3.service=whoami3" 31 | - "traefik.http.services.whoami3.loadbalancer.server.port=80" 32 | 33 | # TLS is used to protect the domain 34 | - "traefik.http.routers.whoami3.tls=true" 35 | - "traefik.http.routers.whoami3.tls.certresolver=leresolver" 36 | - "traefik.http.routers.whoami3.tls.domains[0].main=${DOMAINNAME}" 37 | - "traefik.http.routers.whoami3.tls.domains[0].sans=*.${DOMAINNAME}" 38 | -------------------------------------------------------------------------------- /whoamitcp/.env_sample: -------------------------------------------------------------------------------- 1 | # 2 | DOMAINNAME=yourdomain -------------------------------------------------------------------------------- /whoamitcp/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | ######################################################### 3 | ######################################################### 4 | ## 5 | ## WHOAMITCP 6 | ## Test app to show IP address and other discovered 7 | ## information 8 | ######################################################### 9 | ######################################################### 10 | services: 11 | whoamitcp: 12 | # A container that exposes an API to show its IP address 13 | image: containous/whoamitcp 14 | 15 | # networks: 16 | # - traefik 17 | 18 | command: 19 | - "--banner=true" 20 | - "--name='Whats in a name'" 21 | - "--port=whoamitcp:11122" 22 | 23 | ports: 24 | - "11122:11122" 25 | 26 | labels: 27 | - "traefik.enable=true" 28 | - "traefik.docker.network=traefik" 29 | - "traefik.port=11122/tcp" 30 | 31 | # Define the URL to access this app 32 | - "traefik.tcp.routers.whoamitcp.rule=HostSNI(`*`)" 33 | 34 | # Access via TCP only 35 | - "traefik.tcp.routers.whoamitcp.entrypoints=ssh" 36 | 37 | # Forward authentication to keycloak for logon 38 | #- "traefik.tcp.routers.whoamitcp.middlewares=keycloakForwardAuth@docker" 39 | 40 | # The application (i.e. this container) 41 | - "traefik.tcp.routers.whoamitcp.service=whoamitcp" 42 | - "traefik.tcp.services.whoamitcp.loadbalancer.server.port=11122" 43 | 44 | # TLS is used to protect the domain 45 | - "traefik.tcp.routers.whoamitcp.tls=true" 46 | - "traefik.tcp.routers.whoamitcp.tls.certresolver=leresolver" 47 | - "traefik.tcp.routers.whoamitcp.tls.domains[0].main=${DOMAINNAME}" 48 | - "traefik.tcp.routers.whoamitcp.tls.domains[0].sans=*.${DOMAINNAME}" 49 | - "traefik.tcp.routers.whoamitcp.tls.passthrough=true" 50 | 51 | networks: 52 | default: 53 | external: 54 | name: traefik --------------------------------------------------------------------------------