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 TerraformWorkspaces
  2. Select your workspace
  3. Go to the Settings 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.).

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.