pihole-kubernetes icon indicating copy to clipboard operation
pihole-kubernetes copied to clipboard

`serviceWeb.https` does not work

Open hrpatel opened this issue 4 years ago • 8 comments

Hello,

Using/enabling serviceWeb.https does not work since the app container only listens on port 80 (even though a LoadBalancer and corresponding port definitions exist for the pod/container).

I've looked through the docs/issues/PRs and haven't come across a similar issue or seen a way of using TLS on the admin page.

Is this expected or am I missing something?

Thank you in advance for your time!

hrpatel avatar Apr 23 '21 05:04 hrpatel

Sorry @hrpatel I missed this ticket. Did you get this working?

MoJo2600 avatar Sep 23 '21 06:09 MoJo2600

Came across the same issue. The container is only listening on port 80. curl -k https://localhost inside the container shows Connection refused.

reitermarkus avatar Nov 05 '21 07:11 reitermarkus

Ok, i think there is a misunderstanding. Please correct me if I'm wrong. I'm not using TLS for pihole so I might be wrong on this topic.

The setting serviceWeb.https will only att the port 443 to the created service. The setting webHttps will only add the containerPort to the container

If you want to have HTTPS enabled there are differnet options. First you have to decide where the TLS termination should take place.

a) At the pihole container: There are two ways, either you create a proxy to handle the certificates (https://discourse.pi-hole.net/t/docker-ssl-certificate-to-pi-hole-web-interface/28919/3) or you configure pihole to handle the certificates (but I'm not sure how to implement this inside the docker container https://discourse.pi-hole.net/t/enabling-https-for-your-pi-hole-web-interface/5771).

b) At the Loadbalancer In this case the Loadbalancer would talk to the container unencrypted on port 80. The Loadbalancer would only expose port 443 for https traffic.

c) At the ingress I think it should be possible to add HTTPS termination to an ingress as well, but I don't know how to do this.

Maybe somebody who has implemented https to pihole could help to clarify this. @utkuozdemir implemented this feature, maybe he has https working already?

MoJo2600 avatar Nov 05 '21 09:11 MoJo2600

I found another tutorial on how to get TLS running with MetalLB https://blog.tekspace.io/setup-kubernetes-cluster-using-k3s-metallb-letsencrypt-on-bare-metal/

MoJo2600 avatar Nov 05 '21 09:11 MoJo2600

@MoJo2600 In my PR I actually didn't touch anything related to the HTTPS logic. My goal was to be able to make it configurable so that it would be possible to get the following part in the service customizable:

  ports:
    - port: 80
      targetPort: http
      protocol: TCP
      name: http
    - port: 443
      targetPort: https
      protocol: TCP
      name: https

In my case, I did not want the section with the 443 https port, because I was using Rancher and its LoadBalancer implementation exposes whatever ports it finds in the Service from the same public IP to the outside world - in my case 443 was already occupied by another process, so I couldn't use Rancher ServiceLB as-is.

With my changes, it is possible to override the used port numbers and disable individual port sections in the manifests - but it does not configure anything regarding the actual HTTPS logic.

utkuozdemir avatar Nov 05 '21 09:11 utkuozdemir

Regarding the HTTPS topic overall:

  • I do the SSL termination at the ingress controller level - as all my other applications.
  • I use Rancher which takes care of the ServiceLBs + comes with Traefik ingress controller.
  • I install cert-manager + configure a ClusterIssuer for let's encrypt named lets-encrypt

My ingresses look like this:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    cert-manager.io/cluster-issuer: lets-encrypt
    traefik.ingress.kubernetes.io/redirect-entry-point: https
  name: pihole
spec:
  rules:
    - host: pihole.example.com
      http: ...
  tls:
    - hosts:
        - pihole.example.com
      secretName: jellyfin-tls

Important part is the annotations and the TLS section - a matching host and particularly you need to specify a secretName that will be filled by cert-manager with the valid SSL certificate.

Of course, you'll also need a DNS record that points pihole.example.com -> YOUR_SERVICELB_IP (In my case it's a wildcard, *.example.com)

utkuozdemir avatar Nov 05 '21 10:11 utkuozdemir

Thank you for your input @utkuozdemir. I think you explained it very well.

MoJo2600 avatar Nov 05 '21 11:11 MoJo2600

I'm doing HTTPS a bit differently than others. I don't want pihole to rely on my ingress. This is because, at this time, my pihole is serving the DNS for the ingress.

This is either incredibly neat or incredibly stupid. Probably both. But I do think there's a lot of value in a standalone TLS config for something like PiHole. This way my PiHole admin console doesn't rely on ingress tech of any kind.

