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:
- Realm9 Backend Service Account (
realm9): Used by the Realm9 application to manage Terraform job pods. Has minimal permissions for job lifecycle management. - 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
- Navigate to Terraform → Workspaces
- Select your workspace
- Go to the Settings tab
- Find the Terraform Service Account field
- Enter the service account name (e.g.,
terraform-k8s-admin) - 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 devterraform-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:
- Add the service account YAML to your GitOps repository
- Sync the changes to your cluster
- Configure the workspace in Realm9 to use the new service account
- 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.).
