Realm9 Logo
Search documentation...

Terraform Service Accounts

Learn how to configure custom Kubernetes service accounts for your Terraform workspaces to manage permissions when creating Kubernetes resources.

Overview

Realm9 uses a two-tier service account architecture for Terraform operations:

  1. Realm9 Backend Service Account (realm9): Used by the Realm9 application to manage Terraform job pods. Has minimal permissions for job lifecycle management.
  2. Terraform Job Service Account: Used by the actual Terraform job pods to create cloud and Kubernetes resources. Can be customized per workspace.

This separation provides better security by following the principle of least privilege - each component only has the permissions it needs.

When Do You Need a Custom Service Account?

✅ You NEED a custom service account if your Terraform code:

  • Creates or manages Kubernetes resources (Deployments, Services, ConfigMaps, etc.)
  • Creates or manages namespaces
  • Deploys Helm charts that create Kubernetes resources
  • Manages RBAC resources (Roles, RoleBindings, ServiceAccounts)
  • Creates Custom Resource Definitions (CRDs)
  • Uses the Kubernetes Terraform provider to provision infrastructure

❌ You DON'T need a custom service account if your Terraform code:

  • Only provisions cloud resources (EC2, S3, RDS, Lambda, etc.)
  • Only uses cloud providers (AWS, Azure, GCP) without Kubernetes
  • Doesn't interact with the Kubernetes cluster at all

The default realm9 service account has permissions to manage Terraform jobs but cannot create Kubernetes resources for security reasons.

How to Configure a Custom Service Account

Step 1: Create the Service Account in Your Cluster

Create a YAML file with the service account, role, and role binding. Here's a basic example:

---
# ServiceAccount for Terraform jobs
apiVersion: v1
kind: ServiceAccount
metadata:
  name: terraform-k8s-admin
  namespace: terraform-runs

---
# ClusterRole with permissions for Kubernetes resources
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: terraform-k8s-admin-role
rules:
  # Namespace management
  - apiGroups: [""]
    resources: ["namespaces"]
    verbs: ["get", "list", "create", "update", "patch", "delete"]

  # Core resources
  - apiGroups: [""]
    resources: ["pods", "services", "configmaps", "secrets", "serviceaccounts"]
    verbs: ["get", "list", "create", "update", "patch", "delete"]

  # Deployments and StatefulSets
  - apiGroups: ["apps"]
    resources: ["deployments", "statefulsets", "daemonsets", "replicasets"]
    verbs: ["get", "list", "create", "update", "patch", "delete"]

  # Ingress
  - apiGroups: ["networking.k8s.io"]
    resources: ["ingresses"]
    verbs: ["get", "list", "create", "update", "patch", "delete"]

---
# Bind the role to the service account
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: terraform-k8s-admin-binding
subjects:
  - kind: ServiceAccount
    name: terraform-k8s-admin
    namespace: terraform-runs
roleRef:
  kind: ClusterRole
  name: terraform-k8s-admin-role
  apiGroup: rbac.authorization.k8s.io

Apply it to your cluster:

kubectl apply -f terraform-service-account.yaml

Step 2: Configure the Workspace

  1. Navigate to Terraform → Workspaces
  2. Select your workspace, then open its Settings page
  3. On the General tab
  4. Find the Terraform Service Account field
  5. Enter the service account name (e.g., terraform-k8s-admin)
  6. Click Save Changes

The system will validate that the service account exists in the cluster before saving.

Step 3: Run Terraform

When you trigger a Terraform plan or apply, the job will use your custom service account instead of the default realm9 account. You can verify this in the job pod spec.

Example: Namespace-Scoped Permissions

If you want to restrict Terraform to only create resources in specific namespaces, use a Role instead of ClusterRole:

---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: terraform-app-deployer
  namespace: terraform-runs

---
# Role scoped to 'production' namespace
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: terraform-app-deployer-role
  namespace: production  # Only works in this namespace
rules:
  - apiGroups: ["apps"]
    resources: ["deployments"]
    verbs: ["get", "list", "create", "update", "patch", "delete"]
  - apiGroups: [""]
    resources: ["services", "configmaps"]
    verbs: ["get", "list", "create", "update", "patch", "delete"]

