Integrate Pulumi with Secret Store CSI Driver in Kubernetes
Container Storage Interface (CSI)
The Container Storage Interface (CSI) is a standard to unify the interface between container orchestrators (like Kubernetes) and different storage vendors (like NetApp, Ceph, etc.). This helps to guarantee that implementing a CSI for a storage vendor is going to work with all orchestrators that support CSI.
Combining CSI, Kubernetes and Secrets = Secret Store CSI Driver
The Kubernetes Secret Store CSI Driver allows you to mount secrets, certificates, and keys from external secret stores into Kubernetes pods as volumes. After attaching the volume, the system mounts the secrets into the container file system.
The benefits of using the Secret Store CSI Driver are that you manage the lifecycle of the secrets outside of Kubernetes while still providing a Kubernetes-native experience of using the secrets in your pods.
In this tutorial, you'll learn:
- How to deploy the Secret Store CSI Driver and Pulumi ESC Provider using Helm or Pulumi
- How to mount secrets stored in Pulumi ESC as volumes in your Kubernetes pods
Prerequisites:
- The Pulumi ESC CLI
- A Kubernetes cluster (for example, kind)
- kubectl
- helm
Deploy Secret Store CSI Driver
Deploy using Helm
First, you need to deploy the Secret Store CSI Driver before you can install the Pulumi ESC provider.
helm repo add secrets-store-csi-driver https://kubernetes-sigs.github.io/secrets-store-csi-driver/charts
helm repo update
helm upgrade -i csi-secrets-store secrets-store-csi-driver/secrets-store-csi-driver --namespace kube-system
Running the above helm upgrade -i
command will install the Secret Store CSI Driver on Linux nodes in the kube-system
namespace.
To validate the installer is running as expected, run the following commands:
kubectl get pods -n kube-system
You should see the csi-secrets-store
pod running.
NAME READY STATUS RESTARTS AGE
csi-secrets-store-secrets-store-csi-driver-drv44 3/3 Running 0 54s
Additionally, you should see the following CRDs installed:
kubectl get crds | grep secrets-store
secretproviderclasses.secrets-store.csi.x-k8s.io 2025-01-02T14:03:58Z
secretproviderclasspodstatuses.secrets-store.csi.x-k8s.io 2025-01-02T14:03:58Z
Now that the Secret Store CSI Driver is running, you can deploy the Pulumi ESC Provider.
helm upgrade -i pulumi-esc-csi-provider oci://ghcr.io/pulumi/helm-charts/pulumi-esc-csi-provider --namespace kube-system
After a few seconds, the pulumi-esc-csi-provider
should be running.
kubectl get pods -n kube-system -l app=pulumi-esc-csi-provider
NAME READY STATUS RESTARTS AGE
pulumi-esc-csi-provider-l8w5f 1/1 Running 0 76s
Now with everything running, we can start using the Pulumi ESC provider to retrieve our secrets from Pulumi ESC.
Create secret containing Pulumi access token
kubectl create secret generic pulumi-secret-provider-auth-credentials --from-literal=pulumi-access-token=${PULUMI_ACCESS_TOKEN}
Create your own SecretProviderClass object
To use the Pulumi ESC provider, you need to create a SecretProviderClass
object. This object tells the Secret Store CSI Driver how to connect to the Pulumi ESC provider.
cat <<EOF | kubectl apply -f -
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: example-provider-pulumi-esc
namespace: default
spec:
provider: pulumi
parameters:
apiUrl: https://api.pulumi.com/api/esc
organization: ${PULUMI_ORG}
project: ${ESC_PROJECT}
environment: ${ESC_ENV}
authSecretName: pulumi-secret-provider-auth-credentials
authSecretNamespace: default
secrets: |
- secretPath: "/"
fileName: "hello"
secretKey: "app.hello"
EOF
Please replace ${PULUMI_ORG}
, ${ESC_PROJECT}
, ${ESC_ENV}
with your Pulumi organization, project, and environment names.
In the secrets
section, you can define the secrets you want to retrieve from Pulumi ESC. secretPath
is the path in the Pulumi ESC project where the secret is stored, fileName
is the name of the file that will be created.
Now, we will create a secret in the Pulumi ESC project and synchronize it into the Kubernetes cluster by creating an SecretProviderClass
:
Create a new ESC environment called csi-secrets-store-app
in the esc-secrets-store-csi-driver-demo
project:
values:
app:
hello: world
hello-secret:
fn::secret: world
If you prefer to use the Pulumi CLI, you can create the environment by running:
pulumi env init <your-org>/esc-secrets-store-csi-driver-demo/csi-secrets-store-app
And set the configuration by running the env edit
command and copy the above YAML into the editor:
pulumi env edit <your-org>/esc-secrets-store-csi-driver-demo/csi-secrets-store-app
Either way, you should see following environment configuration in the Pulumi Cloud Console:
Now we can update our deployment to use the example-provider-pulumi-esc
provider.
apiVersion: apps/v1
kind: Deployment
metadata:
name: example-provider-pulumi-esc
namespace: default
labels:
app: example-provider-pulumi-esc
spec:
replicas: 1
selector:
matchLabels:
app: example-provider-pulumi-esc
template:
metadata:
labels:
app: example-provider-pulumi-esc
spec:
containers:
- name: client
image: busybox:latest
command: ["sh", "-c"]
args:
- |
set -eux
ls /run/secrets
find /run/secrets/ -mindepth 1 -maxdepth 1 -not -name '.*' | xargs -t -I {} sh -c 'echo "$(cat "{}")"'
tail -f /dev/null
volumeMounts:
- name: data
mountPath: /run/secrets
volumes:
- name: data
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: "example-provider-pulumi-esc"
An important part of the deployment is the secretProviderClass: "example-provider-pulumi-esc"
attribute in the volumeAttributes
section. This tells the Secret Store CSI Driver to use the example-provider-pulumi-esc
provider to retrieve the secrets.
On pod start or restart, the Secret Store CSI Driver will communicate with the Pulumi ESC provider to retrieve the secrets content from Pulumi ESC as defined in the SecretProviderClass
object.
Then the volume is mounted in the pod as tmpfs
and the secret contents are written to the mounted volume.
Now you can deploy the deployment and validate that the secrets are mounted in the pod.
kubectl apply -f deployment.yaml
Verify the secret is mounted in the pod
NAME=$(kubectl get pods -o name | grep example-provider-pulumi-esc | cut -d'/' -f2)
kubectl logs $NAME
You should see the secret content world
printed to the console.
+ ls /run/secrets
hello
+ find /run/secrets/ -mindepth 1 -maxdepth 1 -not -name '.*'
+ xargs -t -I '{}' sh -c 'echo "$(cat "{}")"'
sh -c echo "$(cat "/run/secrets/hello")"
world
+ tail -f /dev/null
Or you can exec into the pod and check the content of the mounted secret:
kubectl exec -it $NAME -- cat /run/secrets/hello
You should see the following output:
world
Cleanup
To uninstall the pulumi-esc-csi-provider
deployment:
kubectl delete secret pulumi-secret-provider-auth-credentials
kubectl delete -f deployment.yaml
helm uninstall pulumi-esc-csi-provider
helm uninstall csi-secrets-store
Deploy using Pulumi
Of course, you can also deploy the CSI driver and Pulumi ESC provider using Pulumi. To do so, you can use the following Pulumi program:
Select your Pulumi supported language
Create a new Pulumi program in your preferred language and add the following code to deploy the External Secrets Operator.
pulumi new <your-preferred-language> --name external-secrets-operator
Before we dig into the code, let’s head to the Pulumi Cloud Console and create a new Pulumi ESC project with the name esc-secrets-store-csi-driver-demo
.
And create the environment dev
:
In the editor add the following yaml into the Environment definition
:
values:
pulumiConfig:
pulumi-pat:
fn::secret: <your-pulumi-pat>
import * as pulumi from "@pulumi/pulumi";
import * as k8s from "@pulumi/kubernetes";
const secretsStoreCSIDriver = new k8s.helm.v4.Chart("secrets-store-csi-driver", {
chart: "secrets-store-csi-driver",
namespace: "kube-system",
repositoryOpts: {
repo: "https://kubernetes-sigs.github.io/secrets-store-csi-driver/charts",
},
values: {
nodeSelector: {
"kubernetes.io/os": "linux",
},
},
});
const secretsStoreCSIPulumiESCProvider = new k8s.helm.v4.Chart(
"secrets-store-csi-pulumi-esc-provider",
{
chart: "oci://ghcr.io/pulumi/helm-charts/pulumi-esc-csi-provider",
namespace: "kube-system",
values: {
nodeSelector: {
"kubernetes.io/os": "linux",
},
},
},
{ dependsOn: secretsStoreCSIDriver },
);
const config = new pulumi.Config();
const mySecret = new k8s.core.v1.Secret(
"my-secret",
{
metadata: {
namespace: "default",
name: "pulumi-access-token",
},
stringData: {
"pulumi-access-token": config.require("pulumi-pat"),
},
type: "Opaque",
},
{ dependsOn: secretsStoreCSIPulumiESCProvider },
);
"use strict";
const pulumi = require("@pulumi/pulumi");
const k8s = require("@pulumi/kubernetes");
const secretsStoreCSIDriver = new k8s.helm.v4.Chart("secrets-store-csi-driver", {
chart: "secrets-store-csi-driver",
namespace: "kube-system",
repositoryOpts: {
repo: "https://kubernetes-sigs.github.io/secrets-store-csi-driver/charts",
},
values: {
nodeSelector: {
"kubernetes.io/os": "linux",
},
},
});
const secretsStoreCSIPulumiESCProvider = new k8s.helm.v4.Chart(
"secrets-store-csi-pulumi-esc-provider",
{
chart: "oci://ghcr.io/pulumi/helm-charts/pulumi-esc-csi-provider",
namespace: "kube-system",
values: {
nodeSelector: {
"kubernetes.io/os": "linux",
},
},
},
{ dependsOn: secretsStoreCSIDriver },
);
const config = new pulumi.Config();
const mySecret = new k8s.core.v1.Secret(
"my-secret",
{
metadata: {
namespace: "default",
name: "pulumi-access-token",
},
stringData: {
"pulumi-access-token": config.require("pulumi-pat"),
},
type: "Opaque",
},
{ dependsOn: secretsStoreCSIPulumiESCProvider },
);
import pulumi
import pulumi_kubernetes as k8s
secrets_store_csi_driver = k8s.helm.v4.Chart(
"secrets-store-csi-driver",
k8s.helm.v4.ChartArgs(
chart="secrets-store-csi-driver",
namespace="kube-system",
repository_opts=k8s.helm.v4.RepositoryOptsArgs(
repo="https://kubernetes-sigs.github.io/secrets-store-csi-driver/charts",
),
values={
"nodeSelector": {
"kubernetes.io/os": "linux",
},
},
),
)
secrets_store_csi_pulumi_esc_provider = k8s.helm.v4.Chart(
"secrets-store-csi-pulumi-esc-provider",
k8s.helm.v4.ChartArgs(
chart="oci://ghcr.io/pulumi/helm-charts/pulumi-esc-csi-provider",
namespace="kube-system",
values={
"nodeSelector": {
"kubernetes.io/os": "linux",
},
},
),
opts=pulumi.ResourceOptions(depends_on=[secrets_store_csi_driver]),
)
config = pulumi.Config()
my_secret = k8s.core.v1.Secret(
"my-secret",
metadata=k8s.meta.v1.ObjectMetaArgs(
namespace="default", name="pulumi-access-token"
),
string_data={
"pulumi-access-token": config.require("pulumi-pat"),
},
type="Opaque",
opts=pulumi.ResourceOptions(depends_on=[secrets_store_csi_pulumi_esc_provider]),
)
package main
import (
k8s "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes"
"github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/apiextensions"
appv1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/apps/v1"
corev1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/core/v1"
chartv4 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/helm/v4"
metav1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/meta/v1"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi/config"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
secretsStoreCsiDriver, err := chartv4.NewChart(ctx, "secrets-store-csi-driver", &chartv4.ChartArgs{
Chart: pulumi.String("secrets-store-csi-driver"),
Namespace: pulumi.String("kube-system"),
RepositoryOpts: chartv4.RepositoryOptsArgs{
Repo: pulumi.String("https://kubernetes-sigs.github.io/secrets-store-csi-driver/charts"),
},
Values: pulumi.Map{
"nodeSelector": pulumi.Map{
"kubernetes.io/os": pulumi.String("linux"),
},
},
})
if err != nil {
return err
}
secretsStoreCsiPulumiEscProvider, err := chartv4.NewChart(ctx, "secrets-store-csi-pulumi-esc-provider", &chartv4.ChartArgs{
Chart: pulumi.String("oci://ghcr.io/pulumi/helm-charts/pulumi-esc-csi-provider"),
Namespace: pulumi.String("kube-system"),
Values: pulumi.Map{
"nodeSelector": pulumi.Map{
"kubernetes.io/os": pulumi.String("linux"),
},
},
}, pulumi.DependsOn([]pulumi.Resource{secretsStoreCsiDriver}))
if err != nil {
return err
}
pulumiPAT := config.Get(ctx, "pulumi-pat")
mySecret, err := corev1.NewSecret(ctx, "my-secret", &corev1.SecretArgs{
Metadata: &metav1.ObjectMetaArgs{
Namespace: pulumi.String("default"),
Name: pulumi.String("pulumi-access-token"),
},
StringData: pulumi.StringMap{
"pulumi-access-token": pulumi.String(pulumiPAT),
},
Type: pulumi.String("Opaque"),
}, pulumi.DependsOn([]pulumi.Resource{secretsStoreCsiPulumiEscProvider}))
if err != nil {
return err
}
return nil
})
}
using Pulumi;
using Pulumi.Kubernetes.Core.V1;
using Pulumi.Kubernetes.Types.Inputs.Core.V1;
using Pulumi.Kubernetes.Helm.V3;
using Pulumi.Kubernetes.Helm;
using Pulumi.Kubernetes.Types.Inputs.Meta.V1;
using Pulumi.Kubernetes.ApiExtensions;
using System.Collections.Generic;
return await Deployment.RunAsync(() =>
{
var secretsStoreCsiDriver = new Release("secrets-store-csi-driver", new()
{
Chart = "secrets-store-csi-driver",
Namespace = "kube-system",
RepositoryOpts = new Pulumi.Kubernetes.Types.Inputs.Helm.V3.RepositoryOptsArgs
{
Repo = "https://kubernetes-sigs.github.io/secrets-store-csi-driver/charts",
},
Values = new Dictionary<string, object>
{
{ "nodeSelector", new Dictionary<string, object>
{
{ "kubernetes.io/os", "linux" },
} },
},
});
var secretsStoreCsiPulumiEscProvider = new Release("secrets-store-csi-pulumi-esc-provider", new()
{
Chart = "oci://ghcr.io/pulumi/helm-charts/pulumi-esc-csi-provider",
Namespace = "kube-system",
Values = new Dictionary<string, object>
{
{ "nodeSelector", new Dictionary<string, object>
{
{ "kubernetes.io/os", "linux" },
} },
},
}, new CustomResourceOptions { DependsOn = { secretsStoreCsiDriver } });
var config = new Config();
var pulumiPAT = config.Require("pulumi-pat");
var mySecret = new Secret("my-secret", new SecretArgs
{
Metadata = new ObjectMetaArgs
{
Namespace = "default",
Name = "pulumi-access-token",
},
StringData =
{
{ "pulumi-access-token", pulumiPAT }
},
Type = "Opaque",
}, new CustomResourceOptions { DependsOn = { secretsStoreCsiPulumiEscProvider } });
});
class SecretProviderClassArgs : CustomResourceArgs
{
public SecretProviderClassArgs(): base("secrets-store.csi.x-k8s.io/v1", "SecretProviderClass")
{
}
[Input("spec")]
public Input<SecretProviderClassSpecArgs>? Spec { get; set; }
}
class SecretProviderClassSpecArgs : ResourceArgs
{
[Input("provider")]
public Input<string>? Provider { get; set; }
[Input("parameters")]
public Input<InputMap<object>>? Parameters { get; set; }
}
class SecretProviderParametersArgs : ResourceArgs
{
[Input("apiUrl")]
public Input<string>? ApiUrl { get; set; }
[Input("organization")]
public Input<string>? Organization { get; set; }
[Input("project")]
public Input<string>? Project { get; set; }
[Input("environment")]
public Input<string>? Environment { get; set; }
[Input("authSecretName")]
public Input<string>? AuthSecretName { get; set; }
[Input("authSecretNamespace")]
public Input<string>? AuthSecretNamespace { get; set; }
[Input("secrets")]
public Input<string>? Secrets { get; set; }
}
Deploy the stack by running:
pulumi up
You will see that the secret was created in the Kubernetes cluster and the ESO instance was deployed successfully.
kubectl get secret pulumi-access-token -o jsonpath='{.data.PULUMI_ACCESS_TOKEN}' | base64 -d
Create a SecretProviderClass
const secretProviderClass = new k8s.apiextensions.CustomResource(
"example-provider-pulumi-esc",
{
apiVersion: "secrets-store.csi.x-k8s.io/v1",
kind: "SecretProviderClass",
metadata: {
name: "example-provider-pulumi-esc",
namespace: "default",
},
spec: {
provider: "pulumi",
parameters: {
apiUrl: "https://api.pulumi.com/api/esc",
organization: "dirien",
project: "esc-secrets-store-csi-driver-demo",
environment: "csi-secrets-store-app",
authSecretName: mySecret.metadata.name,
authSecretNamespace: mySecret.metadata.namespace,
secrets: `- secretPath: "/"
fileName: "hello"
secretKey: "app.hello"
`,
},
},
},
{ dependsOn: secretsStoreCSIPulumiESCProvider },
);
const secretProviderClass = new k8s.apiextensions.CustomResource(
"example-provider-pulumi-esc",
{
apiVersion: "secrets-store.csi.x-k8s.io/v1",
kind: "SecretProviderClass",
metadata: {
name: "example-provider-pulumi-esc",
namespace: "default",
},
spec: {
provider: "pulumi",
parameters: {
apiUrl: "https://api.pulumi.com/api/esc",
organization: "dirien",
project: "esc-secrets-store-csi-driver-demo",
environment: "csi-secrets-store-app",
authSecretName: mySecret.metadata.name,
authSecretNamespace: mySecret.metadata.namespace,
secrets: `- secretPath: "/"
fileName: "hello"
secretKey: "app.hello"
`,
},
},
},
{ dependsOn: secretsStoreCSIPulumiESCProvider },
);
secret_provider_class = k8s.apiextensions.CustomResource(
"example-provider-pulumi-esc",
api_version="secrets-store.csi.x-k8s.io/v1",
kind="SecretProviderClass",
metadata=k8s.meta.v1.ObjectMetaArgs(
name="example-provider-pulumi-esc", namespace="default"
),
spec={
"provider": "pulumi",
"parameters": {
"apiUrl": "https://api.pulumi.com/api/esc",
"organization": "dirien",
"project": "esc-secrets-store-csi-driver-demo",
"environment": "csi-secrets-store-app",
"authSecretName": my_secret.metadata["name"],
"authSecretNamespace": my_secret.metadata["namespace"],
"secrets": '- secretPath: "/"\n fileName: "hello"\n secretKey: "app.hello"\n',
},
},
opts=pulumi.ResourceOptions(depends_on=[secrets_store_csi_pulumi_esc_provider]),
)
secretProviderClass, err := apiextensions.NewCustomResource(ctx, "example-provider-pulumi-esc", &apiextensions.CustomResourceArgs{
ApiVersion: pulumi.String("secrets-store.csi.x-k8s.io/v1"),
Kind: pulumi.String("SecretProviderClass"),
Metadata: &metav1.ObjectMetaArgs{
Name: pulumi.String("example-provider-pulumi-esc"),
Namespace: pulumi.String("default"),
},
OtherFields: k8s.UntypedArgs{
"provider": pulumi.String("pulumi"),
"parameters": pulumi.Map{
"apiUrl": pulumi.String("https://api.pulumi.com/api/esc"),
"organization": pulumi.String("dirien"),
"project": pulumi.String("esc-secrets-store-csi-driver-demo"),
"environment": pulumi.String("csi-secrets-store-app"),
"authSecretName": mySecret.Metadata.Name().Elem(),
"authSecretNamespace": mySecret.Metadata.Namespace().Elem(),
"secrets": pulumi.String(`- secretPath: "/"
fileName: "hello"
secretKey: "app.hello"
`),
},
},
}, pulumi.DependsOn([]pulumi.Resource{secretsStoreCsiPulumiEscProvider}))
if err != nil {
return err
}
var secretProviderClass = new Pulumi.Kubernetes.ApiExtensions.CustomResource("example-provider-pulumi-esc", new SecretProviderClassArgs
{
Metadata = new ObjectMetaArgs
{
Name = "example-provider-pulumi-esc",
Namespace = "default",
},
Spec = new SecretProviderClassSpecArgs
{
Provider = "pulumi",
Parameters = new InputMap<object>
{
{ "apiUrl", "https://api.pulumi.com/api/esc" },
{ "organization", "dirien" },
{ "project", "esc-secrets-store-csi-driver-demo" },
{ "environment", "csi-secrets-store-app" },
{ "authSecretName", mySecret.Metadata.Apply(metadata => metadata.Name) },
{ "authSecretNamespace", mySecret.Metadata.Apply(metadata => metadata.Namespace) },
{ "secrets", "- secretPath: \"/\"\n fileName: \"hello\"\n secretKey: \"app.hello\"\n" }
},
},
}, new CustomResourceOptions { DependsOn = { secretsStoreCsiPulumiEscProvider } });
We can check that the secret was successfully synchronized by running:
kubectl get secretproviderclasses example-provider-pulumi-esc
Deploy an Application and Mount the Secret
Now, we can deploy an application that references the secret from the Kubernetes cluster. I am going to use busybox
that reads mounted file in.
const deployment = new k8s.apps.v1.Deployment("example-provider-pulumi-esc", {
metadata: {
name: "example-provider-pulumi-esc",
namespace: "default",
labels: {
app: "example-provider-pulumi-esc",
},
},
spec: {
replicas: 1,
selector: {
matchLabels: {
app: "example-provider-pulumi-esc",
},
},
template: {
metadata: {
labels: {
app: "example-provider-pulumi-esc",
},
},
spec: {
containers: [
{
name: "client",
image: "busybox:latest",
command: ["sh", "-c"],
args: [
`set -eux
ls /run/secrets
find /run/secrets/ -mindepth 1 -maxdepth 1 -not -name '.*' | xargs -t -I {} sh -c 'echo "$(cat "{}")"'
tail -f /dev/null`,
],
volumeMounts: [
{
name: "data",
mountPath: "/run/secrets",
},
],
},
],
volumes: [
{
name: "data",
csi: {
driver: "secrets-store.csi.k8s.io",
readOnly: true,
volumeAttributes: {
secretProviderClass: secretProviderClass.metadata.name,
},
},
},
],
},
},
},
});
export const deploymentName = deployment.metadata.name;
const deployment = new k8s.apps.v1.Deployment("example-provider-pulumi-esc", {
metadata: {
name: "example-provider-pulumi-esc",
namespace: "default",
labels: {
app: "example-provider-pulumi-esc",
},
},
spec: {
replicas: 1,
selector: {
matchLabels: {
app: "example-provider-pulumi-esc",
},
},
template: {
metadata: {
labels: {
app: "example-provider-pulumi-esc",
},
},
spec: {
containers: [
{
name: "client",
image: "busybox:latest",
command: ["sh", "-c"],
args: [
`set -eux
ls /run/secrets
find /run/secrets/ -mindepth 1 -maxdepth 1 -not -name '.*' | xargs -t -I {} sh -c 'echo "$(cat "{}")"'
tail -f /dev/null`,
],
volumeMounts: [
{
name: "data",
mountPath: "/run/secrets",
},
],
},
],
volumes: [
{
name: "data",
csi: {
driver: "secrets-store.csi.k8s.io",
readOnly: true,
volumeAttributes: {
secretProviderClass: secretProviderClass.metadata.name,
},
},
},
],
},
},
},
});
exports.deploymentName = deployment.metadata.name;
deployment = k8s.apps.v1.Deployment(
"example-provider-pulumi-esc",
metadata=k8s.meta.v1.ObjectMetaArgs(
namespace="default",
name="example-provider-pulumi-esc",
labels={
"app": "example-provider-pulumi-esc",
},
),
spec=k8s.apps.v1.DeploymentSpecArgs(
replicas=1,
selector=k8s.meta.v1.LabelSelectorArgs(
match_labels={
"app": "example-provider-pulumi-esc",
},
),
template=k8s.core.v1.PodTemplateSpecArgs(
metadata=k8s.meta.v1.ObjectMetaArgs(
labels={
"app": "example-provider-pulumi-esc",
},
),
spec=k8s.core.v1.PodSpecArgs(
containers=[
k8s.core.v1.ContainerArgs(
name="client",
image="busybox:latest",
command=["sh", "-c"],
args=[
"set -eux\nls /run/secrets\nfind /run/secrets/ -mindepth 1 -maxdepth 1 -not -name '.*' | xargs -t -I {} sh -c 'echo \"$(cat \"{}\")\"'\ntail -f /dev/null",
],
volume_mounts=[
k8s.core.v1.VolumeMountArgs(
name="data",
mount_path="/run/secrets",
),
],
),
],
volumes=[
k8s.core.v1.VolumeArgs(
name="data",
csi=k8s.core.v1.CSIVolumeSourceArgs(
driver="secrets-store.csi.k8s.io",
read_only=True,
volume_attributes={
"secretProviderClass": secret_provider_class.metadata[
"name"
],
},
),
),
],
),
),
),
)
pulumi.export("deploymentName", deployment.metadata["name"])
deployment, err := appv1.NewDeployment(ctx, "example-provider-pulumi-esc", &appv1.DeploymentArgs{
Metadata: &metav1.ObjectMetaArgs{
Name: pulumi.String("example-provider-pulumi-esc"),
Namespace: pulumi.String("default"),
Labels: pulumi.StringMap{
"app": pulumi.String("example-provider-pulumi-esc"),
},
},
Spec: &appv1.DeploymentSpecArgs{
Replicas: pulumi.Int(1),
Selector: &metav1.LabelSelectorArgs{
MatchLabels: pulumi.StringMap{
"app": pulumi.String("example-provider-pulumi-esc"),
},
},
Template: &corev1.PodTemplateSpecArgs{
Metadata: &metav1.ObjectMetaArgs{
Labels: pulumi.StringMap{
"app": pulumi.String("example-provider-pulumi-esc"),
},
},
Spec: &corev1.PodSpecArgs{
Containers: corev1.ContainerArray{
&corev1.ContainerArgs{
Name: pulumi.String("client"),
Image: pulumi.String("busybox:latest"),
Command: pulumi.StringArray{
pulumi.String("sh"),
pulumi.String("-c"),
},
Args: pulumi.StringArray{
pulumi.String(`set -eux
ls /run/secrets
find /run/secrets/ -mindepth 1 -maxdepth 1 -not -name '.*' | xargs -t -I {} sh -c 'echo "$(cat "{}")"'
tail -f /dev/null`),
},
VolumeMounts: &corev1.VolumeMountArray{
&corev1.VolumeMountArgs{
Name: pulumi.String("data"),
MountPath: pulumi.String("/run/secrets"),
},
},
},
},
Volumes: &corev1.VolumeArray{
&corev1.VolumeArgs{
Name: pulumi.String("data"),
Csi: corev1.CSIVolumeSourceArgs{
Driver: pulumi.String("secrets-store.csi.k8s.io"),
ReadOnly: pulumi.Bool(true),
VolumeAttributes: pulumi.StringMap{
"secretProviderClass": secretProviderClass.Metadata.Name().Elem(),
},
},
},
},
},
},
},
}, pulumi.DependsOn([]pulumi.Resource{secretProviderClass}))
if err != nil {
return err
}
var deployment = new Pulumi.Kubernetes.Apps.V1.Deployment("example-provider-pulumi-esc", new Pulumi.Kubernetes.Types.Inputs.Apps.V1.DeploymentArgs
{
Metadata = new ObjectMetaArgs
{
Name = "example-provider-pulumi-esc",
Namespace = "default",
Labels =
{
{ "app", "example-provider-pulumi-esc" },
},
},
Spec = new Pulumi.Kubernetes.Types.Inputs.Apps.V1.DeploymentSpecArgs
{
Replicas = 1,
Selector = new Pulumi.Kubernetes.Types.Inputs.Meta.V1.LabelSelectorArgs
{
MatchLabels =
{
{ "app", "example-provider-pulumi-esc" },
},
},
Template = new Pulumi.Kubernetes.Types.Inputs.Core.V1.PodTemplateSpecArgs
{
Metadata = new ObjectMetaArgs
{
Labels =
{
{ "app", "example-provider-pulumi-esc" },
},
},
Spec = new Pulumi.Kubernetes.Types.Inputs.Core.V1.PodSpecArgs
{
Containers =
{
new Pulumi.Kubernetes.Types.Inputs.Core.V1.ContainerArgs
{
Name = "client",
Image = "busybox:latest",
Command =
{
"sh",
"-c",
},
Args =
{
"set -eux\nls /run/secrets\nfind /run/secrets/ -mindepth 1 -maxdepth 1 -not -name '.*' | xargs -t -I {} sh -c 'echo \"$(cat \"{}\")\"'\ntail -f /dev/null",
},
VolumeMounts =
{
new Pulumi.Kubernetes.Types.Inputs.Core.V1.VolumeMountArgs
{
Name = "data",
MountPath = "/run/secrets",
},
},
},
},
Volumes =
{
new Pulumi.Kubernetes.Types.Inputs.Core.V1.VolumeArgs
{
Name = "data",
Csi = new Pulumi.Kubernetes.Types.Inputs.Core.V1.CSIVolumeSourceArgs
{
Driver = "secrets-store.csi.k8s.io",
ReadOnly = true,
VolumeAttributes =
{
{ "secretProviderClass", secretProviderClass.Metadata.Apply(metadata => metadata.Name) },
},
},
},
},
},
},
},
}, new CustomResourceOptions { DependsOn = { secretProviderClass } });
After deploying the stack, you can get the logs of the busybox
pod to see that the secret was successfully mounted:
NAME=$(kubectl get pods -o name | grep example-provider-pulumi-esc | cut -d'/' -f2)
kubectl logs $NAME
You should see the following output:
+ ls /run/secrets
hello
+ find /run/secrets/ -mindepth 1 -maxdepth 1 -not -name '.*'
+ xargs -t -I '{}' sh -c 'echo "$(cat "{}")"'
sh -c echo "$(cat "/run/secrets/hello")"
world
+ tail -f /dev/null
Or you can exec into the pod and check the content of the mounted secret:
kubectl exec -it $NAME -- cat /run/secrets/hello
You should see the following output:
world
Cleanup
To uninstall the pulumi-esc-csi-provider
deployment:
pulumi destroy
Next Steps
In this tutorial, you learned how to integrate the Secret Store CSI Driver with Pulumi ESC to securely manage your secrets in Kubernetes. To learn more about Pulumi ESC, check out the Pulumi ESC documentation.
As we continue to improve the Pulumi ESC provider, we would love to hear your feedback. Please reach out to us on Slack or GitHub with any questions or suggestions.
To dive deeper into using Pulumi ESC for advanced scenarios, check out the following resources:
- Pulumi ESC and External Secrets Operator: Learn how to use the External Secrets Operator to manage secrets in Pulumi ESC and synchronize them with your Kubernetes cluster. Check out the Pulumi ESC and External Secrets Operator tutorial.
- Environment Composition: Learn more about how to effectively compose multiple environments to manage configurations across your infrastructure. Explore the Pulumi documentation on environment imports.
- Managing Secrets: Learn how to securely manage and adopt dynamic, short-lived secrets on demand using Pulumi ESC, ensuring sensitive information is protected across different environments. Read more in the Pulumi ESC documentation.