Where is the key? - terraform

resource "google_service_account" "myaccount" {
account_id = "dev-foo-account"
}
resource "google_service_account_key" "mykey" {
service_account_id = google_service_account.myaccount.name
}
data "google_service_account_key" "mykey" {
name = google_service_account_key.mykey.name
public_key_type = "TYPE_X509_PEM_FILE"
}
If I create a Service Account and a key like this - how do I obtain the key afterwards?
terraform output yields:
$ terraform output -json google_service_account_key
The output variable requested could not be found in the state
file. If you recently added this to your configuration, be
sure to run `terraform apply`, since the state won't be updated
with new output variables until that command is run.

You have to put that variable as an output if you want to use it after apply the plan:
output "my_private_key" {
value = data.google_service_account_key.mykey.private_key
}
To output the value of "my_private_key":
$ terraform output my_private_key
To obtain the credentials as a JSON which can later be used for authentication:
$ terraform output -raw key | base64 -d -

Related

How to create azurerm_resourcegroup through terraform only when it does not exist in Azure?

I want my terraform script to create the resource group only when it does not exist in Azure, otherwise it should skip the creation of resource group.
Well, you can use Terraform external to execute the CLI command to check if the resource group exists or not. And then use the result to determine whether the resource group will create. Here is an example:
./main.tf
provider "azurerm" {
features {}
}
variable "group_name" {}
variable "location" {
default = "East Asia"
}
data "external" "example" {
program = ["/bin/bash","./script.sh"]
query = {
group_name = var.group_name
}
}
resource "azurerm_resource_group" "example" {
count = data.external.example.result.exists == "true" ? 0 : 1
name = var.group_name
location = var.location
}
./script.sh
#!/bin/bash
eval "$(jq -r '#sh "GROUP_NAME=\(.group_name)"')"
result=$(az group exists -n $GROUP_NAME)
jq -n --arg exists "$result" '{"exists":$exists}'
Terraform is declarative, not imperative. When using Terraform you shouldn't need to check for existing resources
to validate your tf script
terraform plan
and to apply the tf script changes
terraform apply
This will validate the resources if it already exists and create if not

How to generate file and re-use its content with terrafrom

I would like to generate ssh keys with a local-exec then read the content of the file.
resource "null_resource" "generate-ssh-keys-pair" {
provisioner "local-exec" {
command = <<EOT
ssh-keygen -t rsa -b 4096 -C "test" -P "" -f "testkey"
EOT
}
}
data "local_file" "public-key" {
depends_on = [null_resource.generate-ssh-keys-pair]
filename = "testkey.pub"
}
data "local_file" "private-key" {
depends_on = [null_resource.generate-ssh-keys-pair]
filename = "testkey"
}
terraform plan works but when I run the apply, I got error on testkey and testkey.pub don't exist.
Thanks
Instead of generating a file using an external command and then reading it in, I would suggest to use the Terraform tls provider to generate the key within Terraform itself, using tls_private_key:
terraform {
required_providers {
tls = {
source = "hashicorp/tls"
}
}
}
resource "tls_private_key" "example" {
algorithm = "RSA"
rsa_bits = 4096
}
The tls_private_key resource type exports two attributes that are equivalent to the two files you were intending to read in your example:
tls_private_key.example.private_key_pem: the private key in PEM format
tls_private_key.example.public_key_openssh: the public key in the format OpenSSH expects to find in .ssh/authorized_keys.
Please note the warning in the tls_private_key documentation that using this resource will cause the private key data to be saved in your Terraform state, and so you should protect that state data accordingly. That would also have been true for your approach of reading files from disk using data resources, because any value Terraform has available for use in expressions must always be stored in the state.
I run your code and there are no problems with it. It correctly generates testkey and testkey.pub.
So whatever causes it to fail for you, its not this snipped of code you've provided in the question. The fault must be outside the code snipped.
Generating an SSH key in Terraform is inherently insecure because it gets stored in the tfstate file, however I had a similar problem to solve and thought that the most secure/usable was to use a secret management service + using a cloud bucket for the backend:
terraform {
required_providers {
google = {
source = "hashicorp/google"
version = "4.3.0"
}
}
backend "gcs" {
bucket = "tfstatebucket"
prefix = "terraform/production"
}
}
//Import Ansible private key from Google Secrets Manager
resource "google_secret_manager_secret" "ansible_private_key" {
secret_id = var.ansible_private_key_secret_id
replication {
automatic = true
}
}
data "google_secret_manager_secret_version" "ansible_private_key"{
secret = google_secret_manager_secret.ansible_private_key.secret_id
version = var.ansible_private_key_secret_id_version_number
}
resource "local_file" "ansible_imported_local_private_key" {
sensitive_content = data.google_secret_manager_secret_version.ansible_private_key.secret_data
filename = var.ansible_imported_local_private_key_filename
file_permission = "0600"
}
In the case of GCP, I would add the secret in Google Secrets Manager, then use terraform import on the secret, which will in turn write it to the backend bucket. That way it doesn't get stored in Git as plain text, you can have the key file local to your project (.terraform shouldn't be under version control), and it's arguably more secure in the bucket.
So the workflow essentially is:
Human --> Secret Manager
Secret Manager --> Terraform Import --> GCS Bucket Backend
|--> Create .terraform/ssh_key
.terraform/ssh_key --> Terraform/Ansible/Whatever
Hashicorp Vault would be another way to address this