---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: terraform-app-deployer-binding
  namespace: production
subjects:
  - kind: ServiceAccount
    name: terraform-app-deployer
    namespace: terraform-runs
roleRef:
  kind: Role
  name: terraform-app-deployer-role
  apiGroup: rbac.authorization.k8s.io

This limits the Terraform job to only create resources in the production namespace.

Example: Helm Chart Deployments

If you're using Terraform to deploy Helm charts, you'll need broader permissions:

---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: terraform-helm-deployer
  namespace: terraform-runs

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: terraform-helm-deployer-role
rules:
  # Helm needs to read CRDs
  - apiGroups: ["apiextensions.k8s.io"]
    resources: ["customresourcedefinitions"]
    verbs: ["get", "list"]

  # Common Helm resources
  - apiGroups: [""]
    resources: ["*"]
    verbs: ["*"]
  - apiGroups: ["apps"]
    resources: ["*"]
    verbs: ["*"]
  - apiGroups: ["batch"]
    resources: ["*"]
    verbs: ["*"]
  - apiGroups: ["networking.k8s.io"]
    resources: ["*"]
    verbs: ["*"]

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: terraform-helm-deployer-binding
subjects:
  - kind: ServiceAccount
    name: terraform-helm-deployer
    namespace: terraform-runs
roleRef:
  kind: ClusterRole
  name: terraform-helm-deployer-role
  apiGroup: rbac.authorization.k8s.io

Security Best Practices

1. Principle of Least Privilege

Grant only the permissions your Terraform code actually needs. Don't use ["*"] verbs unless absolutely necessary.

2. Use Namespace-Scoped Roles When Possible

Prefer Role over ClusterRole to limit blast radius.

3. Separate Service Accounts by Environment

Create different service accounts for dev, staging, and production workspaces:

  • terraform-dev-deployer - broader permissions for dev
  • terraform-prod-deployer - restricted permissions for production

4. Regular Audits

Review service account permissions quarterly and remove unused permissions.

5. Test in Development First

Always test new service accounts and permissions in a development workspace before using in production.

Troubleshooting

Error: "Service account not found"

Cause: The service account doesn't exist in the terraform-runs namespace.

Solution:

# Check if SA exists
kubectl get sa -n terraform-runs

# Create the service account using your YAML file
kubectl apply -f your-sa-config.yaml

Error: "Forbidden: User cannot create resource 'deployments'"

Cause: The service account doesn't have sufficient permissions.

Solution: Update the ClusterRole/Role to include the missing permission:

- apiGroups: ["apps"]
  resources: ["deployments"]
  verbs: ["get", "list", "create", "update", "patch", "delete"]

Verify Permissions

Check what a service account can do:

# Check if SA can create deployments
kubectl auth can-i create deployments \
  --as=system:serviceaccount:terraform-runs:terraform-k8s-admin \
  -n production

# Should return "yes" if permissions are correct

Using with ArgoCD/GitOps

If you manage your cluster with ArgoCD or similar GitOps tools:

  1. Add the service account YAML to your GitOps repository
  2. Sync the changes to your cluster
  3. Configure the workspace in Realm9 to use the new service account
  4. Run a Terraform plan to verify

FAQ

Q: Can I use the same service account for multiple workspaces? A: Yes! Service accounts can be shared across workspaces. This is useful for workspaces that deploy similar types of resources.

Q: What happens if I don't specify a service account? A: The workspace will use the default realm9 service account, which can only manage Terraform jobs but cannot create Kubernetes resources.

Q: Can I change the service account after creating the workspace? A: Yes, you can update the service account in the workspace settings at any time.

Q: Do I need to restart anything after changing the service account? A: No, the new service account will be used for the next Terraform run automatically.

Q: Can I use service accounts for cloud resources (AWS, Azure, GCP)? A: Custom Kubernetes service accounts only control permissions within the Kubernetes cluster. Cloud resource permissions are managed through cloud connections (IAM roles, service principals, etc.).