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.).
Related Documentation
Need Help?
If you're stuck or have questions about service account configuration, check the troubleshooting guide in /docs/TERRAFORM-RBAC-TROUBLESHOOTING.md in the repository or contact support.