How to extract sensitive output variables in terraform?

I have a terraform config which creates an AWS IAM user with an access key, and I assign both id and secret to output variables:
...
resource "aws_iam_access_key" "brand_new_user" {
user = aws_iam_user.brand_new_user.name
}
output "brand_new_user_id" {
value = aws_iam_access_key.brand_new_user.id
}
output "brand_new_user_secret" {
value = aws_iam_access_key.brand_new_user.encrypted_secret
sensitive = true
}
Here brand_new_user_secret is declared as sensitive, so terraform output obviously does not print it.
Is there any way to get its output value without parsing the whole state file?
Trying to access it directly (terraform output brand_new_user_secret) does not work (results in an error "The output variable requested could not be found in the state file...").
Terraform version: 0.12.18
I had some hopes to avoid it, but so far I did not find a better way than parse terraform state:
terraform state pull | jq '.resources[] | select(.type == "aws_iam_access_key") | .instances[0].attributes'
which would result in a structure similar to:
{
"encrypted_secret": null,
"id": "....",
"key_fingerprint": null,
"pgp_key": null,
"secret": "....",
"ses_smtp_password": "....",
"ses_smtp_password_v4": null,
"status": "Active",
"user": "...."
}
To see the sensitive value interactively, i.e. for the purposes of analyzing/debugging the state, you can use the Terraform's console command and nonsensitive() function:
$ terraform console
> nonsensitive(aws_iam_access_key.brand_new_user.encrypted_secret)
You may need to use other functions to decode/manipulate the value before printing it.
The output command does print a sensitive output if a single output is specified. This is using Terraform v1.1.7 and 0.14.8
terraform output brand_new_user_secret
The response I get when not specifying an attribute name is:
brand_new_user_secret = (sensitive)
whereas the question said this produced an error, so the behaviour may have changed since 0.12.
See https://www.terraform.io/cli/commands/output
I'm using a hacky workaround here like this...
resource "aws_iam_access_key" "brand_new_user" {
user = aws_iam_user.brand_new_user.name
}
output "brand_new_user_id" {
value = aws_iam_access_key.brand_new_user.id
}
data "template_file" "secret" {
template = aws_iam_access_key.brand_new_user.encrypted_secret
}
output "brand_new_user_secret" {
value = data.template_file.secret.rendered
}
I haven't tried it, but the docs seem to suggest that if you want to output encrypted_secret you must supply a pgp_key to the aws_iam_access_key resource:
pgp_key - (Optional) Either a base-64 encoded PGP public key, or a keybase username in the form keybase:some_person_that_exists, for use in the encrypted_secret output attribute.
encrypted_secret - The encrypted secret, base64 encoded, if pgp_key was specified. ~> NOTE: The encrypted secret may be decrypted using the command line, for example: terraform output encrypted_secret | base64 --decode | keybase pgp decrypt.
https://www.terraform.io/docs/providers/aws/r/iam_access_key.html
I think the recommended way to do this with modern Terraform (v1.1+) is to use terraform output -json [<name>]. The -json switch bypasses Terraform's sensitive value masking.
For example, with Terraform 1.2.7, to list and unmask all outputs:
$ terraform output -json
{
"app_database_password": {
"sensitive": true,
"type": "string",
"value": "..."
}
}
Or, if you just want a single output's value without needing to parse JSON (n.b. still seems to be enclosed in quotes):
$ terraform output -json app_database_password
"..."

