Homelab: Certyfikaty i Dynamic DNS

Logo

W poprzednim wpisie z tego cyklu napisałem co nieco na temat postawienia k3s na prywatnym poleasingowym terminalu Della, który służy mi wiernie jako prywatny homelab.
Zainspirowany przez znajomych, rozwinę niektóre z tematów z poprzedniego wpisu i pokażę coś więcej o usługach Cloudflare oraz o zabezpieczeniu naszych usług i tajemnic.


Cloudflare Dynamic DNS

Czasami zamiast tunelowania, chcemy skorzystać bezpośrednio z wpisów A, ale… jak w mojej i wielu innych sytuacjach, mamy zmienne IP. Wtedy z pomocą przychodzi druga pożyteczna usługa w Cloudflare (która ma też inne dynamic dns odpowiedniki, jak np. DuckDNS).

W przeciwieństwie do tunelowania, konfiguracja Dynamic DNS jest nieco bardziej manualna (choć da się ją zautomatyzować) i wymaga wygenerowania tokena API z ustawieniami DNS w panelu Cloudflare. Dodatkowo warto pobrać również zoneId dla istniejącej domeny. Serwery gier itp napewno lepiej postawić w takiej formie

Tip: zoneId można pobrać w panelu Cloudflare w sekcji Overview, (Prawy dolny róg)

Konfiguracja

Na portalu Cloudflare, w sekcji subdomains ustawiamy, do jakiej subdomeny chcemy aktualizować nasze IP. Jeśli chcemy, aby połączenie szło bezpośrednio, możemy wyłączyć proxy (proxied: false).

Przykładowy plik konfiguracyjny (config.json):

{
  "cloudflare": [
    {
      "authentication": {
        "api_token": "apiToken"
      },
      "zone_id": "zoneFromCloudflare",
      "subdomains": [
        {
          "name": "www",
          "proxied": true
        }
      ]
    }
  ],
  "a": true,
  "aaaa": false,
  "purgeUnknownRecords": false,
  "ttl": "Auto"
}

Tak przygotowany plik należy zakodować do Base64:

cat config.json | base64

Następnie wkleić zakodowaną wartość do pliku config.yaml używanego przez Kubernetes.

Konfiguracja Kubernetes

---
apiVersion: v1
kind: Namespace
metadata:
  name: cloudflare-ddns
---
apiVersion: v1
data:
  config.json: zakodowany_base64_z_poprzedniego_kroku
kind: Secret
metadata:
  name: cloudflare-ddns
  namespace: cloudflare-ddns
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: cloudflare-ddns
  namespace: cloudflare-ddns
spec:
  selector:
    matchLabels:
      app: cloudflare-ddns
  template:
    metadata:
      labels:
        app: cloudflare-ddns
    spec:
      containers:
      - name: cloudflare-ddns
        image: timothyjmiller/cloudflare-ddns:latest
        env:
        - name: CONFIG_PATH
          value: '/etc/cloudflare-ddns/'
        volumeMounts:
        - mountPath: '/etc/cloudflare-ddns'
          name: cloudflare-ddns
          readOnly: true
      volumes:
      - name: cloudflare-ddns
        secret:
          secretName: cloudflare-ddns

Po zaaplikowaniu ustawień możemy skonfigurować domeny w naszym domyślnym load balancerze i podpiąć certyfikat Let’s Encrypt.


Tworzenie sekretów w Kubernetes

Aby lepiej chronić wrażliwe dane (np. klucze API lub connection strings), możemy używać sekretów zamiast zmiennych środowiskowych. Zapewni nam to mniej wycieków po stronie kodu, gdzie możemy śmielej wrzucać do repozytorium naszą konfigurację kubernetesową czy helmową, ponieważ wiemy, że nasze dane są w naszej bezpiecznej krypcie.

Tworzenie sekretu

Sekrety można stworzyć prostą komendą:

kubectl create secret generic api-secrets \
  --from-literal=ConnectionStrings__Marten="wartość_marten" \
  --from-literal=Media__ConnectionStrings="wartość_media"

Weryfikacja sekretów

Możesz sprawdzić utworzone sekrety w klastrze:

kubectl get secret api-secrets

Użycie sekretów w aplikacji

Dodaj do swojego pliku Deployment odwołania do sekretów jako zmienne środowiskowe:

env:
  - name: ConnectionStrings__Marten
    valueFrom:
      secretKeyRef:
        name: api-secrets
        key: ConnectionStrings__Marten
  - name: Media__ConnectionStrings
    valueFrom:
      secretKeyRef:
        name: api-secrets
        key: Media__ConnectionStrings

Let’s Encrypt na Homelab

Jeśli chcemy zautomatyzować zarządzanie certyfikatami SSL/TLS, możemy użyć narzędzia Cert Manager w Kubernetes.

Cert Manager integruje się z Let’s Encrypt, umożliwiając automatyczne wystawianie i odnawianie certyfikatów, przez co deploy trwa o wiele krócej. Nic nie stoi na przeszkodzie, by kupić certyfikat i go wstawić jako nowy secret w naszym środowisku.

  1. Instalacja Cert Managera za pomocą Helm:
   helm repo add jetstack https://charts.jetstack.io
   helm repo update
   helm install cert-manager jetstack/cert-manager \
     --namespace cert-manager \
     --create-namespace \
     --set installCRDs=true
  1. Utworzenie sekreta z API tokenem Cloudflare:
   kubectl create secret generic cloudflare-api-token \
     --namespace cert-manager \
     --from-literal=api-token=<YOUR_CLOUDFLARE_API_TOKEN>
  1. Tworzenie ClusterIssuer:
   apiVersion: cert-manager.io/v1
   kind: ClusterIssuer
   metadata:
     name: lifelike-cert
   spec:
     acme:
       email: <your-email@example.com>
       server: https://acme-v02.api.letsencrypt.org/directory
       privateKeySecretRef:
         name: cluster-issuer-account-key
       solvers:
       - dns01:
           cloudflare:
             email: <your-cloudflare-email@example.com>
             apiTokenSecretRef:
               name: cloudflare-api-token
               key: api-token
  1. Podpięcie certyfikatu w Ingress:
   apiVersion: networking.k8s.io/v1
   kind: Ingress
   metadata:
     name: coffeerecipes-api-ingress
     annotations:
       traefik.ingress.kubernetes.io/router.entrypoints: websecure
       traefik.ingress.kubernetes.io/router.tls: "true"
       cert-manager.io/cluster-issuer: lifelike-cert
   spec:
     tls:
     - hosts:
         - dev-api-recipes.lifelike.cloud
       secretName: lifelike-cloud-tls
     rules:
     - host: dev-api-recipes.lifelike.cloud
       http:
         paths:
         - path: /
           pathType: Prefix
           backend:
             service:
               name: coffeerecipes-api-service
               port:
                 number: 80

Kod na koniec:

Aby podsumować działanie w tej części i nie irytować się, gdzie kod i wszystko

apiVersion: apps/v1
kind: Deployment
metadata:
  name: coffeerecipes-api
  labels:
    app: coffeerecipes-api
spec:
  replicas: 1
  selector:
    matchLabels:
      app: coffeerecipes-api
  template:
    metadata:
      labels:
        app: coffeerecipes-api
    spec:
      containers:
        - name: coffeerecipes-api
          image: aluspl/coffeerecipesapi:develop
          imagePullPolicy: Always
          ports:
            - containerPort: 80
          env:
            - name: ConnectionStrings__Marten
              valueFrom:
                secretKeyRef:
                  name: api-secrets
                  key: ConnectionStrings__Marten
            - name: Media__ConnectionStrings
              valueFrom:
                secretKeyRef:
                  name: api-secrets
                  key: Media__ConnectionStrings
            - name: ASPNETCORE_URLS
              value: "http://*:80"
          livenessProbe:
            httpGet:
              path: /
              port: 80
            initialDelaySeconds: 30
            periodSeconds: 10
          resources:
            limits:
              memory: "1024Mi"
              cpu: "500m"
            requests:
              memory: "512Mi"
              cpu: "250m"

---

# Service dla aplikacji .NET
apiVersion: v1
kind: Service
metadata:
  name: coffeerecipes-api-service
spec:
  selector:
    app: coffeerecipes-api
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
      name: "http"
---

# Ingress dla aplikacji z użyciem Traefik
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: coffeerecipes-api-ingress
  annotations:
    traefik.ingress.kubernetes.io/router.entrypoints: websecure
    traefik.ingress.kubernetes.io/router.tls: "true"
    cert-manager.io/cluster-issuer: lifelike.cloud
