Terraform EKS configmaps is forbidden

9/24/2021

I am trying to deploy a Kubernetes cluster on AWS EKS using Terraform, run from a Gitlab CI pipeline. My code currently gets a full cluster up and running, except there is a step in which it tries to add the nodes (which are created separately) into the cluster.

When it tries to do this, this is the error I receive:

│ Error: configmaps is forbidden: User "system:serviceaccount:gitlab-managed-apps:default" cannot create resource "configmaps" in API group "" in the namespace "kube-system"
│   with module.mastercluster.kubernetes_config_map.aws_auth[0],
│   on .terraform/modules/mastercluster/aws_auth.tf line 63, in resource "kubernetes_config_map" "aws_auth":
│   63: resource "kubernetes_config_map" "aws_auth" {

Terraform I believe is trying to edit the configmap aws_auth in the kube-system namespace, but for whatever reason, it doesn't have permission to do so?

I have found a different answer from years ago on Stackoverflow, that currently matches with what the documentation has to say about adding a aws_eks_cluster_auth data source and adding this to the kubernetes provider.

My configuration of this currently looks like this:

data "aws_eks_cluster" "mastercluster" {
	name 	= module.mastercluster.cluster_id
}

data "aws_eks_cluster_auth" "mastercluster" {
	name 	= module.mastercluster.cluster_id
}

provider "kubernetes" {
	alias 	= "mastercluster"

	host					= data.aws_eks_cluster.mastercluster.endpoint
	cluster_ca_certificate 	= base64decode(data.aws_eks_cluster.mastercluster.certificate_authority[0].data)
	token 					= data.aws_eks_cluster_auth.mastercluster.token

	load_config_file		= false
}

The weird thing is, this has worked for me before. I have successfully deployed multiple clusters using this method. This configuration is an almost identical copy to another one I had before, only the names of the clusters are different. I am totally lost as to why this can possibly go wrong.

-- Quenten Schoonderwoerd
amazon-eks
amazon-web-services
gitlab-ci
kubernetes
terraform

1 Answer

9/26/2021

Use semver to lock hashicorp provider versions

That's why is so important to use semver in terraform manifests.

As per Terraform documentation:

Terraform providers manage resources by communicating between Terraform and target APIs. Whenever the target APIs change or add functionality, provider maintainers may update and version the provider.

When multiple users or automation tools run the same Terraform configuration, they should all use the same versions of their required providers.

Use RBAC rules for Kubernetes

There is a Github issue filed about this: v2.0.1: Resources cannot be created. Does kubectl reference to kube config properly? · Issue #1127 · hashicorp/terraform-provider-kubernetes with the same error message as in yours case.

And one of the comments answers:

Offhand, this looks related to RBAC rules in the cluster (which may have been installed by the helm chart). This command might help diagnose the permissions issues relating to the service account in the error message.

$ kubectl auth can-i create namespace --as=system:serviceaccount:gitlab-prod:default
$ kubectl auth can-i --list --as=system:serviceaccount:gitlab-prod:default

You might be able to compare that list with other users on the cluster:

kubectl auth can-i --list --namespace=default --as=system:serviceaccount:default:default
$ kubectl auth can-i create configmaps
yes

$ kubectl auth can-i create configmaps --namespace=nginx-ingress --as=system:serviceaccount:gitlab-prod:default
no

And investigate related clusterroles:

$ kube describe clusterrolebinding system:basic-user
Name:         system:basic-user
Labels:       kubernetes.io/bootstrapping=rbac-defaults
Annotations:  rbac.authorization.kubernetes.io/autoupdate: true
Role:
  Kind:  ClusterRole
  Name:  system:basic-user
Subjects:
  Kind   Name                  Namespace
  ----   ----                  ---------
  Group  system:authenticated


$ kubectl describe clusterrole system:basic-user
Name:         system:basic-user
Labels:       kubernetes.io/bootstrapping=rbac-defaults
Annotations:  rbac.authorization.kubernetes.io/autoupdate: true
PolicyRule:
  Resources                                      Non-Resource URLs  Resource Names  Verbs
  ---------                                      -----------------  --------------  -----
  selfsubjectaccessreviews.authorization.k8s.io  []                 []              [create]
  selfsubjectrulesreviews.authorization.k8s.io   []                 []              [create]

My guess is that the chart or Terraform config in question is responsible for creating the service account, and the [cluster] roles and rolebindings, but it might be doing so in the wrong order, or not idempotently (so you get different results on re-install vs the initial install). But we would need to see a configuration that reproduces this error. In my testing of version 2 of the providers on AKS, EKS, GKE, and minikube, I haven't seen this issue come up.

Feel free to browse these working examples of building specific clusters and using them with Kubernetes and Helm providers. Giving the config a skim might give you some ideas for troubleshooting further.

Howto solve RBAC issues

As for the error

Error: configmaps is forbidden: User "system:serviceaccount:kube-system:default" cannot list

There is great explanation by @m-abramovich:

First, some information for newbies.
In Kubernetes there are:

  • Account - something like your ID. Example: john
  • Role - some group in the project permitted to do something. Examples: cluster-admin, it-support, ...
  • Binding - joining Account to Role. "John in it-support" - is a binding.

Thus, in our message above, we see that our Tiller acts as account "default" registered at namespace "kube-system". Most likely you didn't bind him to a sufficient role.

Now back to the problem.
How do we track it:

  • check if you have specific account for tiller. Usually it has same name - "tiller":
    kubectl [--namespace kube-system] get serviceaccount
    create if not:
    kubectl [--namespace kube-system] create serviceaccount tiller
  • check if you have role or clusterrole (cluster role is "better" for newbies - it is cluster-wide unlike namespace-wide role). If this is not a production, you can use highly privileged role "cluster-admin":
    kubectl [--namespace kube-system] get clusterrole
    you can check role content via:
    kubectl [--namespace kube-system] get clusterrole cluster-admin -o yaml
  • check if account "tiller" in first clause has a binding to clusterrole "cluster-admin" that you deem sufficient:
    kubectl [--namespace kube-system] get clusterrolebinding
    if it is hard to figure out based on names, you can simply create new:
    kubectl [--namespace kube-system] create clusterrolebinding tiller-cluster-admin --clusterrole=cluster-admin --serviceaccount=kube-system:tiller
  • finally, when you have the account, the role and the binding between them, you can check if you really act as this account:
    kubectl [--namespace kube-system] get deploy tiller-deploy -o yaml

I suspect that your output will not have settings "serviceAccount" and "serviceAccountName":

dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
terminationGracePeriodSeconds: 30

if yes, than add an account you want tiller to use:
kubectl [--namespace kube-system] patch deploy tiller-deploy -p '{"spec":{"template":{"spec":{"serviceAccount":"tiller"}}}}'
(if you use PowerShell, then check below for post from @snpdev)
Now you repeat previous check command and see the difference:

dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
serviceAccount: tiller                     <-- new line
serviceAccountName: tiller          <-- new line
terminationGracePeriodSeconds: 30

Resources:

-- Yasen
Source: StackOverflow