Secure Communication in Kubernetes with Consul Connect and Vault Agent Injector


Bicycle

Securing Communication in Kubernetes with Consul Connect and Vault Agent Injector

In modern cloud-native Kubernetes environments, security is paramount. One of the key challenges is ensuring secure communication between microservices while also managing secrets securely. Whether you're running a monolithic application or a set of microservices in Kubernetes, securing inter-service communication and secret management is crucial to prevent unauthorized access and ensure data privacy. Enter HashiCorp Consul Connect combined with HashiCorp Vault Agent Injector, bringing the same powerful service mesh solution from the HashiCorp ecosystem to Kubernetes.

In this blog post, we'll explore how you can secure the communication of your workloads in Kubernetes using HashiCorp Consul Connect service mesh while leveraging Vault Agent Injector for dynamic secret management, providing a direct Kubernetes equivalent to our Nomad examples.

Note: This article demonstrates how to use the same HashiCorp Consul Connect service mesh technology shown in our Secure Communication on HashiCorp Nomad with Consul Connect post, but running in Kubernetes. It also builds upon the secret management concepts from Kubernetes and Vault Agent Injector: Dynamic Secrets Management.

What is HashiCorp Consul Connect?

Consul Connect is HashiCorp's service mesh solution that provides secure service-to-service communication in your infrastructure. It achieves this by enabling mutual Transport Layer Security (mTLS) between services. Each service gets its own identity, and Consul enforces that only trusted services can communicate with each other. By using Connect, service communication is authenticated and encrypted, ensuring that the network's security is maintained even in hostile or compromised environments.

Key Features of Consul Connect:

  • mTLS (Mutual TLS): Ensure that all service-to-service communication is encrypted, and both the client and server verify each other's identity.
  • Service Identity: Each service registered in Consul has a unique identity, which can be used for fine-grained access control.
  • Authorization Policies (Intentions): You can define intentions that control which services are allowed to communicate, adding an extra layer of security.
  • Automatic Certificate Rotation: Consul manages certificate lifecycle, ensuring that certificates are renewed and rotated automatically to reduce operational overhead.
  • Native Kubernetes Integration: Consul Connect can run natively in Kubernetes using the official Consul Helm chart.

Why Consul Connect with Kubernetes?

While Kubernetes provides excellent orchestration capabilities, it lacks native service mesh features for encrypted communication between services. Consul Connect bridges this gap by bringing HashiCorp's proven service mesh technology to Kubernetes environments. When combined with Vault Agent Injector, you get:

  • Familiar Technology: Use the same Consul Connect you know from Nomad environments
  • Comprehensive Security: Network-level security (Consul Connect) and secret management (Vault)
  • Zero-Trust Architecture: Services must be explicitly authorized to communicate and receive appropriate secrets
  • Operational Consistency: Maintain the same service mesh patterns across Nomad and Kubernetes deployments
  • HashiCorp Ecosystem Integration: Seamless integration between Consul, Vault, and Kubernetes

Setting Up Consul Connect and Vault Agent Injector in Kubernetes

Prerequisites:

  1. Kubernetes cluster with sufficient resources for Consul Connect injection
  2. External HashiCorp Consul cluster running at consul.example.com:8500
  3. External HashiCorp Vault cluster running at vault.example.com:8200
  4. Helm for installing components
  5. kubectl configured for your cluster

Step 1: Install Consul Connect Injector for External Consul

Install only the Consul Connect injector components to connect directly to an external Consul cluster without deploying Consul clients:

 1# Add HashiCorp Helm repository
 2helm repo add hashicorp https://helm.releases.hashicorp.com
 3helm repo update
 4
 5# Create consul-values.yaml for external Consul configuration
 6cat > consul-values.yaml <<EOF
 7global:
 8  name: consul
 9  datacenter: dc1