spec:
  tls:
    - hosts:
        - dev-api-recipes.lifelike.cloud
      secretName: lifelike-cloud-tls
  rules:
    - host: dev-api-recipes.lifelike.cloud
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: coffeerecipes-api-service
                port:
                  number: 80

Podsumowanie

Mam nadzieję, że wprowadzone zmiany i dodatkowe wskazówki będą pomocne w zabezpieczeniu Waszych homelabów. Dzięki za rady po poprzednim wpisie – dzięki nim udało mi się dopracować ten cykl.

Jak zwykle, zapraszam do spojrzenia, pouczenia – bo ja sam się dopiero uczę.

Źródła

Poniżej źródła, które pomogły mi w tym artykule.

https://nolifelover.medium.com/create-cert-manager-clustterissuer-with-cloudflare-for-automate-issue-and-renew-lets-encrypt-ssl-4877d3f12b44

https://cavecafe.medium.com/homelab-cloudflare-ddns-setup-09b37b54a7fb

https://nolifelover.medium.com/create-cert-manager-clustterissuer-with-cloudflare-for-automate-issue-and-renew-lets-encrypt-ssl-4877d3f12b44

Homelab: Cloudflare i K3S

Domowy Homelab krążył za mną od jakiegoś czasu. Przerażała mnie jednak myśl tego, że mam zmienne IP i inne wydatki. Jednak po odkryciu usług Cloudflare Tunnel, sprawa sporo się ułatwiła.

Setup

Czym jest DDNS ?

DDNS (Dynamic Domain Name System) to usługa, która automatycznie aktualizuje rekordy DNS, gdy zmienia się adres IP urządzenia.

DDNS mam w domu, dzięki użyciu usługi w NAS Synology, ale mimo to jest to usługa dość ograniczona, podobnie jak routing w web platform od Synology. Można było zainstalować Nginx w Dockerze i kierować porty 80 i 443 tam, aby rozwiązywać routing, ale było to uciążliwe. Zakupiłem więc Mini PC Dell Optiflex 7050 z konfiguracją: i5 6gen, 16 GB RAM i 512 SSD, aby zainstalować na nim Proxmox i Kubernetes.

W przyszłości planuję dokupić więcej RAM, aby ten master PC mógł obsługiwać dodatkowe usługi, jak baza danych czy Home Assistant z dodatkami.

Przy okazji, to także sposób na szybszy hosting dedykowanego serwera do gry Satisfactory, który na procesorze z Synology… zaczynał zawodzić.

Proxmox

Tutaj, miałem wiele opcji, bo mogłem zainstalować bezpośrednio Ubuntu i na nim postawić k3s, ale.. Zachęciła mnie myśl o separacji usług na osobne maszyny (kontenery), z myślą np. o Home Assistant w przyszłości. Proxmox, dzięki swoim skryptom, sporo to ułatwia, a hyper-v od MS mam wrażenie, że jest jednak trochę ociężałe i nie ma web panelu.

Proxmox to dystrybucja oparta na Debianie, która oferuje wirtualizację. Dodatkowym atutem jest obsługa przez web panel. Społeczność wokół Proxmox jest bogata, więc jest pełno gotowych skryptów z obrazami i konfiguracjami, np. do szybkiego postawienia Home Assistant z usługami.

Proxmox można pobrać za darmo ze strony: https://www.proxmox.com/en/downloads 

Strona ze skryptami: https://tteck.github.io/Proxmox/

ISO na pendrive wrzuciłem za pomocą Rufus, a następnie w BIOS ustawiłem bootowanie z pendrive i zainstalowałem Proxmox zgodnie z prostym przewodnikiem. Ustawiłem też stałe IP na routerze.

Warto zainstalować skrypt „Proxmox VE Post Install” z powyższej strony.

Warto też dodać, że Jeśli w sytuacjach, gdy posiadacie nas, to śmiało możecie podłączyć do każdej instancji dysk sieciowy po SMB czy NFS. Warto tylko dodać, że do LXC, musicie odznaczyć Typ instalacji, jako unprivileged, przy tworzeniu, bo inaczej nie będziecie nawet mieli opcji ustawienia tego potem. Więcej o tym napiszę w późniejszych wpisach.

