EKS Terraform - datasource aws_eks_cluster_auth - token expired - terraform

I've an EKS cluster deployed in AWS and I use terraform to deploy components to that cluster.
In order to get authenticated I'm using the following EKS datasources that provides the cluster API Authentication:
data "aws_eks_cluster_auth" "cluster" {
name = var.cluster_id
}
data "aws_vpc" "eks_vpc" {
id = var.vpc_id
}
And using the token inside several local-exec provisioners (apart of other resources) to deploy components
resource "null_resource" "deployment" {
provisioner "local-exec" {
working_dir = path.module
command = <<EOH
kubectl \
--server="${data.aws_eks_cluster.cluster.endpoint}" \
--certificate-authority=./ca.crt \
--token="${data.aws_eks_cluster_auth.cluster.token}" \
apply -f test.yaml
EOH
}
}
The problem I have is that some resources are taking a little while to deploy and at some point when terraform executes the next resource I get this error because the token has expired:
exit status 1. Output: error: You must be logged in to the server (the server has asked for the client to provide credentials)
Is there a way to force re-creation of the data before running the local-execs?

UPDATE: example moved to https://github.com/aidanmelen/terraform-kubernetes-rbac/blob/main/examples/authn_authz/main.tf
The data.aws_eks_cluster_auth.cluster_auth.token creates a token with a non-configurable 15 minute timeout.
One way to get around this is to use the sts token to create a long-lived service-account token and use that to provision the terraform-kubernetes-provider for long running kuberenetes resources.
I created a module called terraform-kubernetes-service-account to capture this common behavior of creating a service account, giving it some permissions, and output the auth information i.e. token, ca.crt, namespace.
For example:
module "terraform_admin" {
source = "aidanmelen/service-account/kubernetes"
name = "terraform-admin"
namespace = "kube-system"
cluster_role_name = "terraform-admin"
cluster_role_rules = [
{
api_groups = ["*"]
resources = ["*"]
resource_names = ["*"]
verbs = ["*"]
},
]
}
provider "kubernetes" {
alias = "terraform_admin_service_account"
host = "https://kubernetes.docker.internal:6443"
cluster_ca_certificate = module.terraform_admin.auth["ca.crt"]
token = module.terraform_admin.auth["token"]
}
data "kubernetes_namespace_v1" "example" {
metadata {
name = kubernetes_namespace.ex_complete.metadata[0].name
}
}

Related

Assign object to Terraform provider configuration

A private Terraform module outputs an object with three properties "host", "token" and "cluster_ca_certificate". The kubernetes provider and the kubernetes section of the helm provider accept the same property names. Unfortunately, as far as I can tell, I cannot e.g. assign the output object to them, so that I don't need to repeat myself:
provider "kubernetes" = module.kubernetes.configuration
provider "helm" {
kubernetes = module.kubernetes.configuration
}
Something like that I would prefer over the much more repetitive and error prone:
provider "kubernetes" {
host = module.kubernetes.configuration.host
token = module.kubernetes.configuration.token
cluster_ca_certificate = module.kubernetes.configuration.cluster_ca_certificate
}
provider "helm" {
kubernetes {
host = module.kubernetes.configuration.host
token = module.kubernetes.configuration.token
cluster_ca_certificate = module.kubernetes.configuration.cluster_ca_certificate
}
}
Am I missing something? Can this be simplified?

Is there a way to retrieve data from another Account in Terraform?

I want to use the resource "data" in Terraform for example for an sns topic but I don't want too look for a resource in the aws-account, for which I'm deploying my other resources. It should look up to my other aws-account (in the same organization) and find resources in there. Is there a way to make this happen?
data "aws_sns_topic" "topic_alarms_data" {
name = "topic_alarms"
}
Define an aws provider with credentials to the remote account:
# Default provider that you use:
provider "aws" {
region = var.context.aws_region
assume_role {
role_arn = format("arn:aws:iam::%s:role/TerraformRole", var.account_id)
}
}
provider "aws" {
alias = "remote"
region = var.context.aws_region
assume_role {
role_arn = format("arn:aws:iam::%s:role/TerraformRole", var.remote_account_id)
}
}
data "aws_sns_topic" "topic_alarms_data" {
provider = aws.remote
name = "topic_alarms"
}
Now the topics are loaded from the second provider.

Terraform apply fails because kubernetes provider runs as user "client" that has no permissions [duplicate]