10  # Configure to use external Consul servers directly
11  consulAPITimeout: 5s
12
13# Disable server installation - we're using external Consul
14server:
15  enabled: false
16
17# Disable client - Connect injector will communicate directly with external Consul
18client:
19  enabled: false
20
21# Enable Connect injection for external Consul
22connectInject:
23  enabled: true
24  default: false  # We'll explicitly enable per service
25  # Configure injector to use external Consul directly
26  consulNode:
27    meta:
28      pod-name: \${HOSTNAME}
29      node-name: \${NODE_NAME}
30  # External Consul servers
31  k8sAllowNamespaces: ["*"]
32  k8sDenyNamespaces: []
33
34controller:
35  enabled: true
36
37# Disable UI since we're using external Consul
38ui:
39  enabled: false
40
41# Disable DNS since no clients are deployed
42dns:
43  enabled: false
44
45# Configure external Consul connection
46externalServers:
47  enabled: true
48  hosts: ["consul.example.com"]
49  httpsPort: 8501  # Use HTTPS port for secure connection
50  useSystemRoots: true
51EOF
52
53# Install Consul Connect components
54kubectl create namespace consul
55helm install consul hashicorp/consul \
56  --namespace consul \
57  --values consul-values.yaml
58
59# Wait for Connect injector to be ready
60kubectl wait --for=condition=ready pod -l app=consul-connect-injector -n consul --timeout=300s

Step 2: Install Vault Agent Injector

Install the Vault Agent Injector as demonstrated in our previous posts:

1# Install only the Agent Injector (no Vault server)
2helm install vault-injector hashicorp/vault \
3  --set "global.externalVaultAddr=https://vault.example.com:8200" \
4  --set "injector.enabled=true" \
5  --set "server.enabled=false" \
6  --set "csi.enabled=false"

Step 3: Configure Vault Authentication

Set up the same Vault configuration as in our previous Kubernetes posts:

 1# Set Vault address environment variable
 2export VAULT_ADDR="https://vault.example.com:8200"
 3
 4# Enable Kubernetes auth method
 5vault auth enable kubernetes
 6
 7# Configure Kubernetes auth
 8KUBERNETES_HOST=$(kubectl config view --raw --minify --flatten -o jsonpath='{.clusters[].cluster.server}')
 9KUBERNETES_CA_CERT=$(kubectl config view --raw --minify --flatten -o jsonpath='{.clusters[].cluster.certificate-authority-data}' | base64 -d)
10
11# Create service account and configure auth
12kubectl create serviceaccount vault-auth
13kubectl apply -f - <<EOF
14apiVersion: rbac.authorization.k8s.io/v1
15kind: ClusterRoleBinding
16metadata:
17  name: role-tokenreview-binding
18roleRef:
19  apiGroup: rbac.authorization.k8s.io
20  kind: ClusterRole
21  name: system:auth-delegator
22subjects:
23- kind: ServiceAccount
24  name: vault-auth
25  namespace: default
26EOF
27
28TOKEN_REVIEWER_JWT=$(kubectl create token vault-auth)
29
30vault write auth/kubernetes/config \
31    token_reviewer_jwt="$TOKEN_REVIEWER_JWT" \
32    kubernetes_host="$KUBERNETES_HOST" \
33    kubernetes_ca_cert="$KUBERNETES_CA_CERT"

Step 4: Configure Vault Secrets and Policies

 1# Enable database secrets engine
 2vault secrets enable -path=dynamic-app/db database
 3
 4# Configure MySQL connection
 5vault write dynamic-app/db/config/mysql \
 6    plugin_name=mysql-database-plugin \
 7    connection_url="{{username}}:{{password}}@tcp(mysql-server.demo.svc.cluster.local:3306)/" \
 8    allowed_roles="*" \
 9    username="root" \