I use MetalLB in my example, but you could totally hostPort it if you wanted, the Virtual IP (VIP) just made it easy to issue a certificate, which I use CertManager.

This means if pihole is down, I cannot get to the ingress.

Because of this I opted to just memorize an easy ip addr, (in the below example 192.168.1.10) which serves as my PiHole's VIP via MetalLB.

NOTE: My notes here do rely on my pull requests, so this won't work for you until these are merged (I'm just slamming them in my own locally versioned chart):

  • https://github.com/MoJo2600/pihole-kubernetes/pull/210
  • https://github.com/MoJo2600/pihole-kubernetes/pull/211
  • https://github.com/MoJo2600/pihole-kubernetes/pull/229

These PRs are needed to mount in a custom external.conf file for lighthttpd and to change the probes to https. Due to the way the redirects work the default kubernetes probes get an https response when they expect an http one (in my testing).

Full Example

Before using this helm chart and kubernetes, I'd used the external.conf file to inject TLS settings like this. (NOTE: these have changed as of container versions 2022.04, so I'll include both)

values.yaml (any tag before 2022.04.01)

image:
  tag: "2022.02.1" # This is the chart default at the time of posting

nodeSelector:
  node-role.kubernetes.io/pihole: pihole

replicaCount: 2

DNS1: "1.1.1.1"
DNS2: "8.8.8.8"

antiaff:
  enabled: true
  avoidRelease: pihole
  strict: true

serviceWeb:
  annotations:
    metallb.universe.tf/allow-shared-ip: pihole-svc
  type: LoadBalancer
  loadBalancerIP: 192.168.1.10

serviceDns:
  annotations:
    metallb.universe.tf/allow-shared-ip: pihole-svc
  type: LoadBalancer
  loadBalancerIP: 192.168.1.10

probes:
  liveness:
    port: https
    scheme: HTTPS
  readiness:
    port: https
    scheme: HTTPS

extraVolumes:
  external-conf-cm:
    configMap:
      name: lighttpd-external-conf
  external-conf:
    emptyDir:
      medium: Memory
  tls-cert:
    secret:
      secretName: pihole-tls-cert

extraVolumeMounts:
  external-conf:
    mountPath: /etc/lighttpd/external.conf
    subPath: external.conf
  tls-cert:
    mountPath: "/etc/ssl/lighttpd-private"

extraInitContainers:
  # Pi-hole attempts to touch /etc/lighttpd/external.conf to make sure it exists
  # but since we're using a configmap this fails.
  # 
  # This way, we copy a real file that's touchable (not a read-only filesystem,
  # like a configmap is). I'd still (someday) like to be able to mount the configmap
  # directly.
  - name: copy-config
    image: busybox
    args:
      - sh
      - -c
      - |
        cp /etc/lighttpd-cm/external.conf /etc/lighttpd/
        ls -l /etc/lighttpd/
    volumeMounts:
      - name: external-conf-cm
        mountPath: /etc/lighttpd-cm/
      - name: external-conf
        mountPath: /etc/lighttpd/

dnsmasq:
  customDnsEntries:
    # Server up *.lab.example.com wildcard to an ingress traefik IP
    - address=/lab.example.com/192.168.1.20
    # Server up *.dev.example.com wildcard to an ingress traefik IP
    - address=/dev.example.com/192.168.1.21

  customSettings:
  - domain=example.com
  - expand-hosts
  - local=/example.com/

  additionalHostsEntries:
    - 192.168.1.30	foo.example.com

extraObjects:
  - apiVersion: cert-manager.io/v1
    kind: Certificate
    metadata:
      name: pihole-tls-cert
    spec:
      dnsNames:
        - "pihole.example.com"
        - "dns.example.com"
      ipAddresses:
        - "192.168.1.10"
      issuerRef:
        group: cert-manager.io
        kind: ClusterIssuer
        name: my-cluster-issuer
      secretName: pihole-tls-cert
  - apiVersion: v1
    kind: ConfigMap
    metadata:
      name: lighttpd-external-conf
    data:
      external.conf: |
        # Redirect HTTP to HTTPS
        $HTTP["scheme"] == "http" {
          $HTTP["host"] =~ ".*" {
            url.redirect = (".*" => "https://%0$0")
          }
        }
      external.conf: |
        $HTTP["host"] =~ "pihole.example.com" {
          # Ensure the Pi-hole Block Page knows that this is not a blocked domain
          setenv.add-environment = ("fqdn" => "true")
        
          # Enable the SSL engine with the mapped in certs from CertManager.
          $SERVER["socket"] == ":443" {
            ssl.engine = "enable"
            ssl.pemfile = "/etc/ssl/lighttpd-private/tls.crt"
            ssl.privkey = "/etc/ssl/lighttpd-private/tls.key"
            ssl.ca-file = "/etc/ssl/lighttpd-private/ca.crt"
            ssl.honor-cipher-order = "enable"
            ssl.cipher-list = "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH"
            ssl.use-sslv2 = "disable"
            ssl.use-sslv3 = "disable"
          }
        }
        # Redirect HTTP to HTTPS
        $HTTP["scheme"] == "http" {
          $HTTP["host"] =~ ".*" {
            url.redirect = (".*" => "https://%0$0")
          }
        }

