From 41326fe3a1b461c762aa6f1fcef49f58786cd07c Mon Sep 17 00:00:00 2001 From: jurgenhaas <juergen.haas@lakedrops.com> Date: Wed, 22 Feb 2023 10:33:05 +0100 Subject: [PATCH] Update Traefik to 2.9 Update Portainer to 2.17.1 Update Hub to 1.0.1 Add support for automatic cert renewal with http or dns challenge --- Traefik.php | 125 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 109 insertions(+), 16 deletions(-) diff --git a/Traefik.php b/Traefik.php index 63a7df3..f709067 100644 --- a/Traefik.php +++ b/Traefik.php @@ -17,42 +17,67 @@ class Traefik { * * @var string */ - protected $name; + protected string $name; /** * @var string */ - protected $domain; + protected string $domain; /** * @var int */ - protected $http_port; + protected int $http_port; /** * @var int */ - protected $https_port; + protected int $https_port; /** * @var string */ - protected $cert_filename; + protected string $cert_filename; /** * @var string */ - protected $key_filename; + protected string $key_filename; /** * @var bool */ - protected $addon_portainer = FALSE; + protected bool $addon_portainer = FALSE; /** * @var string */ - protected $hub_token = ''; + protected string $hub_token = ''; + + /** + * @var array + */ + protected array $env; + + /** + * @var bool + */ + protected bool $tls; + + /** + * @var bool + */ + protected bool $dns_challenge; + + /** + * @var string + */ + protected string $dns_challenge_provider; + + /** + * @var string + */ + protected string $dns_challenge_resolver; /** * Traefik constructor. @@ -70,14 +95,45 @@ class Traefik { * Filename of the SSL certificate. * @param string $key_filename * Filename of the private key for the SSL certificate. + * @param array $env + * A list of environment variables for the Traefik container. + * @param bool $tls + * Whether TLS should be supported. + * @param bool $dns_challenge + * If TLS is supported, it uses the http challenge by default. Set to TRUE + * to use the DNS challenge. + * @param string $dns_challenge_provider + * If DNS challenge should be used, a provider is required. For a list of + * supported providers: + * @see https://doc.traefik.io/traefik/https/acme/#providers + * @param string $dns_challenge_resolver + * In some scenarios, the DNS resolver needs to be defined in order to + * prevent local or shadow DNS servers being used. */ - public function __construct(string $name, $domain = 'docker.localhost', $http_port = 8000, $https_port = 8443, $cert_filename = 'fullchain.pem', $key_filename = 'privkey.pem') { + public function __construct( + string $name, + string $domain = 'docker.localhost', + int $http_port = 8000, + int $https_port = 8443, + string $cert_filename = '', + string $key_filename = '', + array $env = [], + bool $tls = FALSE, + bool $dns_challenge = FALSE, + string $dns_challenge_provider = '', + string $dns_challenge_resolver = '' + ) { $this->name = $name; $this->domain = $domain; $this->http_port = $http_port; $this->https_port = $https_port; $this->cert_filename = $cert_filename; $this->key_filename = $key_filename; + $this->env = $env; + $this->tls = $tls; + $this->dns_challenge = $dns_challenge; + $this->dns_challenge_provider = $dns_challenge_provider; + $this->dns_challenge_resolver = $dns_challenge_resolver; } /** @@ -146,6 +202,13 @@ class Traefik { * @return bool */ private function updateFile(string $filename, string $content): bool { + if ($content === '') { + if (file_exists($filename)) { + unlink($filename); + return TRUE; + } + return FALSE; + } if (file_exists($filename) && file_get_contents($filename) === $content) { return FALSE; } @@ -162,15 +225,13 @@ class Traefik { 'services' => [ 'traefik' => [ 'container_name' => 'traefik', - 'image' => 'traefik:2.7', + 'image' => 'traefik:2.9', 'command' => [ '--api=true', '--api.dashboard=true', '--api.insecure=true', + '--log.level=warning', '--entrypoints.web.address=:80', - '--entrypoints.websecure.address=:443', - '--entrypoints.websecure.http.tls.domains[0].main=' . $this->domain, - '--entrypoints.websecure.http.tls.domains[0].sans=.' . $this->domain, '--providers.file.directory=/configuration/', '--providers.file.watch=true', '--providers.docker=true', @@ -182,7 +243,6 @@ class Traefik { ], 'ports' => [ $this->http_port . ':80', - $this->https_port . ':443', ], 'labels' => [ 'traefik.enable=true', @@ -203,10 +263,13 @@ class Traefik { ], ], ]; + if (!empty($this->env)) { + $config['services']['traefik']['environment'] = $this->env; + } if ($this->addon_portainer) { $config['services']['portainer'] = [ 'container_name' => 'portainer', - 'image' => 'portainer/portainer-ce:2.11.1', + 'image' => 'portainer/portainer-ce:2.17.1', 'command' => '-H unix:///var/run/docker.sock', 'restart' => 'unless-stopped', 'networks' => [ @@ -238,7 +301,7 @@ class Traefik { $config['services']['traefik']['ports'][] = '9901:9901'; $config['services']['hub-agent'] = [ 'container_name' => 'hub-agent', - 'image' => 'ghcr.io/traefik/hub-agent-traefik:v0.7.0', + 'image' => 'ghcr.io/traefik/hub-agent-traefik:v1.0.1', 'command' => [ 'run', '--hub.token=' . $this->hub_token, @@ -258,6 +321,33 @@ class Traefik { ], ]; } + if ($this->tls) { + $config['services']['traefik']['command'][] = '--entrypoints.websecure.address=:443'; + $config['services']['traefik']['command'][] = '--entrypoints.web.http.redirections.entrypoint.to=websecure'; + $config['services']['traefik']['command'][] = '--certificatesresolvers.lakedrops.acme.email=admin@' . $this->domain; + $config['services']['traefik']['ports'][] = $this->https_port . ':443'; + $config['services']['traefik']['labels'][] = 'traefik.http.routers.traefik.tls=true'; + $config['services']['traefik']['labels'][] = 'traefik.http.routers.traefik.tls.certresolver=lakedrops'; + if ($this->addon_portainer) { + $config['services']['portainer']['labels'][] = 'traefik.http.routers.traefik.tls=true'; + $config['services']['portainer']['labels'][] = 'traefik.http.routers.traefik.tls.certresolver=lakedrops'; + } + if ($this->hub_token) { + $config['services']['hub-agent']['labels'][] = 'traefik.http.routers.traefik.tls=true'; + $config['services']['hub-agent']['labels'][] = 'traefik.http.routers.traefik.tls.certresolver=lakedrops'; + } + if ($this->dns_challenge && $this->dns_challenge_provider !== '') { + $config['services']['traefik']['command'][] = '--certificatesresolvers.lakedrops.acme.dnschallenge=true'; + $config['services']['traefik']['command'][] = '--certificatesresolvers.lakedrops.acme.dnschallenge.provider=' . $this->dns_challenge_provider; + if ($this->dns_challenge_resolver !== '') { + $config['services']['traefik']['command'][] = '--certificatesresolvers.lakedrops.acme.dnschallenge.resolvers=' . $this->dns_challenge_resolver; + } + } + else { + $config['services']['traefik']['command'][] = '--certificatesresolvers.lakedrops.acme.storage=/etc/traefik/acme/acme.json'; + $config['services']['traefik']['command'][] = '--certificatesresolvers.lakedrops.acme.httpchallenge.entrypoint=web'; + } + } return $config; } @@ -265,6 +355,9 @@ class Traefik { * @return string */ private function defaultCertificatesConfig(): string { + if ($this->cert_filename === '' || $this->key_filename === '') { + return ''; + } return <<<EOF [[tls.certificates]] certFile = "/certs/$this->cert_filename" -- GitLab