I can use terraform to deploy a Kubernetes cluster in GKE.
Then I have set up the provider for Kubernetes as follows:
provider "kubernetes" {
host = "${data.google_container_cluster.primary.endpoint}"
client_certificate = "${base64decode(data.google_container_cluster.primary.master_auth.0.client_certificate)}"
client_key = "${base64decode(data.google_container_cluster.primary.master_auth.0.client_key)}"
cluster_ca_certificate = "${base64decode(data.google_container_cluster.primary.master_auth.0.cluster_ca_certificate)}"
}
By default, terraform interacts with Kubernetes with the user client, which has no power to create (for example) deployments. So I get this error when I try to apply my changes with terraform:
Error: Error applying plan:
1 error(s) occurred:
* kubernetes_deployment.foo: 1 error(s) occurred:
* kubernetes_deployment.foo: Failed to create deployment: deployments.apps is forbidden: User "client" cannot create deployments.apps in the namespace "default"
I don't know how should I proceed now, how should I give this permissions to the client user?
If the following fields are added to the provider, I am able to perform deployments, although after reading the documentation it seems these credentials are used for HTTP communication with the cluster, which is insecure if it is done through the internet.
username = "${data.google_container_cluster.primary.master_auth.0.username}"
password = "${data.google_container_cluster.primary.master_auth.0.password}"
Is there any other better way of doing so?
you can use the service account that are running the terraform
data "google_client_config" "default" {}
provider "kubernetes" {
host = "${google_container_cluster.default.endpoint}"
token = "${data.google_client_config.default.access_token}"
cluster_ca_certificate = "${base64decode(google_container_cluster.default.master_auth.0.cluster_ca_certificate)}"
load_config_file = false
}
OR
give permissions to the default "client"
But you need a valid authentication on GKE cluster provider to run this :/ ups circular dependency here
resource "kubernetes_cluster_role_binding" "default" {
metadata {
name = "client-certificate-cluster-admin"
}
role_ref {
api_group = "rbac.authorization.k8s.io"
kind = "ClusterRole"
name = "cluster-admin"
}
subject {
kind = "User"
name = "client"
api_group = "rbac.authorization.k8s.io"
}
subject {
kind = "ServiceAccount"
name = "default"
namespace = "kube-system"
}
subject {
kind = "Group"
name = "system:masters"
api_group = "rbac.authorization.k8s.io"
}
}
It looks like the user that you are using is missing the required RBAC role for creating deployments. Make sure that user has the correct verbs for the deployments resource. You can take a look at this Role examples to have an idea about it.
You need to provide both. Check this example on how to integrate the Kubernetes provider with the Google Provider.
Example of how to configure the Kubernetes provider:
provider "kubernetes" {
host = "${var.host}"
username = "${var.username}"
password = "${var.password}"
client_certificate = "${base64decode(var.client_certificate)}"
client_key = "${base64decode(var.client_key)}"
cluster_ca_certificate = "${base64decode(var.cluster_ca_certificate)}"
}

Terraform provision to multiple accounts

I use terraform to deploy lambda to one aws account and s3 trigger for lambda in other. Because of that, I created two separate folders and each of them holds state of specific account.
However, I'd like to merge everything into one template. Is it possible to do it? Example:
provider "aws" {
profile = "${var.aws_profile}"
region = "eu-west-1"
}
provider "aws" {
alias = "bucket-trigger-account"
region = "eu-west-1"
profile = "${var.aws_bucket_trigger_profile}
}
I want thie following resource to be provisioned by aws bucket-trigger-account. How can I do it?
resource "aws_s3_bucket_notification" "bucket_notification" {
bucket = "${var.notifications_bucket}"
lambda_function {
lambda_function_arn = "arn:aws:lambda:eu-west-1-xxx"
events = ["s3:ObjectCreated:*"]
filter_suffix = ".test"
}
}
Found out that simply using provider argument on resource let's you use a different provider for that resource: provider = "aws.bucket-trigger-account"

Referencing Azure ACS Cluster Master VM Public IP?

I'm using this repo to create a kubernetes cluster on Azure using acs-engine.
I am wondering if anyone can help me identify how to reference the master VM's public IP address.
This would be used to ssh into the master VM (ssh user#public-ip), which is important because I want to run local-exec provisioners to configure my cluster with Ansible.
I don't believe that it is the first_master_ip in the below main.tf (this is given a value on the repo's variables.tf), though I also don't know how to reference this IP as well.
One other thing that I have tried is to obtain the master VM public IP address using the azure command line, however I also haven't had any success with this because I don't know how to get the cluster-name, which would be passed in with az acs kubernetes browse -g <resource-group-name> -n <cluster-name>
Any help would be greatly greatly appreciated as I've really hit a road block with this.
provider "azurerm" {
subscription_id = "${var.azure_subscription_id}"
client_id = "${var.azure_client_id}"
client_secret = "${var.azure_client_secret}"
tenant_id = "${var.azure_tenant_id}"
}
# Azure Resource Group
resource "azurerm_resource_group" "default" {
name = "${var.resource_group_name}"
location = "${var.azure_location}"
}
resource "azurerm_public_ip" "test" {
name = "acceptanceTestPublicIp1"
location = "${var.azure_location}"
resource_group_name = "${azurerm_resource_group.default.name}"
public_ip_address_allocation = "static"
}
data "template_file" "acs_engine_config" {
template = "${file(var.acs_engine_config_file)}"
vars {
master_vm_count = "${var.master_vm_count}"
dns_prefix = "${var.dns_prefix}"
vm_size = "${var.vm_size}"
first_master_ip = "${var.first_master_ip}"
worker_vm_count = "${var.worker_vm_count}"
admin_user = "${var.admin_user}"
ssh_key = "${var.ssh_key}"
service_principle_client_id = "${var.azure_client_id}"
service_principle_client_secret = "${var.azure_client_secret}"
}
}
# Locally output the rendered ACS Engine Config (after substitution has been performed)
resource "null_resource" "render_acs_engine_config" {
provisioner "local-exec" {
command = "echo '${data.template_file.acs_engine_config.rendered}' > ${var.acs_engine_config_file_rendered}"
}
depends_on = ["data.template_file.acs_engine_config"]
}
# Locally run the ACS Engine to produce the Azure Resource Template for the K8s cluster
resource "null_resource" "run_acs_engine" {
provisioner "local-exec" {
command = "acs-engine generate ${var.acs_engine_config_file_rendered}"
}
depends_on = ["null_resource.render_acs_engine_config"]
}
I have no experience with terraform but acs-engine sets up a lb with a public ip that goes through your master (or balances across multiple masters). You find the ip of that lb by using <dns_prefix>.<region>.cloudapp.azure.com.
But if you need the ip to provision something extra, this won't be enough when you have multiple masters.

Resources