10    password="super-duper-password"
11
12# Create dynamic credentials role
13vault write dynamic-app/db/roles/app \
14    db_name=mysql \
15    creation_statements="CREATE USER '{{name}}'@'%' IDENTIFIED BY '{{password}}';GRANT ALL ON my_app.* TO '{{name}}'@'%';" \
16    default_ttl="1h" \
17    max_ttl="24h"
18
19# Enable transit engine for encryption
20vault secrets enable -path=dynamic-app/transit transit
21vault write -f dynamic-app/transit/keys/app
22
23# Create Vault policy
24vault policy write dynamic-app-consul - <<EOF
25path "dynamic-app/db/creds/app" {
26  capabilities = ["read"]
27}
28path "dynamic-app/transit/encrypt/app" {
29  capabilities = ["update"]
30}
31path "dynamic-app/transit/decrypt/app" {
32  capabilities = ["update"]
33}
34EOF
35
36# Create Kubernetes role
37vault write auth/kubernetes/role/dynamic-app-consul \
38    bound_service_account_names=dynamic-app-consul \
39    bound_service_account_namespaces=demo \
40    policies=dynamic-app-consul \
41    ttl=24h

Deploy Services with Consul Connect and Vault Integration

Step 5: Create Demo Namespace and Register Services

1# Create demo namespace
2kubectl create namespace demo
3
4# Create service accounts
5kubectl create serviceaccount dynamic-app-consul -n demo
6kubectl create serviceaccount mysql-server-consul -n demo

Step 6: Deploy MySQL Database with Consul Connect

 1# mysql-consul-deployment.yaml
 2apiVersion: apps/v1
 3kind: Deployment
 4metadata:
 5  name: mysql-server
 6  namespace: demo
 7  labels:
 8    app: mysql-server
 9spec:
10  replicas: 1
11  selector:
12    matchLabels:
13      app: mysql-server
14  template:
15    metadata:
16      labels:
17        app: mysql-server
18      annotations:
19        "consul.hashicorp.com/connect-inject": "true"
20        "consul.hashicorp.com/connect-service": "mysql-server"
21        "consul.hashicorp.com/connect-service-port": "3306"
22    spec:
23      serviceAccountName: mysql-server-consul
24      containers:
25      - name: mysql
26        image: mysql:9
27        env:
28        - name: MYSQL_ROOT_PASSWORD
29          value: "super-duper-password"
30        - name: MYSQL_DATABASE
31          value: "my_app"
32        ports:
33        - containerPort: 3306
34          name: mysql
35        resources:
36          requests:
37            cpu: 500m
38            memory: 1Gi
39          limits:
40            cpu: 500m
41            memory: 1Gi
42        readinessProbe:
43          exec:
44            command:
45            - "/bin/bash"
46            - "-c"
47            - "mysqladmin ping -h 127.0.0.1 -u root -p$MYSQL_ROOT_PASSWORD"
48          initialDelaySeconds: 30
49          periodSeconds: 10
50        livenessProbe:
51          exec:
52            command:
53            - "/bin/bash"
54            - "-c"
55            - "mysqladmin ping -h 127.0.0.1 -u root -p$MYSQL_ROOT_PASSWORD"
56          initialDelaySeconds: 60
57          periodSeconds: 10
58
59---
60apiVersion: v1
61kind: Service
62metadata:
63  name: mysql-server
64  namespace: demo
65  labels:
66    app: mysql-server
67spec:
68  ports:
69  - port: 3306
70    name: mysql
71    targetPort: 3306
72  selector:
73    app: mysql-server