Konfiguracja LXT

Update:

Z racji, że nie zawsze k3s potrafi poprawnie zainstalować, w linku poniżej mały poradnik, jak poprawnie ustawić host w proxmox. Postaram się to dodatkowo dodać tutaj.

https://betterprogramming.pub/rancher-k3s-kubernetes-on-proxmox-containers-2228100e2d13

Kubernetes

Ostatnio coraz częściej pracuję z Kubernetesem w firmie. Na początku wydawał się, bardziej skomplikowany niż trzeba, głównie przez pliki konfiguracyjne, które są trudniejsze niż np. Docker Compose.

Swojego master node stworzyłem na podstawie Debian LXC, który jest lżejszy i bardziej wydajny niż VM. Kontener ma 14 GB RAM i 4 vCPU, ponieważ obsługuje także dedykowany serwer do gry Satisfactory. W przyszłości będzie można dodać kolejne węzły, np. Raspberry Pi 4+.

Zainstalowałem K3s na Debian LXC: [https://k3s.io/](https://k3s.io/), co było bardzo proste. Aktualne dokumenty instalacyjne można znaleźć tutaj: https://docs.k3s.io/quick-start

Dla łatwiejszego dostępu do konfiguracji klastra:

cp /etc/rancher/k3s/k3s.yaml ~/.kube/config

lub

export KUBECONFIG=/etc/rancher/k3s/k3s.yaml

Polecam także narzędzie K9s, które znacznie ułatwia zarządzanie Kubernetes. Instrukcje instalacji znajdziesz tutaj: https://k9scli.io

Certyfikat

Kupiłem domenę lifelike.cloud, aby podpiąć certyfikat. Na potrzeby tego artykułu tworzę self-signed certificate, ponieważ ostateczny certyfikat będzie dostarczony przez Cloudflare Tunnel.

Tworzenie klucza prywatnego:

openssl genrsa -out wildcard.key 2048

Tworzenie CSR:

openssl req -new -key wildcard.key -subj "/CN=*.lifelike.cloud" -out wildcard.csr

Tworzenie certyfikatu ważnego rok:

openssl x509 -req -in wildcard.csr -signkey wildcard.key -out wildcard.crt -days 365

Podpinanie certyfikatu jako sekret w Kubernetes:

kubectl create secret tls tls-cert --cert=wildcard.crt --key=wildcard.key

Serwisy

Stawiam API dostępne w open source na moim GitHubie: https://github.com/aluspl/CoffeeRecipesApi/tree/develop.

Obraz aplikacji jest hostowany w Docker Hub jako:

aluspl/coffeerecipesapi:develop

Najpierw łączymy się z master node i tworzymy plik `api.yml`:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: coffeerecipes-api
  labels:
    app: coffeerecipes-api
spec:
  replicas: 1
  selector:
    matchLabels:
      app: coffeerecipes-api
  template:
    metadata:
      labels:
        app: coffeerecipes-api
    spec:
      containers:
        - name: coffeerecipes-api
          image: aluspl/coffeerecipesapi:develop
          imagePullPolicy: Always
          ports:
            - containerPort: 80
          env:
            - name: ConnectionStrings__Marten
              value: "Host=ip;Port=6432;Database=coffeerecipes;Username=postgres;Password=password#;"
            - name: Media__ConnectionStrings
              value: "blobStorageConnectionString"
            - name: ASPNETCORE_URLS
              value: "http://*:80"
          livenessProbe:
            httpGet:
              path: /
              port: 80
            initialDelaySeconds: 30
            periodSeconds: 10
          resources:
            limits:
              memory: "2024Mi"
              cpu: "500m"
            requests:
              memory: "1024Mi"
              cpu: "500m"

---

# Service dla aplikacji .NET
apiVersion: v1
kind: Service
metadata:
  name: coffeerecipes-api-service
spec:
  selector:
    app: coffeerecipes-api
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
      name: "http"
---

# Ingress dla aplikacji z użyciem Traefik
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: coffeerecipes-api-ingress
  annotations:
    traefik.ingress.kubernetes.io/router.entrypoints: websecure
    traefik.ingress.kubernetes.io/router.tls: "true"
spec:
  tls:
    - hosts:
        - dev-api-recipes.lifelike.cloud
      secretName: lifelike-cloud-tls
  rules:
    - host: dev-api-recipes.lifelike.cloud
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: coffeerecipes-api-service
                port:
                  number: 80

Następnie aplikujemy konfigurację:

kubectl apply -f api.yaml

Cloudflare Tunnel

Cloudflare Tunnel pozwala na udostępnienie serwisów na zewnątrz bez otwierania portów na routerze. Chroni także przed atakami DDoS. Ustawiłem domenę na Cloudflare, aby zarządzać rekordami DNS.

Link do dokumentacji Cloudflare Tunnel: https://developers.cloudflare.com/cloudflare-one/tutorials/many-cfd-one-tunnel.

Przykład konfiguracji:

apiVersion: v1
kind: ConfigMap
metadata:
  name: cloudflared
data:
  config.yaml: |
    tunnel: lifelikecloudtunnel
    credentials-file: /etc/cloudflared/creds/credentials.json
    ingress:
    - hostname: dev-api-recipes.lifelike.cloud
      service: http://coffeerecipes-api-service:80

Teraz aplikujemy konfigurację:

kubectl apply -f cloudflare.yaml

Podsumowanie

Temat Homelab może wydawać się skomplikowany, ale po zrozumieniu podstaw, wszystko staje się łatwiejsze. Zachęcam do eksperymentowania i dalszej zabawy z technologiami!

Przy okazji, w kolejnej części, opowiem więcej o przejściu z ręcznie tworzonego certyfikatu, do automatycznie odnawianego let’s encrypt oraz przeniesieniu zmiennych typu connection stringi (do bazy czy blob storage) do kubernetesowych secretów.

p.s Dzięki Łukasz i Teodor za merytoryczne sprawdzenie artykułu.

Azure: Blob Storage

Hej Wam. Trzeba trochę odkurzyć projekt, a z racji, że w chwili pisania tego artykułu wylądowałem na ławce, postanowiłem się podszkolić z wiedzy, która przyda się każdemu. Mi się przydała do mojego projektu.
Nie jest też to droga usługa, przynajmniej na potrzebę usług takich jak galeria na stronie.
W tej części nie tylko opowiem Wam na temat założenia Storage Account, ale także jak obsłużyć “Bloby”.
Przy okazji, to już 2 lata od regularnego pisania w ramach DSP. Dzisiaj nie dałbym radę pisać 2x tygodniowo wpisów jak wtedy.

Czytaj dalej „Azure: Blob Storage”

.Net Core – DEV OPS for Dummies

Dev Ops for DUmmies

Jesli tu jesteś, to wiedz, że ten artykuł potrzebuje lekkiego update. Net Core poszło o 6 wersji do przodu, a docker-compose dalej ten sam 🙂

Miało być więcej o Angularze, ale ostatnie wygaśnięcie mojego rocznego darmowego planu na Azure oraz całkiem fajny pakiet webowy z kredytami na digitalocean (DigitalOcean: Cloud Computing, Simplicity at Scale) , który był na Humble Bundle… zmotywował mnie, aby trochę zabawić się w DEV OPS.
O instalacji dockerów na ubuntu wspominałem już wcześniej, ale przypomnę trochę tu o nim z miłym dodatkiem w postaci docker-compose. Do tego, co nieco o zabezpieczeniu za pomocą certyfikatu. Kod dostępny jak zawsze na github. Co ważniejsze, zmotywowało mnie to do pracy nad własnymi umiejętnościami oraz pogodzenie się z Ubuntu (Gdyż na Windows Home dockery nie działają, a SSH jest ubogie).

Czytaj dalej „.Net Core – DEV OPS for Dummies”

Własna Firma – Od Zera do Przedsiębiorcy

Własna Firma

Hej. Dzisiaj trochę zarządzania, czyli własna firma. Mija właśnie 5 miesięcy, odkąd prowadzę własną działalność gospodarczą. Opiszę w tym miejscu, dlaczego to się opłaca i nie jest tak straszne, jakby się mogło Wam wydawać. Wpis będzie dotyczyć działalności jednoosobowej. Wpis powstał na doświadczeniach  z moją firmą, warto skontaktować się z księgowością po więcej pytań

Czytaj dalej „Własna Firma – Od Zera do Przedsiębiorcy”