DEV Community

Tingwei
Tingwei

Posted on

Hands-on Guide to Sealed Secrets: Deploying .NET 6 Apps Securely in KIND

Repository

Objective

This guide demonstrates encrypting Kubernetes secrets with Sealed Secrets, using a PostgreSQL connection string as an example, and setting up a local Traefik TLS secret with mkcert.

Prerequisites

Installing Sealed Secrets

  • Sealed Secrets controller (Cluster-Side Controller)
# by Helm
helm repo add sealed-secrets \
https://bitnami-labs.github.io/sealed-secrets

helm install sealed-secrets -n kube-system  \
--set-string fullnameOverride=sealed-secrets-controller \
sealed-secrets/sealed-secrets
Enter fullscreen mode Exit fullscreen mode
  • kubeseal (client-side utility)
go install \
github.com/bitnami-labs/sealed-secrets/cmd/kubeseal@main
Enter fullscreen mode Exit fullscreen mode

Creating the sealed secret with Sealed Secrets

  • Create the Kubernetes secret
kubectl create secret generic ms-dev-secret \
 --dry-run=client -o yaml > appsettings-secret.Development.yaml \
 --from-literal=pg-cluster='Host=your_host_ip;Port=5432;Database=your_database;Username=your_username;Password=your_password'
Enter fullscreen mode Exit fullscreen mode

pg-cluster is the name of the key in the secret.
ms-dev-secret is the name of the secret.

  • Create the sealed secret from the secret
kubeseal --scope cluster-wide \
< appsettings-secret.Development.yaml -o yaml  \
> appsettings-sealed-secret.Development.yaml
Enter fullscreen mode Exit fullscreen mode

appsettings-sealed-secret.Development.yaml is the sealed secret.
--scope

  • How to decrypt the sealed secret to the secret
# First, find the sealed secret controller
# Running kubectl get secrets -A will show secrets starting with sealed-secrets-
# For example, sealed-secrets-abc 

kubectl get secrets -A

# Secondly, get the private key "sealed-secret-key.pem"
kubectl get secret -n kube-system sealed-secrets-abc \
 -o jsonpath='{.data.tls\.key}' | base64 --decode \
 > sealed-secret-key.pem

# Finally, decrypt the sealed secret "appsettings-sealed-secret.Development.yaml"
# to the secret "appsettings-secret.Development.yaml"
kubeseal --recovery-unseal \
--recovery-private-key sealed-secret-key.pem  \
< appsettings-sealed-secret.Development.yaml  \
-o yaml > appsettings-secret.Development.yaml

# memo: decode base64 string
echo [base64_string] | base64 --decode

Enter fullscreen mode Exit fullscreen mode

Running cloud-provider-kind

# Run As administrator
cloud-provider-kind
Enter fullscreen mode Exit fullscreen mode

Setting up mkcert

  • Install the cert/key
# on Windows, I used choco
choco install mkcert
mkcert -install
mkcert localhost 127.0.0.1
Enter fullscreen mode Exit fullscreen mode

localhost.pem is the mkcert cert
localhost-key.pem is the mkcert key

Creating the Kubernetes tls secret for Traefik

kubectl create secret tls localhost-tls-secret \
    --cert=localhost.pem \
    --key=localhost-key.pem \
    --namespace=default
Enter fullscreen mode Exit fullscreen mode

localhost-tls-secret is the tls secret for Traefik

  • Remove mkcert cert/key
mkcert -uninstall
rm -rf "$(mkcert -CAROOT)"
Enter fullscreen mode Exit fullscreen mode

Configuring Traefik with TLS

  • Deploy Traefik
helm install -f traefik-values-localhost.yaml \
 traefik traefik/traefik
Enter fullscreen mode Exit fullscreen mode
  • Update localhost tls from external ip of Traefik
mkcert localhost 127.0.0.1 172.19.0.6
Enter fullscreen mode Exit fullscreen mode

172.19.0.6 is the Traefik external ip given by cloud-provider-kind
remember to rename new key and new cert as "localhost.pem" and "localhost-key.pem"

  • traefik-values-localhost.yaml
ports:
  web:
    redirectTo:
      port: websecure
tlsStore:
  default:
    defaultCertificate:
      secretName: localhost-tls-secret
Enter fullscreen mode Exit fullscreen mode

Deploying the .NET 6 App with Sealed Secrets

# deploy sealed secret first
kubectl apply -f appsettings-sealed-secret.Development.yaml

