DEV Community

Tingwei
Tingwei

Posted on

1

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!

👋 While you are here

Reinvent your career. Join DEV.

It takes one minute and is worth it for your career.

Get started

Top comments (0)

👋 Kindness is contagious

Discover a treasure trove of wisdom within this insightful piece, highly respected in the nurturing DEV Community enviroment. Developers, whether novice or expert, are encouraged to participate and add to our shared knowledge basin.

A simple "thank you" can illuminate someone's day. Express your appreciation in the comments section!

On DEV, sharing ideas smoothens our journey and strengthens our community ties. Learn something useful? Offering a quick thanks to the author is deeply appreciated.

Okay