Authentication - FreshPerf/PVE4J GitHub Wiki

Authentication

This guide covers authentication methods and API token management in PVE4J.

Table of Contents

API Tokens

API tokens are the recommended authentication method for PVE4J. They provide:

  • Secure, token-based authentication
  • Fine-grained permission control
  • Easy revocation
  • No password exposure

Token Format

Proxmox API tokens follow this format:

USER@REALM!TOKENID=SECRET

Components:

  • USER - Username (e.g., root, admin, automation)
  • REALM - Authentication realm (e.g., pam, pve)
  • TOKENID - Token identifier (user-defined)
  • SECRET - Token secret (UUID generated by Proxmox)

Example Tokens

// Root user with PAM authentication
String token = "root@pam!automation=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";

// Custom user with PVE realm
String token = "admin@pve!readonly=yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy";

// Service account
String token = "backup@pam!backup-service=zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz";

Creating API Tokens

Via Proxmox Web Interface

  1. Log in to Proxmox web interface
  2. Navigate to DatacenterPermissionsAPI Tokens
  3. Click Add
  4. Fill in the form:
    • User: Select the user
    • Token ID: Enter a descriptive name (e.g., automation, readonly)
    • Privilege Separation:
      • Checked: Token has same permissions as user
      • Unchecked: Token has limited permissions (recommended)
    • Expire: Set expiration (optional)
    • Comment: Add description
  5. Click Add
  6. Important: Copy the displayed secret immediately (shown only once)

Via Command Line

SSH into your Proxmox server:

# Create API token
pveum user token add root@pam automation --privsep 0

# The secret will be displayed - save it immediately

Token Permissions

After creating a token, assign appropriate permissions:

# Grant VM.Admin role to token for all VMs
pveum acl modify /vms -token 'root@pam!automation' -role PVEVMAdmin

# Grant read-only access to cluster
pveum acl modify / -token 'root@pam!readonly' -role PVEAuditor

Using API Tokens

Basic Usage

import fr.freshperf.pve4j.Proxmox;

Proxmox proxmox = Proxmox.create(
    "pve.example.com",
    8006,
    "root@pam!automation=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
);

From Environment Variables

public class ProxmoxConfig {
    public static Proxmox createClient() {
        String host = System.getenv("PROXMOX_HOST");
        int port = Integer.parseInt(System.getenv("PROXMOX_PORT"));
        String token = System.getenv("PROXMOX_API_TOKEN");
        
        return Proxmox.create(host, port, token);
    }
}

Set environment variables:

export PROXMOX_HOST=pve.example.com
export PROXMOX_PORT=8006
export PROXMOX_API_TOKEN=root@pam!automation=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

From Configuration File

import java.io.*;
import java.util.Properties;

public class ProxmoxConfig {
    public static Proxmox createClientFromConfig(String configPath) 
            throws IOException {
        
        Properties props = new Properties();
        try (FileInputStream fis = new FileInputStream(configPath)) {
            props.load(fis);
        }
        
        String host = props.getProperty("proxmox.host");
        int port = Integer.parseInt(props.getProperty("proxmox.port"));
        String token = props.getProperty("proxmox.token");
        
        return Proxmox.create(host, port, token);
    }
}

Configuration file (proxmox.properties):

proxmox.host=pve.example.com
proxmox.port=8006
proxmox.token=root@pam!automation=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

Token Security

Never Hardcode Tokens

// BAD - hardcoded token
Proxmox proxmox = Proxmox.create(
    "pve.example.com", 8006, 
    "root@pam!app=12345678-1234-1234-1234-123456789abc"
);

// GOOD - from environment
Proxmox proxmox = Proxmox.create(
    System.getenv("PROXMOX_HOST"),
    Integer.parseInt(System.getenv("PROXMOX_PORT")),
    System.getenv("PROXMOX_API_TOKEN")
);

Use Least Privilege

# Create limited token for specific operations
pveum user token add automation@pve readonly --privsep 1

# Grant only necessary permissions
pveum acl modify /vms -token 'automation@pve!readonly' -role PVEAuditor

Rotate Tokens Regularly

public class TokenRotation {
    public void rotateToken() {
        // 1. Create new token in Proxmox
        // 2. Update configuration with new token
        // 3. Test new token
        // 4. Remove old token from Proxmox
    }
}

Set Expiration Dates

When creating tokens, set expiration dates for better security:

pveum user token add root@pam temp --expire "2025-12-31"

Monitor Token Usage

Regularly audit which tokens are in use and revoke unused ones.

Storing Tokens Securely

Environment Variables

# Linux/Mac
export PROXMOX_API_TOKEN="root@pam!app=secret"

# Windows
set PROXMOX_API_TOKEN=root@pam!app=secret

Encrypted Configuration Files

import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;

public class SecureConfig {
    public String decryptToken(String encryptedToken, String key) 
            throws Exception {
        
        Cipher cipher = Cipher.getInstance("AES");
        SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(), "AES");
        cipher.init(Cipher.DECRYPT_MODE, secretKey);
        
        byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(encryptedToken));
        return new String(decrypted);
    }
}

Password Authentication

PVE4J supports username/password authentication through the createWithPassword() method. This creates a client that authenticates using a ticket obtained from the Proxmox API.

Basic Password Authentication

import fr.freshperf.pve4j.Proxmox;
import fr.freshperf.pve4j.SecurityConfig;

try {
    // With default PAM realm and secure settings
    Proxmox proxmox = Proxmox.createWithPassword(
        "pve.example.com",
        8006,
        "root",
        "your-password"
    );
    
    // Now you can use the client as normal
    System.out.println("Version: " + proxmox.getVersion().execute().getVersion());
    
} catch (ProxmoxAPIError | InterruptedException e) {
    System.err.println("Authentication failed: " + e.getMessage());
}

With Custom Realm

try {
    // Authenticate with a specific realm (e.g., LDAP, Active Directory)
    Proxmox proxmox = Proxmox.createWithPassword(
        "pve.example.com",
        8006,
        "admin",
        "password",
        "ldap",  // Custom realm
        SecurityConfig.secure()
    );
} catch (ProxmoxAPIError | InterruptedException e) {
    System.err.println("Authentication failed: " + e.getMessage());
}

With Custom Security Configuration

try {
    // For development with self-signed certificates
    Proxmox proxmox = Proxmox.createWithPassword(
        "192.168.1.100",
        8006,
        "root",
        "password",
        SecurityConfig.insecure()
    );
} catch (ProxmoxAPIError | InterruptedException e) {
    System.err.println("Authentication failed: " + e.getMessage());
}

Password Authentication Considerations

  • Tickets are short-lived (2 hours by default)
  • The client stores the ticket and CSRF token for subsequent requests
  • If the session expires, you need to create a new client
  • API tokens are recommended for long-running applications

Recommendation: Use API tokens for automated applications and password authentication for interactive tools or short-lived scripts.

Ticket Authentication (Manual)

For more control over the authentication process, you can manually request a ticket:

Get Authentication Ticket

import fr.freshperf.pve4j.entities.access.PveAccessTicket;

try {
    PveAccessTicket ticket = proxmox.getAccess()
            .getTicket("root", "password123", "pam")
            .execute();
    
    System.out.println("Ticket: " + ticket.getTicket());
    System.out.println("CSRF Token: " + ticket.getCSRFPreventionToken());
    System.out.println("Username: " + ticket.getUsername());
    System.out.println("Cluster: " + ticket.getClustername());
    
} catch (ProxmoxAPIError | InterruptedException e) {
    System.err.println("Authentication failed: " + e.getMessage());
}

Ticket Limitations

  • Short-lived (2 hours by default)
  • Requires password storage
  • Needs refresh mechanism for long-running applications

Common Authentication Patterns

Pattern 1: Single Client Instance

public class ProxmoxClient {
    private static final Proxmox INSTANCE;
    
    static {
        INSTANCE = Proxmox.create(
            System.getenv("PROXMOX_HOST"),
            Integer.parseInt(System.getenv("PROXMOX_PORT")),
            System.getenv("PROXMOX_API_TOKEN")
        );
    }
    
    public static Proxmox getInstance() {
        return INSTANCE;
    }
}

Pattern 2: Per-Operation Clients

public class ProxmoxOperations {
    public void performOperation() {
        Proxmox proxmox = createClient();
        try {
            // Perform operation
        } catch (Exception e) {
            // Handle error
        }
    }
    
    private Proxmox createClient() {
        return Proxmox.create(
            System.getenv("PROXMOX_HOST"),
            Integer.parseInt(System.getenv("PROXMOX_PORT")),
            System.getenv("PROXMOX_API_TOKEN")
        );
    }
}

Pattern 3: Multi-Environment Support

public class ProxmoxConfig {
    public enum Environment {
        DEVELOPMENT,
        STAGING,
        PRODUCTION
    }
    
    public static Proxmox createClient(Environment env) {
        String prefix = env.name().toLowerCase();
        
        String host = System.getenv(prefix + "_PROXMOX_HOST");
        int port = Integer.parseInt(System.getenv(prefix + "_PROXMOX_PORT"));
        String token = System.getenv(prefix + "_PROXMOX_TOKEN");
        
        return Proxmox.create(host, port, token);
    }
}

Troubleshooting Authentication

Invalid Token Error (401)

Error: HTTP 401 - Unauthorized

Solutions:

  1. Verify token format: USER@REALM!TOKENID=SECRET
  2. Check token exists in Proxmox
  3. Ensure token is not expired
  4. Verify token has required permissions

Permission Denied (403)

Error: HTTP 403 - Forbidden

Solutions:

  1. Check token permissions in Proxmox
  2. Verify ACL settings for the resource
  3. Ensure privilege separation is configured correctly

Testing Token

public boolean testToken(Proxmox proxmox) {
    try {
        proxmox.getVersion().execute();
        System.out.println("Token is valid");
        return true;
    } catch (ProxmoxAPIError e) {
        if (e.getStatusCode() == 401) {
            System.err.println("Token is invalid or expired");
        } else if (e.getStatusCode() == 403) {
            System.err.println("Token lacks necessary permissions");
        }
        return false;
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        return false;
    }
}

Next Steps