Terraform - Using Local Command Result as an Variable for tf file

Is there a way to use local-exec to generate an output for a variable inside of Terraform .tf file?
data-external feature of Terraform has helped me
cat owner.sh
jq -n --arg username $(git config user.name) '{"username": $username}'
The config part which must be added on instance_create.tf files;
data "external" "owner_tag_generator" {
program = ["bash", "/full/path/of/owner.sh"]
}
output "owner" {
value = "${data.external.owner_tag_generator.result}"
}
tags {
...
CreatorName = "${data.external.owner_tag_generator.result.username}"
...
}

Is it possible to access module state in a terraform remote state file?

If a terraform script uses a module that has outputs, it's possible to access those module outputs in using the -module option for the terraform output command:
$ terraform output --help
Usage: terraform output [options] [NAME]
Reads an output variable from a Terraform state file and prints
the value. If NAME is not specified, all outputs are printed.
Options:
-state=path Path to the state file to read. Defaults to
"terraform.tfstate".
-no-color If specified, output won't contain any color.
-module=name If specified, returns the outputs for a
specific module
-json If specified, machine readable output will be
printed in JSON format
If I store that state file in S3 or some such, I can then reference the outputs of the main script by using the terraform_remote_state data provider.
data "terraform_remote_state" "base_networking" {
backend = "s3"
config {
bucket = "${var.remote_state_bucket}"
region = "${var.remote_state_region}"
key = "${var.networking_remote_state_key}"
}
}
resource "aws_instance" "my_instance" {
subnets = "${data.terraform_remote_state.base_networking.vpc_id}"
}
Is it possible to access the module outputs that are present in the state file as well? I'm looking for something like "${data.terraform_remote_state.base_networking.module.<module_name>.<output>}" or similar.
Yes, you can access remote state outputs from your own modules. You just need to "propagate" the outputs.
E.g., let's say you have something like this, your base_networking infrastructure, which contains a module for creating your VPC, and you want that VPC ID to be accessible via remote state:
base_networking/
main.tf
outputs.tf
vpc/
main.tf
outputs.tf
In base_networking/main.tf you create your VPC using your base_networking/vpc module:
module "vpc" {
source = "./vpc"
region = "${var.region}"
name = "${var.vpc_name}"
cidr = "${var.vpc_cidr}"
}
In base_networking/vpc/outputs.tf in your module you have an id output:
output "id" {
value = "${aws_vpc.vpc.id}"
}
In base_networking/outputs.tf you also have a vpc_id output that propagates module.vpc.id:
output "vpc_id" {
value = "${module.vpc.id}"
With that you can now access vpc_id using something like:
data "terraform_remote_state" "base_networking" {
backend = "s3"
config = {
bucket = "${var.remote_state_bucket}"
region = "${var.remote_state_region}"
key = "${var.networking_remote_state_key}"
}
}
[...]
vpc_id = "${data.terraform_remote_state.base_networking.vpc_id}"

Resources