From: Luis Chamberlain <mcgrof@kernel.org>
To: Chuck Lever <cel@kernel.org>, Daniel Gomez <da.gomez@kruces.com>,
kdevops@lists.linux.dev
Cc: Luis Chamberlain <mcgrof@kernel.org>
Subject: [PATCH v4 1/8] aws: prevent SSH key conflicts across multiple kdevops directories
Date: Tue, 16 Sep 2025 17:34:42 -0700 [thread overview]
Message-ID: <20250917003451.2318229-2-mcgrof@kernel.org> (raw)
In-Reply-To: <20250917003451.2318229-1-mcgrof@kernel.org>
Problem:
When using kdevops in multiple directories for AWS deployments, all instances
share the same hardcoded SSH key name "kdevops_aws_key" in the AWS backend.
This creates a critical issue: if different directories use different SSH keys
but the same key name, terraform will silently replace the existing key in AWS,
immediately locking users out of any running instances that were using the
previous key.
Root Cause:
The AWS terraform configuration uses a fixed ssh_keyname variable that defaults
to "kdevops_aws_key". When terraform runs with a different public key content
but the same key name, it updates the aws_key_pair resource, replacing the
public key in AWS without warning.
Solution:
Implement directory-based unique SSH key naming for AWS, following the pattern
already established for Lambda Labs. Each kdevops directory now generates a
unique SSH key name based on its absolute path using SHA256 hashing.
Implementation Details:
1. Created scripts/aws_ssh_key_name.py:
- Generates unique key names like "kdevops-aws-projectname-a8999659"
- Uses SHA256 hash of the directory path (8 chars) for uniqueness
- Includes project name for human readability
- Provides options for key name, private key path, or public key path
2. Added terraform/aws/kconfigs/Kconfig.ssh:
- New configuration choice: unique keys (default) vs shared keys
- Unique mode: Each directory gets its own SSH key name automatically
- Shared mode: Legacy behavior with explicit warnings about risks
- Dynamic key name generation using the Python script
3. Updated terraform integration:
- Modified terraform/aws/Kconfig to include new SSH configuration
- Updated playbooks/roles/gen_tfvars/templates/aws/terraform.tfvars.j2
to use dynamic ssh_keyname variable
- Added defaults in playbooks/roles/gen_tfvars/defaults/main.yml
- Integrated with global SSH config in terraform/Kconfig.ssh
Benefits:
- Prevents accidental key overwrites in AWS backend
- No more lockouts from existing instances when using multiple directories
- Backward compatible - users can still use shared keys if needed
- Consistent with Lambda Labs implementation pattern
- Each directory's instances are isolated with their own SSH keys
Example:
Directory: /home/user/projects/kdevops
AWS Key Name: kdevops-aws-projects-kdevops-a8999659
Local Keys: ~/.ssh/kdevops-aws-projects-kdevops-a8999659[.pub]
This ensures that running kdevops from different directories will never
conflict, as each gets its own unique key name in both AWS and locally.
Generated-by: Claude AI
Signed-off-by: Luis Chamberlain <mcgrof@kernel.org>
---
playbooks/roles/gen_tfvars/defaults/main.yml | 2 +
.../templates/aws/terraform.tfvars.j2 | 3 +-
scripts/aws_ssh_key_name.py | 165 ++++++++++++++++++
terraform/Kconfig.ssh | 14 +-
terraform/aws/Kconfig | 1 +
terraform/aws/kconfigs/Kconfig.ssh | 79 +++++++++
6 files changed, 257 insertions(+), 7 deletions(-)
create mode 100755 scripts/aws_ssh_key_name.py
create mode 100644 terraform/aws/kconfigs/Kconfig.ssh
diff --git a/playbooks/roles/gen_tfvars/defaults/main.yml b/playbooks/roles/gen_tfvars/defaults/main.yml
index c9e531bc..9957a078 100644
--- a/playbooks/roles/gen_tfvars/defaults/main.yml
+++ b/playbooks/roles/gen_tfvars/defaults/main.yml
@@ -27,6 +27,8 @@ terraform_aws_instance_type: "t2.micro"
terraform_aws_ebs_volumes_per_instance: "0"
terraform_aws_ebs_volume_size: 0
terraform_aws_ebs_volume_type: "gp3"
+terraform_aws_ssh_key_name: "kdevops-aws-fallback"
+terraform_aws_ssh_pubkey_file: "~/.ssh/kdevops_terraform.pub"
terraform_oci_assign_public_ip: false
terraform_oci_use_existing_vcn: false
diff --git a/playbooks/roles/gen_tfvars/templates/aws/terraform.tfvars.j2 b/playbooks/roles/gen_tfvars/templates/aws/terraform.tfvars.j2
index d880254b..4c976f13 100644
--- a/playbooks/roles/gen_tfvars/templates/aws/terraform.tfvars.j2
+++ b/playbooks/roles/gen_tfvars/templates/aws/terraform.tfvars.j2
@@ -14,7 +14,8 @@ aws_ebs_volume_iops = {{ terraform_aws_ebs_volume_iops }}
aws_ebs_volume_throughput = {{ terraform_aws_ebs_volume_throughput }}
{% endif %}
-ssh_config_pubkey_file = "{{ kdevops_terraform_ssh_config_pubkey_file }}"
+ssh_keyname = "{{ terraform_aws_ssh_key_name }}"
+ssh_config_pubkey_file = "{{ terraform_aws_ssh_pubkey_file | default(kdevops_terraform_ssh_config_pubkey_file) }}"
ssh_config_user = "{{ kdevops_terraform_ssh_config_user }}"
ssh_config = "{{ sshconfig }}"
diff --git a/scripts/aws_ssh_key_name.py b/scripts/aws_ssh_key_name.py
new file mode 100755
index 00000000..ff4bc967
--- /dev/null
+++ b/scripts/aws_ssh_key_name.py
@@ -0,0 +1,165 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: copyleft-next-0.3.1
+
+"""
+Generate a unique SSH key name for AWS based on the current directory.
+This ensures each kdevops instance uses its own SSH key to prevent conflicts.
+"""
+
+import hashlib
+import os
+import sys
+
+
+def get_directory_hash(path: str, length: int = 8) -> str:
+ """
+ Generate a short hash of the directory path.
+
+ Args:
+ path: Directory path to hash
+ length: Number of hex characters to use (default 8)
+
+ Returns:
+ Hex string of specified length
+ """
+ # Get the absolute path to ensure consistency
+ abs_path = os.path.abspath(path)
+
+ # Create SHA256 hash of the path
+ hash_obj = hashlib.sha256(abs_path.encode("utf-8"))
+
+ # Return first N characters of the hex digest
+ return hash_obj.hexdigest()[:length]
+
+
+def get_project_name(path: str) -> str:
+ """
+ Extract a meaningful project name from the path.
+
+ Args:
+ path: Directory path
+
+ Returns:
+ Project name derived from directory
+ """
+ abs_path = os.path.abspath(path)
+
+ # Get the last two directory components for context
+ # e.g., /home/user/projects/kdevops -> projects-kdevops
+ parts = abs_path.rstrip("/").split("/")
+
+ if len(parts) >= 2:
+ # Use last two directories
+ project_parts = parts[-2:]
+ # Filter out generic names
+ filtered = [
+ p
+ for p in project_parts
+ if p not in ["data", "home", "root", "usr", "var", "tmp", "cloud", "opt"]
+ ]
+ if filtered:
+ return "-".join(filtered)
+
+ # Fallback to just the last directory
+ return parts[-1] if parts else "kdevops"
+
+
+def generate_ssh_key_name(
+ prefix: str = "kdevops-aws", include_project: bool = True
+) -> str:
+ """
+ Generate a unique SSH key name for the current directory.
+
+ Args:
+ prefix: Prefix for the key name (default "kdevops-aws")
+ include_project: Include project name in the key (default True)
+
+ Returns:
+ Unique SSH key name like "kdevops-aws-projectname-a1b2c3d4"
+ """
+ # Find the kdevops root directory
+ script_dir = os.path.dirname(os.path.abspath(__file__))
+ kdevops_root = os.path.dirname(script_dir)
+
+ # Use the kdevops root directory for consistent key naming
+ dir_hash = get_directory_hash(kdevops_root)
+
+ parts = [prefix]
+
+ if include_project:
+ project = get_project_name(kdevops_root)
+ # Limit project name length and sanitize
+ project = project.replace("_", "-").replace(".", "-")[:20]
+ parts.append(project)
+
+ parts.append(dir_hash)
+
+ # Create the key name
+ key_name = "-".join(parts)
+
+ # Ensure it's a valid AWS key name (alphanumeric, hyphens, underscores)
+ key_name = "".join(c if c.isalnum() or c in "-_" else "-" for c in key_name)
+
+ # Remove multiple consecutive hyphens
+ while "--" in key_name:
+ key_name = key_name.replace("--", "-")
+
+ # AWS key pair names have a 255 character limit, but let's be reasonable
+ if len(key_name) > 60:
+ # Keep prefix and full hash for uniqueness
+ key_name = f"{prefix}-{dir_hash}"
+
+ return key_name.strip("-")
+
+
+def get_ssh_key_path(key_type: str = "") -> str:
+ """
+ Generate the local SSH key file path based on directory.
+
+ Args:
+ key_type: Either "" for private key, ".pub" for public key
+
+ Returns:
+ Path to SSH key file
+ """
+ key_name = generate_ssh_key_name()
+ return os.path.expanduser(f"~/.ssh/{key_name}{key_type}")
+
+
+def main():
+ """Main entry point."""
+ if len(sys.argv) > 1:
+ if sys.argv[1] == "--help" or sys.argv[1] == "-h":
+ print("Usage: aws_ssh_key_name.py [--simple|--path|--pubkey-path]")
+ print()
+ print("Generate a unique SSH key name based on current directory.")
+ print()
+ print("Options:")
+ print(" --simple Generate simple name without project context")
+ print(" --path Output full path to private key file")
+ print(" --pubkey-path Output full path to public key file")
+ print(" --help Show this help message")
+ print()
+ print("Examples:")
+ print(" Default: kdevops-aws-projectname-a1b2c3d4")
+ print(" Simple: kdevops-aws-a1b2c3d4")
+ print(" Path: /home/user/.ssh/kdevops-aws-projectname-a1b2c3d4")
+ print(
+ " Pubkey Path: /home/user/.ssh/kdevops-aws-projectname-a1b2c3d4.pub"
+ )
+ sys.exit(0)
+ elif sys.argv[1] == "--simple":
+ print(generate_ssh_key_name(include_project=False))
+ elif sys.argv[1] == "--path":
+ print(get_ssh_key_path())
+ elif sys.argv[1] == "--pubkey-path":
+ print(get_ssh_key_path(".pub"))
+ else:
+ print(f"Unknown option: {sys.argv[1]}", file=sys.stderr)
+ sys.exit(1)
+ else:
+ print(generate_ssh_key_name())
+
+
+if __name__ == "__main__":
+ main()
diff --git a/terraform/Kconfig.ssh b/terraform/Kconfig.ssh
index 8a19d7c5..8f762cde 100644
--- a/terraform/Kconfig.ssh
+++ b/terraform/Kconfig.ssh
@@ -27,26 +27,28 @@ config TERRAFORM_SSH_CONFIG_USER
config TERRAFORM_SSH_CONFIG_PUBKEY_FILE
string "The ssh public key to use to log in"
default "~/.ssh/kdevops_terraform_$(shell, echo $(TOPDIR_PATH) | sha256sum | cut -c1-8).pub" if TERRAFORM_LAMBDALABS
+ default TERRAFORM_AWS_SSH_PUBKEY_FILE if TERRAFORM_AWS && TERRAFORM_AWS_SSH_KEY_UNIQUE
default "~/.ssh/kdevops_terraform.pub"
help
The ssh public key which will be pegged onto the systems's
~/.ssh/authorized_keys file so you can log in.
- For Lambda Labs, the key path is made unique per directory by appending
- the directory checksum to avoid conflicts when running multiple kdevops
- instances.
+ For Lambda Labs and AWS (when using unique keys), the key path is made
+ unique per directory by appending the directory checksum to avoid
+ conflicts when running multiple kdevops instances.
config TERRAFORM_SSH_CONFIG_PRIVKEY_FILE
string "The ssh private key file for authentication"
default "~/.ssh/kdevops_terraform_$(shell, echo $(TOPDIR_PATH) | sha256sum | cut -c1-8)" if TERRAFORM_LAMBDALABS
+ default TERRAFORM_AWS_SSH_PRIVKEY_FILE if TERRAFORM_AWS && TERRAFORM_AWS_SSH_KEY_UNIQUE
default "~/.ssh/kdevops_terraform"
help
The ssh private key file used for authenticating to the systems.
This should correspond to the public key specified above.
- For Lambda Labs, the key path is made unique per directory by appending
- the directory checksum to avoid conflicts when running multiple kdevops
- instances.
+ For Lambda Labs and AWS (when using unique keys), the key path is made
+ unique per directory by appending the directory checksum to avoid
+ conflicts when running multiple kdevops instances.
config TERRAFORM_SSH_CONFIG_GENKEY
bool "Should we create a new random key for you?"
diff --git a/terraform/aws/Kconfig b/terraform/aws/Kconfig
index 557f3a08..d4015cda 100644
--- a/terraform/aws/Kconfig
+++ b/terraform/aws/Kconfig
@@ -11,6 +11,7 @@ source "terraform/aws/kconfigs/Kconfig.storage"
endmenu
menu "Identity & Access"
source "terraform/aws/kconfigs/Kconfig.identity"
+source "terraform/aws/kconfigs/Kconfig.ssh"
endmenu
endif # TERRAFORM_AWS
diff --git a/terraform/aws/kconfigs/Kconfig.ssh b/terraform/aws/kconfigs/Kconfig.ssh
new file mode 100644
index 00000000..3b62be48
--- /dev/null
+++ b/terraform/aws/kconfigs/Kconfig.ssh
@@ -0,0 +1,79 @@
+# AWS SSH Key Management Configuration
+
+choice
+ prompt "AWS SSH key management strategy"
+ default TERRAFORM_AWS_SSH_KEY_UNIQUE
+ help
+ Choose how SSH keys are managed for AWS instances.
+
+ Unique key (recommended): Each kdevops project directory gets its own
+ SSH key, preventing conflicts when using multiple kdevops instances.
+
+ Shared key: Use the same key name across all projects (less secure,
+ risk of overwriting keys and locking out existing instances).
+
+config TERRAFORM_AWS_SSH_KEY_UNIQUE
+ bool "Use unique SSH key per project directory (recommended)"
+ help
+ Generate a unique SSH key name for each kdevops project directory.
+ The key name will be based on the directory path, ensuring no conflicts
+ between different kdevops deployments.
+
+ Example: kdevops-aws-projectname-a1b2c3d4
+
+ This prevents accidentally overwriting SSH keys in AWS when using
+ kdevops in multiple directories, which would lock you out of
+ existing instances.
+
+config TERRAFORM_AWS_SSH_KEY_SHARED
+ bool "Use shared SSH key name (legacy/risky)"
+ help
+ Use a fixed SSH key name that you specify. This is less secure
+ and can lead to accidental key overwrites if you use kdevops in
+ multiple directories with different keys.
+
+ WARNING: Using the same key name with different public keys will
+ replace the key in AWS and lock you out of existing instances!
+
+endchoice
+
+config TERRAFORM_AWS_SSH_KEY_NAME_CUSTOM
+ string "Custom SSH key name (only for shared mode)"
+ default "kdevops_aws_key"
+ depends on TERRAFORM_AWS_SSH_KEY_SHARED
+ help
+ Specify the custom SSH key name to use when in shared mode.
+
+ WARNING: If this key already exists in AWS with a different
+ public key, it will be replaced, potentially locking you out
+ of existing instances!
+
+config TERRAFORM_AWS_SSH_KEY_NAME
+ string
+ output yaml
+ default $(shell, python3 scripts/aws_ssh_key_name.py 2>/dev/null || echo "kdevops-aws-fallback") if TERRAFORM_AWS_SSH_KEY_UNIQUE
+ default TERRAFORM_AWS_SSH_KEY_NAME_CUSTOM if TERRAFORM_AWS_SSH_KEY_SHARED
+ help
+ The actual SSH key name that will be used in AWS.
+ When using unique mode, this is automatically generated based on
+ the current directory path.
+
+config TERRAFORM_AWS_SSH_PUBKEY_FILE
+ string
+ output yaml
+ default $(shell, python3 scripts/aws_ssh_key_name.py --pubkey-path 2>/dev/null || echo "~/.ssh/kdevops_terraform.pub") if TERRAFORM_AWS_SSH_KEY_UNIQUE
+ default "~/.ssh/kdevops_terraform.pub" if TERRAFORM_AWS_SSH_KEY_SHARED
+ help
+ Path to the SSH public key file.
+ When using unique mode, this path includes the directory-specific
+ key name to avoid conflicts.
+
+config TERRAFORM_AWS_SSH_PRIVKEY_FILE
+ string
+ output yaml
+ default $(shell, python3 scripts/aws_ssh_key_name.py --path 2>/dev/null || echo "~/.ssh/kdevops_terraform") if TERRAFORM_AWS_SSH_KEY_UNIQUE
+ default "~/.ssh/kdevops_terraform" if TERRAFORM_AWS_SSH_KEY_SHARED
+ help
+ Path to the SSH private key file.
+ When using unique mode, this path includes the directory-specific
+ key name to avoid conflicts.
--
2.51.0
next prev parent reply other threads:[~2025-09-17 0:34 UTC|newest]
Thread overview: 13+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-09-17 0:34 [PATCH v4 0/8] aws: add dynamic kconfig support Luis Chamberlain
2025-09-17 0:34 ` Luis Chamberlain [this message]
2025-09-17 3:36 ` [PATCH v4 1/8] aws: prevent SSH key conflicts across multiple kdevops directories Chuck Lever
2025-09-17 0:34 ` [PATCH v4 2/8] terraform/aws: Add scripts to gather provider resource information Luis Chamberlain
2025-09-17 0:34 ` [PATCH v4 3/8] aws: add optimized Kconfig generator using Chuck's scripts Luis Chamberlain
2025-09-17 3:58 ` Chuck Lever
2025-09-17 0:34 ` [PATCH v4 4/8] aws: integrate dynamic Kconfig generation with make targets Luis Chamberlain
2025-09-17 3:40 ` Chuck Lever
2025-09-17 7:05 ` Luis Chamberlain
2025-09-17 0:34 ` [PATCH v4 5/8] aws: add cloud billing support with make cloud-bill Luis Chamberlain
2025-09-17 0:34 ` [PATCH v4 6/8] aws: replace static Kconfig files with dynamically generated ones Luis Chamberlain
2025-09-17 0:34 ` [PATCH v4 7/8] aws: add GPU instance defconfigs for AI/ML workloads Luis Chamberlain
2025-09-17 0:34 ` [PATCH v4 8/8] docs: add documentation for dynamic cloud configuration Luis Chamberlain
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20250917003451.2318229-2-mcgrof@kernel.org \
--to=mcgrof@kernel.org \
--cc=cel@kernel.org \
--cc=da.gomez@kruces.com \
--cc=kdevops@lists.linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox