Code it Yourself…

A blog on Microsoft Azure and .NET by Carlos Mendible

Reading Kubernetes Secrets with Dapr and .NET Core

Dapr is an event-driven, portable runtime for building microservices on cloud and edge.

Dapr supports the fundamental features you’ll need such as: service invocation, state management, publish/subscribe messaging and since version 0.5.0 the ability to read from secret stores!

This post will show you to read kubernetes secrets using Dapr and .NET Core:

Prerequistes

Create an .NET Core project and add dependencies

Open the command line and type:

dotnet new web -o dapr.k8s.secrets

cd dapr.k8s.secrets

dotnet add package Dapr.Client -v 0.5.0-preview02

Update Startup.cs

Update Startup.cs with the following contents in order to expose a secret endpoint and use Dapr to fetch a kubernetes secret:

namespace dapr.k8s.secrets
{
    using System.Text.Json;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.AspNetCore.Http;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.Hosting;
    using Microsoft.Extensions.DependencyInjection;
    using Dapr.Client;
    using System;
    using System.Collections.Generic;

    public class Startup
    {
        // Dapr listens for requets on localhost
        const string localhost = "127.0.0.1";

        // Get the Dapr gRPC port
        static string daprPort => Environment.GetEnvironmentVariable("DAPR_GRPC_PORT") ?? "50001";

        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            // Create Dapr Client
            var client = new DaprClientBuilder()
                .UseEndpoint($"https://{localhost}:{daprPort}")
                .Build();

            // Add the DaprClient to DI.
            services.AddSingleton<DaprClient>(client);
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env, DaprClient client)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseRouting();

            app.UseEndpoints(endpoints =>
            {
                // Add the secrets route
                endpoints.MapGet("secret", Secret);
            });

            async Task Secret(HttpContext context)
            {
                // Get the secret from kubernetes
                var secretValues = await client.GetSecretAsync(
                    "kubernetes", // Name of the Dapr Secret Store
                    "super-secret", // Name of the k8s secret
                    new Dictionary<string, string>() { { "namespace", "default" } }); // Namespace where the k8s secret is deployed

                // Get the secret value
                var secretValue = secretValues["super-secret"];

                context.Response.ContentType = "application/json";
                await JsonSerializer.SerializeAsync(context.Response.Body, secretValue);
            }
        }
    }
}

Create a Dockerfile with the following contents:

FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS builder
WORKDIR /app

# caches restore result by copying csproj file separately
COPY *.csproj .
RUN dotnet restore

COPY . .
RUN dotnet publish --output /app/ --configuration Release
RUN sed -n 's:.*<AssemblyName>\(.*\)</AssemblyName>.*:\1:p' *.csproj > __assemblyname
RUN if [ ! -s __assemblyname ]; then filename=$(ls *.csproj); echo ${filename%.*} > __assemblyname; fi

# Stage 2
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1
WORKDIR /app
COPY --from=builder /app .

ENV PORT 80
EXPOSE 80

ENTRYPOINT dotnet $(cat /app/__assemblyname).dll

Build the Docker image and push it to a container registry

I’ll be pushing the container to Docker Hub:

 docker build -t cmendibl3/dapr-k8s-secrets:1.0.0 .
 docker push cmendibl3/dapr-k8s-secrets:1.0.0

Create manifest and deploy the application to kubernetes

Create a deployment.yaml with the following contents (remember to replace the image with your own values):

---
# ASP.NET Core Application
apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: dapr-k8s-secrets
  namespace: default
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: dapr-k8s-secrets
        aadpodidbinding: requires-vault
      annotations:
        dapr.io/enabled: "true"
        dapr.io/id: "dapr-k8s-secrets"
        dapr.io/port: "80"
    spec:
      containers:
        - name: dapr-k8s-secrets
          image: cmendibl3/dapr-k8s-secrets:1.0.0
          ports:
            - containerPort: 80
          imagePullPolicy: Always
---
# Create a Kubernetes Secret
apiVersion: v1
kind: Secret
metadata:
  name: super-secret
  namespace: default
type: Opaque
data:
  super-secret: eW91ciBzdXBlciBzZWNyZXQK

---
# If RBAC is enabled in K8s, give the default service account access to read secrets in the default namespace
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: dapr-secret-reader
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: secret-reader
subjects:
  - kind: ServiceAccount
    name: default
    namespace: default

If you haven’t installed Dapr in your kuberntes cluster run:

dapr init --kubernetes

Now deploy the application to kubernetes:

kubectl apply -f ./deployment.yaml

Test the application

Get the pod name and execute a port forward to test the API

$pod = kubectl get po --selector=app=dapr-k8s-secrets -n default -o jsonpath='{.items[*].metadata.name}'
kubectl port-forward $pod 80:80

Run the following command in other terminal:

curl http://localhost/secret

If everything is working you should read:

"your super secret"

Hope it helps!

Please find all code and files here, and learn more about Dapr and the Secret API here