values.yaml (2022.04.01 and above, I'm using `2022.05 atm)

image:
  tag: "2022.05"

nodeSelector:
  node-role.kubernetes.io/pihole: pihole

replicaCount: 2

DNS1: "1.1.1.1"
DNS2: "8.8.8.8"

antiaff:
  enabled: true
  avoidRelease: pihole
  strict: true

serviceWeb:
  annotations:
    metallb.universe.tf/allow-shared-ip: pihole-svc
  type: LoadBalancer
  loadBalancerIP: 192.168.1.10

serviceDns:
  annotations:
    metallb.universe.tf/allow-shared-ip: pihole-svc
  type: LoadBalancer
  loadBalancerIP: 192.168.1.10

probes:
  liveness:
    port: https
    scheme: HTTPS
  readiness:
    port: https
    scheme: HTTPS

extraVolumes:
  external-conf-cm:
    configMap:
      name: lighttpd-external-conf
  external-conf:
    emptyDir:
      medium: Memory
  tls-cert:
    secret:
      secretName: pihole-tls-cert

extraVolumeMounts:
  external-conf:
    mountPath: /etc/lighttpd/external.conf
    subPath: external.conf
  tls-cert:
    mountPath: "/etc/ssl/lighttpd-private"

extraInitContainers:
  # Pi-hole attempts to touch /etc/lighttpd/external.conf to make sure it exists
  # but since we're using a configmap this fails.
  # 
  # This way, we copy a real file that's touchable (not a read-only filesystem,
  # like a configmap is). I'd still (someday) like to be able to mount the configmap
  # directly.
  - name: copy-config
    image: busybox
    args:
      - sh
      - -c
      - |
        cp /etc/lighttpd-cm/external.conf /etc/lighttpd/
        ls -l /etc/lighttpd/
    volumeMounts:
      - name: external-conf-cm
        mountPath: /etc/lighttpd-cm/
      - name: external-conf
        mountPath: /etc/lighttpd/

dnsmasq:
  customDnsEntries:
    # Server up *.lab.example.com wildcard to an ingress traefik IP
    - address=/lab.example.com/192.168.1.20
    # Server up *.dev.example.com wildcard to an ingress traefik IP
    - address=/dev.example.com/192.168.1.21

  customSettings:
  - domain=example.com
  - expand-hosts
  - local=/example.com/

  additionalHostsEntries:
    - 192.168.1.30	foo.example.com

extraObjects:
  - apiVersion: cert-manager.io/v1
    kind: Certificate
    metadata:
      name: pihole-tls-cert
    spec:
      dnsNames:
        - "pihole.example.com"
        - "dns.example.com"
      ipAddresses:
        - "192.168.1.10"
      issuerRef:
        group: cert-manager.io
        kind: ClusterIssuer
        name: my-example-issuer
      secretName: pihole-tls-cert
  - apiVersion: v1
    kind: ConfigMap
    metadata:
      name: lighttpd-external-conf
    data:
      external.conf: |
        # New configs as of 2022.04 and newer
        server.modules += ( "mod_openssl" )

        setenv.add-environment = ("fqdn" => "true")

        # Enable the SSL engine with the mapped in certs from CertManager.
        $SERVER["socket"] == ":443" {
          ssl.engine  = "enable"
          ssl.pemfile = "/etc/ssl/lighttpd-private/tls.crt"
          ssl.privkey = "/etc/ssl/lighttpd-private/tls.key"
          ssl.ca-file = "/etc/ssl/lighttpd-private/ca.crt"
          ssl.openssl.ssl-conf-cmd = ("MinProtocol" => "TLSv1.3", "Options" => "-ServerPreference")
        }

        # Redirect HTTP to HTTPS
        $HTTP["scheme"] == "http" {
          $HTTP["host"] =~ ".*" {
            url.redirect = (".*" => "https://%0$0")
          }
        }

fernferret avatar May 28 '22 20:05 fernferret