Step 7: Deploy Dynamic App with Consul Connect and Vault Agent Injector

  1# dynamic-app-consul.yaml
  2apiVersion: apps/v1
  3kind: Deployment
  4metadata:
  5  name: dynamic-app
  6  namespace: demo
  7  labels:
  8    app: dynamic-app
  9spec:
 10  replicas: 1
 11  selector:
 12    matchLabels:
 13      app: dynamic-app
 14  template:
 15    metadata:
 16      labels:
 17        app: dynamic-app
 18      annotations:
 19        # Consul Connect annotations
 20        "consul.hashicorp.com/connect-inject": "true"
 21        "consul.hashicorp.com/connect-service": "dynamic-app"
 22        "consul.hashicorp.com/connect-service-port": "8080"
 23        "consul.hashicorp.com/connect-service-upstreams": "mysql-server:3306"
 24
 25        # Vault Agent Injector annotations
 26        "vault.hashicorp.com/agent-inject": "true"
 27        "vault.hashicorp.com/agent-inject-status": "update"
 28        "vault.hashicorp.com/agent-inject-vault-addr": "https://vault.example.com:8200"
 29        "vault.hashicorp.com/role": "dynamic-app-consul"
 30        "vault.hashicorp.com/agent-inject-secret-config.ini": "dynamic-app/db/creds/app"
 31        "vault.hashicorp.com/agent-inject-template-config.ini": |
 32          [DEFAULT]
 33          LogLevel = DEBUG
 34          Port = 8080
 35
 36          [DATABASE]
 37          Address = 127.0.0.1
 38          Port = 3306
 39          Database = my_app
 40          {{- with secret "dynamic-app/db/creds/app" }}
 41          User = {{ .Data.username }}
 42          Password = {{ .Data.password }}
 43          {{- end }}
 44
 45          [VAULT]
 46          Enabled = True
 47          InjectToken = True
 48          Namespace =
 49          Address = https://vault.example.com:8200
 50          KeyPath = dynamic-app/transit
 51          KeyName = app
 52    spec:
 53      serviceAccountName: dynamic-app-consul
 54      containers:
 55      - name: dynamic-app
 56        image: ghcr.io/infralovers/nomad-vault-mysql:1.0.0
 57        ports:
 58        - containerPort: 8080
 59          name: http
 60        env:
 61        - name: CONFIG_FILE
 62          value: "/vault/secrets/config.ini"
 63        - name: VAULT_ADDR
 64          value: "https://vault.example.com:8200"
 65        resources:
 66          requests:
 67            cpu: 256m
 68            memory: 256Mi
 69          limits:
 70            cpu: 256m
 71            memory: 256Mi
 72        livenessProbe:
 73          httpGet:
 74            path: /health
 75            port: 8080
 76          initialDelaySeconds: 60
 77          periodSeconds: 10
 78        readinessProbe:
 79          httpGet:
 80            path: /health
 81            port: 8080
 82          initialDelaySeconds: 30
 83          periodSeconds: 5
 84
 85---
 86apiVersion: v1
 87kind: Service
 88metadata:
 89  name: dynamic-app
 90  namespace: demo
 91  labels:
 92    app: dynamic-app
 93spec:
 94  ports:
 95  - port: 8080
 96    name: http
 97    targetPort: 8080
 98  selector:
 99    app: dynamic-app
100
101---
102# Ingress for external access
103apiVersion: networking.k8s.io/v1
104kind: Ingress
105metadata:
106  name: dynamic-app-ingress
107  namespace: demo
108  annotations:
109    nginx.ingress.kubernetes.io/rewrite-target: /
110spec:
111  rules:
112  - host: dynamic-app.local
113    http:
114      paths:
115      - path: /
116        pathType: Prefix
117        backend:
118          service:
119            name: dynamic-app
120            port:
121              number: 8080

Configure Consul Connect Security Policies

Step 8: Create Consul Connect Intentions

Create Consul intentions to control service-to-service communication, just like in the Nomad example. Connect to your external Consul cluster to configure intentions:

 1# Set Consul address environment variable
 2export CONSUL_HTTP_ADDR="https://consul.example.com:8500"
 3
 4# Create intention to allow dynamic-app to connect to mysql-server
 5consul intention create -allow dynamic-app mysql-server
 6
 7# Verify intentions
 8consul intention list
 9
10# Optional: Create more granular intentions with deny-all default
11consul intention create -deny "*" "*"
12consul intention create -allow dynamic-app mysql-server

Alternatively, you can use Kubernetes CRDs for intentions:

 1# consul-intentions.yaml
 2apiVersion: consul.hashicorp.com/v1alpha1
 3kind: ServiceIntentions
 4metadata:
 5  name: mysql-server-intentions
 6  namespace: demo
 7spec:
 8  destination:
 9    name: mysql-server
10  sources:
11  - name: dynamic-app
12    action: allow

Advanced Configuration Options

Client-less Architecture Benefits

This deployment approach uses no Consul clients in Kubernetes, providing several advantages:

  1. Reduced Resource Usage: No Consul client pods consuming cluster resources
  2. Simplified Operations: Fewer components to monitor and maintain
  3. Direct Communication: Connect injector communicates directly with external Consul servers
  4. Better Security: No local Consul agents that could be compromised
  5. Centralized Management: All Consul operations happen on external cluster

