public inbox for kdevops@lists.linux.dev
 help / color / mirror / Atom feed
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>, Your Name <email@example.com>
Subject: [PATCH v3 04/10] scripts: add Lambda Labs credentials management
Date: Sat, 30 Aug 2025 20:59:58 -0700	[thread overview]
Message-ID: <20250831040004.2159779-5-mcgrof@kernel.org> (raw)
In-Reply-To: <20250831040004.2159779-1-mcgrof@kernel.org>

Add secure credential management for Lambda Labs API access:
- Support for environment variable (LAMBDALABS_API_KEY)
- Local credential file storage (~/.config/lambda-labs/credentials)
- API key validation and testing
- Secure file permissions (0600)
- Cross-platform compatibility

Provides standardized credential handling following cloud provider
best practices for API key management.

Generated-by: Claude AI
Signed-off-by: Your Name <email@example.com>
---
 scripts/lambdalabs_credentials.py | 242 ++++++++++++++++++++++++++++++
 1 file changed, 242 insertions(+)
 create mode 100755 scripts/lambdalabs_credentials.py

diff --git a/scripts/lambdalabs_credentials.py b/scripts/lambdalabs_credentials.py
new file mode 100755
index 0000000..0079491
--- /dev/null
+++ b/scripts/lambdalabs_credentials.py
@@ -0,0 +1,242 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: copyleft-next-0.3.1
+
+"""
+Lambda Labs credentials management.
+Reads API keys from credentials file (~/.lambdalabs/credentials).
+"""
+
+import os
+import configparser
+from pathlib import Path
+from typing import Optional
+
+
+def get_credentials_file_path() -> Path:
+    """Get the default Lambda Labs credentials file path."""
+    return Path.home() / ".lambdalabs" / "credentials"
+
+
+def read_credentials_file(
+    path: Optional[Path] = None, profile: str = "default"
+) -> Optional[str]:
+    """
+    Read Lambda Labs API key from credentials file.
+
+    Args:
+        path: Path to credentials file (defaults to ~/.lambdalabs/credentials)
+        profile: Profile name to use (defaults to "default")
+
+    Returns:
+        API key if found, None otherwise
+    """
+    if path is None:
+        path = get_credentials_file_path()
+
+    if not path.exists():
+        return None
+
+    try:
+        config = configparser.ConfigParser()
+        config.read(path)
+
+        if profile in config:
+            # Try different possible key names
+            for key_name in ["lambdalabs_api_key", "api_key"]:
+                if key_name in config[profile]:
+                    return config[profile][key_name].strip()
+
+        # Also check if it's in DEFAULT section
+        if "DEFAULT" in config:
+            for key_name in ["lambdalabs_api_key", "api_key"]:
+                if key_name in config["DEFAULT"]:
+                    return config["DEFAULT"][key_name].strip()
+
+    except Exception:
+        # Silently fail if file can't be parsed
+        pass
+
+    return None
+
+
+def get_api_key(profile: str = "default") -> Optional[str]:
+    """
+    Get Lambda Labs API key from credentials file.
+
+    Args:
+        profile: Profile name to use from credentials file
+
+    Returns:
+        API key if found, None otherwise
+    """
+    # Try default credentials file
+    api_key = read_credentials_file(profile=profile)
+    if api_key:
+        return api_key
+
+    # Try custom credentials file path from environment
+    custom_path = os.environ.get("LAMBDALABS_CREDENTIALS_FILE")
+    if custom_path:
+        api_key = read_credentials_file(Path(custom_path), profile=profile)
+        if api_key:
+            return api_key
+
+    return None
+
+
+def create_credentials_file(
+    api_key: str, path: Optional[Path] = None, profile: str = "default"
+) -> bool:
+    """
+    Create or update Lambda Labs credentials file.
+
+    Args:
+        api_key: The API key to save
+        path: Path to credentials file (defaults to ~/.lambdalabs/credentials)
+        profile: Profile name to use (defaults to "default")
+
+    Returns:
+        True if successful, False otherwise
+    """
+    if path is None:
+        path = get_credentials_file_path()
+
+    try:
+        # Create directory if it doesn't exist
+        path.parent.mkdir(parents=True, exist_ok=True)
+
+        # Read existing config or create new one
+        config = configparser.ConfigParser()
+        if path.exists():
+            config.read(path)
+
+        # Add or update the profile
+        if profile not in config:
+            config[profile] = {}
+
+        config[profile]["lambdalabs_api_key"] = api_key
+
+        # Write the config file with restricted permissions
+        with open(path, "w") as f:
+            config.write(f)
+
+        # Set restrictive permissions (owner read/write only)
+        path.chmod(0o600)
+
+        return True
+
+    except Exception as e:
+        print(f"Error creating credentials file: {e}")
+        return False
+
+
+def main():
+    """Command-line utility for managing Lambda Labs credentials."""
+    import sys
+
+    if len(sys.argv) < 2:
+        print("Usage:")
+        print("  lambdalabs_credentials.py get [profile]     - Get API key")
+        print("  lambdalabs_credentials.py set <api_key> [profile] - Set API key")
+        print(
+            "  lambdalabs_credentials.py check [profile]   - Check if API key is configured"
+        )
+        print("  lambdalabs_credentials.py test [profile]    - Test API key validity")
+        print(
+            "  lambdalabs_credentials.py path              - Show credentials file path"
+        )
+        sys.exit(1)
+
+    command = sys.argv[1]
+
+    if command == "get":
+        profile = sys.argv[2] if len(sys.argv) > 2 else "default"
+        api_key = get_api_key(profile)
+        if api_key:
+            print(api_key)
+            sys.exit(0)
+        else:
+            print("No API key found", file=sys.stderr)
+            sys.exit(1)
+
+    elif command == "set":
+        if len(sys.argv) < 3:
+            print("Error: API key required", file=sys.stderr)
+            sys.exit(1)
+        api_key = sys.argv[2]
+        profile = sys.argv[3] if len(sys.argv) > 3 else "default"
+
+        if create_credentials_file(api_key, profile=profile):
+            print(
+                f"API key saved to {get_credentials_file_path()} (profile: {profile})"
+            )
+            sys.exit(0)
+        else:
+            print("Failed to save API key", file=sys.stderr)
+            sys.exit(1)
+
+    elif command == "check":
+        profile = sys.argv[2] if len(sys.argv) > 2 else "default"
+        api_key = get_api_key(profile)
+        if api_key:
+            print(f"[OK] API key configured (profile: {profile})")
+            # Show sources checked
+            if read_credentials_file(profile=profile):
+                print(f"  Source: {get_credentials_file_path()}")
+            elif os.environ.get("LAMBDALABS_CREDENTIALS_FILE"):
+                print(f"  Source: {os.environ.get('LAMBDALABS_CREDENTIALS_FILE')}")
+            sys.exit(0)
+        else:
+            print("[ERROR] No API key found")
+            print(f"  Checked: {get_credentials_file_path()}")
+            if os.environ.get("LAMBDALABS_CREDENTIALS_FILE"):
+                print(f"  Checked: {os.environ.get('LAMBDALABS_CREDENTIALS_FILE')}")
+            sys.exit(1)
+
+    elif command == "test":
+        profile = sys.argv[2] if len(sys.argv) > 2 else "default"
+        api_key = get_api_key(profile)
+        if not api_key:
+            print("[ERROR] No API key found")
+            sys.exit(1)
+
+        # Test the API key
+        import urllib.request
+        import urllib.error
+        import json
+
+        print(f"Testing API key (profile: {profile})...")
+        headers = {"Authorization": f"Bearer {api_key}", "User-Agent": "kdevops/1.0"}
+
+        try:
+            req = urllib.request.Request(
+                "https://cloud.lambdalabs.com/api/v1/instances", headers=headers
+            )
+            with urllib.request.urlopen(req) as response:
+                data = json.loads(response.read().decode())
+                print(f"[OK] API key is VALID")
+                print(f"  Current instances: {len(data.get('data', []))}")
+                sys.exit(0)
+        except urllib.error.HTTPError as e:
+            if e.code == 403:
+                print(f"[ERROR] API key is INVALID (HTTP 403 Forbidden)")
+                print("  The key exists but Lambda Labs rejected it.")
+                print("  Please get a new API key from https://cloud.lambdalabs.com")
+            else:
+                print(f"[ERROR] API test failed: HTTP {e.code}")
+            sys.exit(1)
+        except Exception as e:
+            print(f"[ERROR] API test failed: {e}")
+            sys.exit(1)
+
+    elif command == "path":
+        print(get_credentials_file_path())
+        sys.exit(0)
+
+    else:
+        print(f"Unknown command: {command}", file=sys.stderr)
+        sys.exit(1)
+
+
+if __name__ == "__main__":
+    main()
-- 
2.50.1


  parent reply	other threads:[~2025-08-31  4:00 UTC|newest]

