Skip to content

Conversation

@mgajda
Copy link
Contributor

@mgajda mgajda commented Jan 1, 2026

Summary

This PR addresses issue #643 by adding an explicit --save-to-file flag that allows users to save credentials to a file when the system keyring is unavailable.

This PR replaces #653 with a more secure approach that requires explicit user consent for fallback to file storage.

Problem (Issue #643)

Users on WSL, ARM systems, and headless environments encounter keyring errors:

Error: failed to unlock correct collection '/org/freedesktop/secrets/aliases/default'

Currently, there's no fallback mechanism, forcing the user to use environment variable UPCLOUD_TOKEN and failing to save token on login.

Solution

  • Explicit fallback: Added --save-to-file flag to account login command
  • Secure file storage: Credentials saved to ~/.config/upcloud/credentials with 0600 permissions
  • Automatic loading: Credentials file is checked automatically during config loading
  • Clear error messages: When keyring fails, users receive specific instructions with --save-to-file option

Changes

  1. New file: internal/config/credentials_file.go - Handles credential file operations
  2. Updated: internal/commands/account/login.go - Added --save-to-file flag
  3. Updated: internal/config/config.go - Load from credentials file before attempting keyring
  4. Updated: internal/clierrors/missing_credentials.go - error messages with fallback guidance

Security Considerations

  • No automatic fallback: File storage requires explicit --save-to-file flag
  • Secure permissions: Files created with 0600 (owner read/write only)
  • Atomic writes: Uses temp file + rename for safe writes
  • Clear warnings: Users informed about security implications

User Experience

Successful save with --save-to-file:

$ echo $TOKEN | upctl account login --with-token --save-to-file
✓ Saving token to credentials file.

Token saved to: /home/user/.config/upcloud/credentials
File permissions: 0600 (read/write for owner only)

This credentials file can be shared by other UpCloud tools.
You can now use: upctl account show

Keyring failure without flag:

$ echo $TOKEN | upctl account login --with-token
✗ Saving provided token to the system keyring.
  Error: System keyring is not accessible.
  
  (WSL detected - keyring typically doesn't work in WSL)
  
To save your token, you can:
  1. Retry with file storage:
     upctl account login --with-token --save-to-file
     
  2. Set environment variable:

     export UPCLOUD_TOKEN=your-token-here
     
  3. Manually add to config file (~/.config/upctl.yaml):
     token: your-token-here
     
For security, tokens are not automatically saved to files without --save-to-file flag.

Related

…back

- Add --save-to-file flag to account login command for explicit fallback
- Create credentials file at ~/.config/upcloud/credentials with 0600 permissions
- Load credentials from file automatically if present
- Provide clear error messages with --save-to-file guidance when keyring fails
- Detect WSL, SSH, and headless environments for better error context

Fixes UpCloudLtd#643
@mgajda
Copy link
Contributor Author

mgajda commented Jan 1, 2026

This implementation extends the shared credentials support in upcloud-go-api with:

  1. Credentials file support (~/.config/upcloud/credentials)
    - The shared package doesn't have file-based fallback
    - We load from this file BEFORE calling credentials.Parse()
  2. Explicit fallback mechanism (--save-to-file flag)
    - The shared package just fails if keyring isn't accessible
    - We provide explicit opt-in for file storage
  3. Enhanced error handling
    - Better error messages when keyring fails
    - Detection of WSL, SSH, headless environments
    - Preservation of token in error messages

The credentials file support could be contributed to the upstream upcloud-go-api/credentials package so all UpCloud Go tools could benefit from it. This would:

  1. Standardize credential file location/format across all tools
  2. Benefit other tools like Terraform provider, Packer plugin, etc.
  3. Maintain single source of truth for credential handling

Current architecture:

User Input

upctl (our code)

Load from credentials file (our addition)

credentials.Parse() (shared package)
├── Config (passed in)
├── Environment variables
└── Keyring

The shared credentials package provides the foundation, but lacks file-based storage which we've added specifically for the CLI to handle environments where keyring isn't available.

Comment on lines +38 to +39
fs.BoolVar(&s.saveToFile, "save-to-file", false,
"Save token to credentials file (~/.config/upcloud/credentials) instead of keyring when keyring is unavailable")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's look into how similar tools handle this situation and find out if there are some common practices 👀

For example, GitHub CLI, which does authentication very nicely, seems to (based on documentation) fallback to saving the credentials to configuration file, but not sure if this requires using --insecure-storage flag or is the default when keyring access fails (gh auth login docs) 🤔

Copy link
Contributor Author

@mgajda mgajda Jan 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I took example of AWS CLI, which uses a separate credentials and configuration files. This allows all to read configuration, but only the user can read credentials.

Saving without explicit override means defaulting to potentially insecure behaviour, which should be discouraged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants