commit b826035b3af8c4bdf535634eaf7bda591df2494d Author: morbalint Date: Tue Jan 9 17:39:03 2024 +0100 init: basic k8s deployment on DO diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..04199ac --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.pdf +local/ \ No newline at end of file diff --git a/1password-connect-server/1password-secret-store.yaml b/1password-connect-server/1password-secret-store.yaml new file mode 100644 index 0000000..380ac2f --- /dev/null +++ b/1password-connect-server/1password-secret-store.yaml @@ -0,0 +1,16 @@ +apiVersion: external-secrets.io/v1beta1 +kind: SecretStore +metadata: + name: kemkas-secret-store + namespace: kemkas +spec: + provider: + onepassword: + connectHost: https://1password-connect.dev.kemkas.hu + vaults: + kemkas: 1 # look in this vault first + auth: + secretRef: + connectTokenSecretRef: + name: onepassword-connect-token + key: token \ No newline at end of file diff --git a/1password-connect-server/connect-server-access-token-secret.yaml b/1password-connect-server/connect-server-access-token-secret.yaml new file mode 100644 index 0000000..c39af9d --- /dev/null +++ b/1password-connect-server/connect-server-access-token-secret.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Secret +metadata: + name: onepassword-connect-token + namespace: kemkas +type: Opaque +stringData: + token: \ No newline at end of file diff --git a/1password-connect-server/connect-server.conf b/1password-connect-server/connect-server.conf new file mode 100644 index 0000000..915adc2 --- /dev/null +++ b/1password-connect-server/connect-server.conf @@ -0,0 +1,14 @@ +server { + listen 80; + server_name 1password-connect.dev.kemkas.hu; + + location / { + proxy_pass_request_headers on; + proxy_pass http://localhost:8080; + } + + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } +} diff --git a/1password-connect-server/docker-compose.yaml b/1password-connect-server/docker-compose.yaml new file mode 100644 index 0000000..a453311 --- /dev/null +++ b/1password-connect-server/docker-compose.yaml @@ -0,0 +1,20 @@ +version: "3.4" + +services: + op-connect-api: + image: 1password/connect-api:latest + ports: + - "8080:8080" + volumes: + - "./1password-credentials.json:/home/opuser/.op/1password-credentials.json" + - "data:/home/opuser/.op/data" + op-connect-sync: + image: 1password/connect-sync:latest + ports: + - "8081:8080" + volumes: + - "./1password-credentials.json:/home/opuser/.op/1password-credentials.json" + - "data:/home/opuser/.op/data" + +volumes: + data: diff --git a/README.md b/README.md new file mode 100644 index 0000000..7eeaec2 --- /dev/null +++ b/README.md @@ -0,0 +1,79 @@ +# Kemkas k8s Deployment + +## ToC +- [Argo CD](#argo-cd) +- [1password ESO connect server](#1password-connect-server) +- [Kemkas](#kemkas) + +## Argo CD + +Goal: continous deployment automation with rollback capability + +Files: +- [argocd-ingress.yml](./argocd/argocd-ingress.yaml) for connecting the service with the load balancer and nginx reverse proxy +- [argocd-values.yml](./argocd/argocd-values.yaml) config for the argocd helm chart to use SSL termination at NGINX. +- [cert-manager-issuer.yaml](./argocd/cert-manager-issuer.yaml) SSL certificate config for the argocd.kemkas.hu domain + +TODO: +- [ ] gRPC setup for ArgoCD to use the CLI. Workaround: k8s port proxy to localhost. + +Setup instructions + +1. DO 1-click install argoCD into k8s cluster (TODO: replace with helm install command, I suspect behind the scenes the 1-click install does the same) +1. DO 1-click install ingress-nginx into k8s cluster (TODO: replace with helm install command, I suspect behind the scenes the 1-click install does the same) +1. DO 1-click install cert-manager into k8s cluster (TODO: replace with helm install command, I suspect behind the scenes the 1-click install does the same) +1. Create DNS A record for argocd.kemkas.hu pointing to the Load Balancer IP address (Load balancer is a DO object created with the ingress-nginx helm chart install) +1. helm update argocd with `argocd-values.yaml` +1. Apply all files in `argocd` directory + ``` + kubectl apply -f cert-manager-issuer.yaml + kubectl apply -f argocd-ingress.yaml + ``` + +## 1Password Connect Server + +Goal: Store secrets in 1password instead of kubernetes, so we can push all kubernetes config while keeping the secrets safe in 1password + +Files: +- [1password-secret-store.yaml](./1password-connect-server/1password-secret-store.yaml): kuberentes ESO Secret Store to be referenced by external secrets +- [connect-server-access-token-secret.yaml](./1password-connect-server/connect-server-access-token-secret.yaml): kubernetes secret file to connect with the 1password connect server without the actual secret token. the token can be generated on 1password website or CLI. +- [docker-compose.yaml](./1password-connect-server/docker-compose.yaml): docker compose config to run the 1password connect server on a separate machine. Requires a `1password-credentials.json` file locally in the same directory, which can be obtained from the 1password website or CLI. +- [connect-server.conf](./1password-connect-server/connect-server.conf) nginx config for the SSL terminating proxy for the 1password connect server (so that secrets don't travel unencrypted) + +TODO: +- [ ] explore running the 1password connect server on the same k8s cluster to simplify infrastructure +- [ ] write script to automate the setup instraction (using `doctl` for full infra automation) + +Setup: +1. create DO droplet (with docker template) +1. create DNS A record pointing to the droplet +1. install nginx, certbot `sudo apt install nginx certbot python3-certbot-nginx` +1. copy `docker-compose.yaml` and `1password-credentials.json` to the droplet and run `docker compose up -d` +1. copy connect-server nginx config to `/etc/nginx/sites-available` and link it to sites enabled `ln -s /etc/nginx/sites-available/connect-server.conf /etc/nginx/sites-enabled/` +1. test nginx config `nginx -t` +1. reload nginx config `nginx -s reload` +1. create SSL certificate with certbot `certbot --nginx -d 1password-connect.dev.kemkas.hu` (email address and accepting terms is required!) +1. setup firewall to block TCP/8080 and TCP/8081 to avoid unencrypted access to the connect server (allow TCP/443 and TCP/22) +1. helm install the external secret operator `helm repo add external-secrets https://charts.external-secrets.io` and `helm install external-secrets external-secrets/external-secrets -n external-secrets --create-namespace --set installCRDs=false` (from [https://external-secrets.io/](https://external-secrets.io/)) +1. create namespace for secret store `kubectl create ns kemkas` +1. create connect server access token secret in k8s `kubectl apply -f ./1password-connect-server/connect-server-access-token-secret.yaml` +1. create secret store `kubectl apply -f ./1password-connect-server/1password-secret-store.yaml` + +## Kemkas + +Goal: deploy an existing docker image into the k8s cluster with proper secrets and expose it on HTTPS with proper SSL cert + +Files: +- [kemkas-external-secrets.yaml](./kemkas/kemkas-external-secrets.yaml): syncs external secrets from the 1password secret store +- [kemkas-deployment.yaml](./kemkas/kemkas-deployment.yaml): configures the kemkas server docker image to run on the cluster +- [kemkas-service.yaml](./kemkas/kemkas-service.yaml): exposes the kemkas server deployment to the cluster +- [kemkas-ssl-cert-issuer.yaml](./kemkas/kemkas-ssl-cert-issuer.yaml): config for issuing SSL certs for accessing the kemkas server on the internet +- [kemkas-ingress.yaml](./kemkas/kemkas-ingress.yaml): exposes the kemkas server on the internet through the nginx ingress proxy + +Setup: +1. connect Digital Ocean Container Registry to the k8s cluster `doctl registry kubernetes-manifest | kubectl apply -f -` +1. create external secrets for the upcoming kemkas deployment `kubectl apply -f ./kemkas/kemkas-external-secrets.yaml` +1. create kemkas deployment `kubectl apply -f ./kemkas/kemkas-deployment.yaml` +1. create service to export the kemkas deployment `kubectl apply -f ./kemkas/kemkas-service.yaml` +1. create SSL cert issuer `kubectl apply -f ./kemkas/kemkas-ssl-cert-issuer.yaml` +1. create ingress for the kemkas service `kubectl apply -f ./kemkas/kemkas-ingress.yaml` diff --git a/argocd/argocd-ingress.yaml b/argocd/argocd-ingress.yaml new file mode 100644 index 0000000..5e3be0b --- /dev/null +++ b/argocd/argocd-ingress.yaml @@ -0,0 +1,26 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: ingress-argocd + namespace: argocd + annotations: + nginx.ingress.kubernetes.io/force-ssl-redirect: "true" + nginx.ingress.kubernetes.io/backend-protocol: "HTTP" + cert-manager.io/issuer: letsencrypt-argocd +spec: + ingressClassName: nginx + rules: + - http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: argocd-server + port: + name: http + host: argocd.kemkas.hu + tls: + - hosts: + - argocd.kemkas.hu + secretName: letsencrypt-argocd diff --git a/argocd/argocd-values.yaml b/argocd/argocd-values.yaml new file mode 100644 index 0000000..7aa2879 --- /dev/null +++ b/argocd/argocd-values.yaml @@ -0,0 +1,5 @@ +server: + ingress: + enabled: "true" + extraArgs: + - "--insecure" \ No newline at end of file diff --git a/argocd/cert-manager-issuer.yaml b/argocd/cert-manager-issuer.yaml new file mode 100644 index 0000000..2d6de7e --- /dev/null +++ b/argocd/cert-manager-issuer.yaml @@ -0,0 +1,20 @@ +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: letsencrypt-argocd + namespace: argocd +spec: + # ACME issuer configuration + # `email` - the email address to be associated with the ACME account (make sure it's a valid one) + # `server` - the URL used to access the ACME server’s directory endpoint + # `privateKeySecretRef` - Kubernetes Secret to store the automatically generated ACME account private key + acme: + email: developer@kemkas.hu + server: https://acme-v02.api.letsencrypt.org/directory + privateKeySecretRef: + name: letsencrypt-argocd-private-key + solvers: + # Use the HTTP-01 challenge provider + - http01: + ingress: + class: nginx diff --git a/ingress-nginx/values.yaml b/ingress-nginx/values.yaml new file mode 100644 index 0000000..428371b --- /dev/null +++ b/ingress-nginx/values.yaml @@ -0,0 +1,18 @@ + +controller: + replicaCount: 1 #reduced to 1 to save costs + # resources: + # requests: + # cpu: 0 + # memory: 90Mi + service: + type: LoadBalancer + annotations: + # Enable proxy protocol + service.beta.kubernetes.io/do-loadbalancer-enable-proxy-protocol: "true" + # Specify whether the DigitalOcean Load Balancer should pass encrypted data to backend droplets + service.beta.kubernetes.io/do-loadbalancer-tls-passthrough: "true" + + # Will add custom configuration options to Nginx https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/ + config: + use-proxy-protocol: "true" \ No newline at end of file diff --git a/kemkas/kemkas-deployment.yaml b/kemkas/kemkas-deployment.yaml new file mode 100644 index 0000000..d5f92a3 --- /dev/null +++ b/kemkas/kemkas-deployment.yaml @@ -0,0 +1,77 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: kemkas + namespace: kemkas + labels: + app: kemkas +spec: + replicas: 1 + selector: + matchLabels: + app: kemkas + template: + metadata: + name: kemkas + labels: + app: kemkas + spec: + containers: + - name: kemkas + image: registry.digitalocean.com/kemkas/kemkas:45914c78 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 8080 + env: + - name: Email__DomainName + value: mail.kemkas.hu + - name: Email__ApiKey + valueFrom: + secretKeyRef: + name: kemkas-envs + key: email--api-key + - name: ConnectionStrings__DefaultConnection + valueFrom: + secretKeyRef: + name: kemkas-envs + key: connection-strings--default-connection + - name: Authentication__Google__ClientId + valueFrom: + secretKeyRef: + name: kemkas-envs + key: authentication--google--client-id + - name: Authentication__Google__ClientSecret + valueFrom: + secretKeyRef: + name: kemkas-envs + key: authentication--google--client-secret + - name: Authentication__Facebook__ClientId + valueFrom: + secretKeyRef: + name: kemkas-envs + key: authentication--facebook--client-id + - name: Authentication__Facebook__ClientSecret + valueFrom: + secretKeyRef: + name: kemkas-envs + key: authentication--facebook--client-secret + - name: Authentication__Discord__ClientId + valueFrom: + secretKeyRef: + name: kemkas-envs + key: authentication--discord--client-id + - name: Authentication__Discord__ClientSecret + valueFrom: + secretKeyRef: + name: kemkas-envs + key: authentication--discord--client-secret + resources: + requests: + cpu: 100m + memory: 100M + limits: + cpu: 1000m + memory: 500M + + restartPolicy: Always + \ No newline at end of file diff --git a/kemkas/kemkas-external-secrets.yaml b/kemkas/kemkas-external-secrets.yaml new file mode 100644 index 0000000..ee58de2 --- /dev/null +++ b/kemkas/kemkas-external-secrets.yaml @@ -0,0 +1,16 @@ +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: kemkas-external-secrets + namespace: kemkas +spec: + refreshInterval: 1h + secretStoreRef: + name: kemkas-secret-store + kind: SecretStore + target: + name: kemkas-envs + creationPolicy: Owner + dataFrom: + - extract: + key: kemkas-app diff --git a/kemkas/kemkas-ingress.yaml b/kemkas/kemkas-ingress.yaml new file mode 100644 index 0000000..9852d28 --- /dev/null +++ b/kemkas/kemkas-ingress.yaml @@ -0,0 +1,26 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: ingress-kemkas + namespace: kemkas + annotations: + # nginx.ingress.kubernetes.io/force-ssl-redirect: "true" + # nginx.ingress.kubernetes.io/backend-protocol: "HTTP" + cert-manager.io/issuer: letsencrypt-kemkas +spec: + ingressClassName: nginx + rules: + - http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: kemkas-service + port: + number: 8080 + host: dev.kemkas.hu + tls: + - hosts: + - dev.kemkas.hu + secretName: letsencrypt-kemkas diff --git a/kemkas/kemkas-service.yaml b/kemkas/kemkas-service.yaml new file mode 100644 index 0000000..1a35ee0 --- /dev/null +++ b/kemkas/kemkas-service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: kemkas-service + namespace: kemkas + labels: + app: kemkas +spec: + selector: + app: kemkas + type: ClusterIP + ports: + - protocol: TCP + port: 8080 + targetPort: 8080 diff --git a/kemkas/kemkas-ssl-cert-issuer.yaml b/kemkas/kemkas-ssl-cert-issuer.yaml new file mode 100644 index 0000000..428d339 --- /dev/null +++ b/kemkas/kemkas-ssl-cert-issuer.yaml @@ -0,0 +1,20 @@ +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: letsencrypt-kemkas + namespace: kemkas +spec: + # ACME issuer configuration + # `email` - the email address to be associated with the ACME account (make sure it's a valid one) + # `server` - the URL used to access the ACME server’s directory endpoint + # `privateKeySecretRef` - Kubernetes Secret to store the automatically generated ACME account private key + acme: + email: developer@kemkas.hu + server: https://acme-v02.api.letsencrypt.org/directory + privateKeySecretRef: + name: letsencrypt-kemkas-private-key + solvers: + # Use the HTTP-01 challenge provider + - http01: + ingress: + class: nginx