How It Works Without Clients

The Consul Connect injector operates independently and:

  • Registers services directly with external Consul servers via HTTPS API
  • Retrieves Connect certificates directly from Consul servers
  • Manages proxy configurations without local Consul agents
  • Handles service discovery through external Consul API calls

Custom Connect Proxy Configuration

You can customize the Connect proxy configuration using annotations:

1annotations:
2  "consul.hashicorp.com/connect-inject": "true"
3  "consul.hashicorp.com/connect-service": "dynamic-app"
4  "consul.hashicorp.com/connect-service-port": "8080"
5  "consul.hashicorp.com/connect-proxy-cpu-request": "50m"
6  "consul.hashicorp.com/connect-proxy-memory-request": "64Mi"
7  "consul.hashicorp.com/connect-proxy-cpu-limit": "100m"
8  "consul.hashicorp.com/connect-proxy-memory-limit": "128Mi"
9  "consul.hashicorp.com/envoy-extra-args": "--log-level debug"

Health Checks Integration

Consul Connect can integrate with Kubernetes health checks:

1annotations:
2  "consul.hashicorp.com/service-sync": "true"
3  "consul.hashicorp.com/service-port": "8080"
4  "consul.hashicorp.com/connect-service-port": "8080"

Benefits of Using Consul Connect with Vault Agent Injector in Kubernetes

  1. Consistent Technology Stack: Use the same Consul Connect across Nomad and Kubernetes deployments
  2. End-to-End Encryption: All communication between services is encrypted using mTLS
  3. Zero Trust Networking: Services must be explicitly allowed to communicate via intentions
  4. Integrated Secret Management: Vault Agent Injector provides dynamic secrets without code changes
  5. Native Kubernetes Integration: Consul Connect runs natively in Kubernetes with CRD support
  6. Operational Familiarity: Same operational model as Nomad-based Consul Connect deployments

Comparison: Nomad vs Kubernetes with Consul Connect

FeatureNomad + Consul ConnectKubernetes + Consul Connect (Client-less)
Service RegistrationAutomatic via NomadDirect API calls to external Consul
Sidecar InjectionNative Nomad integrationKubernetes admission controller
ConfigurationJob specification HCLKubernetes annotations
Service DiscoveryConsul DNS/APIExternal Consul API calls
Health ChecksNomad + Consul checksKubernetes + External Consul API
Resource ManagementNomad schedulerKubernetes scheduler
Local Consul AgentsOptional with ConnectNone required (client-less)

Conclusion

Using HashiCorp Consul Connect with Vault Agent Injector in Kubernetes provides a familiar and powerful service mesh solution that maintains consistency with your existing HashiCorp infrastructure. This approach is particularly valuable for organizations already using Consul Connect with Nomad who want to extend the same security patterns to Kubernetes workloads.

Key advantages of this approach:

  • Technology Consistency: Same Consul Connect service mesh across orchestration platforms
  • Zero-Trust Security: Explicit service communication authorization via intentions
  • Comprehensive Secret Management: Dynamic secrets from Vault without application changes
  • Operational Familiarity: Consistent troubleshooting and monitoring across platforms
  • Gradual Migration: Maintain service mesh consistency while migrating from Nomad to Kubernetes
  • Resource Efficiency: Client-less architecture reduces Kubernetes resource consumption
  • Simplified Architecture: Direct communication with external Consul eliminates local agents

By leveraging Consul Connect in Kubernetes, you can ensure secure, authenticated, and encrypted communication between your services while maintaining the operational patterns and expertise your team has developed with HashiCorp's service mesh technology.

Start enhancing your Kubernetes deployments today by integrating Consul Connect with Vault Agent Injector, and maintain the same level of service mesh security and operational consistency across your entire infrastructure.

Go Back explore our courses

We are here for you

You are interested in our courses or you simply have a question that needs answering? You can contact us at anytime! We will do our best to answer all your questions.

Contact us