# deploy the app
kubectl apply -f deploy.yaml
Enter fullscreen mode Exit fullscreen mode
  • appsettings-sealed-secret.Development.yaml
---
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  annotations:
    sealedsecrets.bitnami.com/cluster-wide: "true"
  creationTimestamp: null
  name: ms-dev-secret
  namespace: default
spec:
  encryptedData:
    pg-cluster: AgAjyf765/UaOa80g1Z+30zEQ4Sgu5uLU+9EwKEQ5yiZC+8lczxJERGpq1oMmzdL2jLe9sYT7jREGynPDcczX8Y5tU+ZH4ADiuUgbgjpOCrfOXT68AF2LEMu6JFhu3bqMhLbYL1yUMTxQmf6YP/kvPxZ3kTyWaiQfOuTTwgKwGRT50mOrbYJaMMJxH6HMe4V8kPQ6qbpg9FSU1S6DzHCI+pf0HKg8cuCLUjxOXNL7Bc7X+PqGG3nP/BlF4WVaGvYpVCxC/4IakPZIwSI5mUWRpSabFl1f+rP2OmuEuChm3l9XzYTZyXUAu8f9c/jR4FmnK4DVv0MOMeVhsSFf/eS64l2HC6/evKscL2AIvw2HWX/bpTJeeNfA0neSqNYn4zMMTs2nSFsQTh0Qd5XE/zdprYY74B6UlbcNZHiOAeKiHlo8bL44ulCcZBTWviBejd7dB/ot07l0lfo0aa1F2O/WKT6/gdGNnxULx7weZbwDKjb970OmtJpmS4HKiwf3vnlOoF6tGsPzd7Bj3mXEFnMPFKdnZ/nwsKc8+lVRFBQ5CaWZziXD5T5uOQB/lSUFoY7miCpGEM49INUbfPpZKZd4aW1ZPFJ1bszlkBL2F7gkl3i+vVtqFHqv3DIF5RG/Vk537dppLEsjA3qK0vGjJJzYTCBppyij86PRcI7EjGeDd63YTuHP59U/RKOfCfLdEntuABjWN124N3rpmQB60Tdr84AWVmQG8SUD1dBSii6cGtqNVN8iLqZqAn1cfGQhV0nG56Ciyd+/hTAVIxTAGqvKMXO/OJWTuzZ9X8p0Ry5WgYDWMgwV+Du7gUJ6Bb+kq2TTTOXGKiUXKFu3wl0vj2WpBmZiYV10DNb9k2jlSVx6JA=
  template:
    metadata:
      annotations:
        sealedsecrets.bitnami.com/cluster-wide: "true"
      creationTimestamp: null
      name: ms-dev-secret
      namespace: default
Enter fullscreen mode Exit fullscreen mode
  • deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ms
  labels:
    app: ms
spec:
  replicas: 1
  selector:
    matchLabels:
      app: ms
  template:
    metadata:
      labels:
        app: ms
    spec:
      containers:
      - name: ms-app
        image: ghcr.io/tingwei628/ms/ms:latest
        ports:
        - containerPort: 10001
        env:
        - name: DOTNET_ENVIRONMENT
          value: Development
        - name: ConnectionStrings__PgCluster
          valueFrom:
            secretKeyRef:
              name: ms-dev-secret
              key: pg-cluster
        - name: ASPNETCORE_URLS
          value: http://+:10001
        livenessProbe:
          httpGet:
            path: /health
            port: 10001
          initialDelaySeconds: 10
          periodSeconds: 5
        readinessProbe:
          httpGet:
            path: /health
            port: 10001
          initialDelaySeconds: 5
          periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
  name: ms-service
  labels:
    app: ms
spec:
  selector:
    app: ms
  ports:
    - port: 10001
      targetPort: 10001
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: ms-ingress
  namespace: default
  labels:
    app: ms
spec:
  entryPoints:
    - websecure
  routes:
    - match: Host(`172.19.0.6`) && PathPrefix(`/ms`)
      kind: Rule
      middlewares:
        - name: strip-ms-prefix
      services:
        - name: ms-service
          port: 10001
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: strip-ms-prefix
  labels:
    app: ms
spec:
  stripPrefix:
    prefixes:
      - /ms
Enter fullscreen mode Exit fullscreen mode

port = 10001 (whatever you want)
ConnectionStrings__PgCluster is from the key "pg-cluster" in the secret "ms-dev-secret"

Testing

ms swagger

Done!

Top comments (0)