Skip to content

Authentication

Maxime edited this page Apr 1, 2026 · 7 revisions

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

Clone this wiki locally