Thread overview: 12+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-08-31  3:59 [PATCH v3 00/10] terraform: add Lambda Labs cloud provider support Luis Chamberlain
2025-08-31  3:59 ` [PATCH v3 01/10] gitignore: add entries for Lambda Labs dynamic configuration Luis Chamberlain
2025-08-31  3:59 ` [PATCH v3 02/10] scripts: add Lambda Labs Python API library Luis Chamberlain
2025-08-31  3:59 ` [PATCH v3 03/10] scripts: add Lambda Labs testing and debugging utilities Luis Chamberlain
2025-08-31  3:59 ` Luis Chamberlain [this message]
2025-08-31  3:59 ` [PATCH v3 05/10] scripts: add Lambda Labs SSH key management utilities Luis Chamberlain
2025-08-31  4:00 ` [PATCH v3 06/10] kconfig: add dynamic cloud provider configuration infrastructure Luis Chamberlain
2025-08-31  4:00 ` [PATCH v3 07/10] terraform/lambdalabs: add Kconfig structure for Lambda Labs Luis Chamberlain
2025-08-31  4:00 ` [PATCH v3 08/10] terraform/lambdalabs: add terraform provider implementation Luis Chamberlain
2025-08-31  4:00 ` [PATCH v3 09/10] ansible/terraform: integrate Lambda Labs into build system Luis Chamberlain
2025-08-31  4:00 ` [PATCH v3 10/10] kconfigs: enable Lambda Labs cloud provider in menus Luis Chamberlain
2025-09-01  1:10 ` [PATCH v3 00/10] terraform: add Lambda Labs cloud provider support 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=20250831040004.2159779-5-mcgrof@kernel.org \
    --to=mcgrof@kernel.org \
    --cc=cel@kernel.org \
    --cc=da.gomez@kruces.com \
    --cc=email@example.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