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.).
