ADR-065: Credentials and Secrets Management
Status
Proposed
Date
2025-12-19
Deciders
- Norbert Bede
Context and Problem Statement
iDempiere Hub needs secure access to various external services:
- VCS Providers: GitHub, GitLab, Bitbucket (for pushing generated plugins)
- Cloud Services: AWS, Azure, GCP (for S3, DynamoDB, etc.)
- iDempiere REST API: OAuth2 tokens, API keys
- LLM Providers: Anthropic, OpenAI API keys
- Databases: Connection credentials
Currently, credentials are managed via environment variables, which works for development but has limitations:
- No rotation: Credentials are static
- No audit: No tracking of credential access
- No centralization: Each deployment manages credentials independently
- Limited security: Environment variables can leak in logs
Decision Drivers
- Security: Credentials should not be stored in plain text or code
- Flexibility: Support multiple secret providers (AWS, Vault, env vars)
- Automation: n8n and CI/CD workflows need secure credential access
- Developer experience: Local development should remain simple
Considered Options
- Environment Variables Only - Current approach
- AWS Secrets Manager - AWS-native secret management
- HashiCorp Vault - Platform-agnostic secret management
- Quarkus Vault Extension - Built-in Vault support
- Hybrid Approach - Multiple providers with fallback chain
Decision Outcome
Chosen option: "Hybrid Approach" with provider abstraction
┌─────────────────────────────────────────────────────────────────────────┐
│ CredentialProvider Interface │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ interface CredentialProvider { │
│ Optional<String> getSecret(String key); │
│ boolean isAvailable(); │
│ int priority(); // Higher = preferred │
│ } │
│ │
└───────────────────────────────────┬─────────────────────────────────────┘
│
┌──────────────────────────┼──────────────────────────┐
│ │ │
┌────▼────┐ ┌──────▼──────┐ ┌──────▼──────┐
│ AWS │ │ HashiCorp │ │ Environment │
│ Secrets │ │ Vault │ │ Variables │
│ Manager │ │ │ │ (fallback) │
│ (100) │ │ (90) │ │ (10) │
└─────────┘ └─────────────┘ └─────────────┘
Credential Categories
| Category | Examples | Provider Recommendation |
|---|---|---|
| VCS Tokens | GITHUB_TOKEN, GITLAB_TOKEN | AWS Secrets Manager |
| API Keys | ANTHROPIC_API_KEY, OPENAI_API_KEY | AWS Secrets Manager |
| OAuth2 | iDempiere client credentials | Vault or AWS |
| Database | IDEMPIERE_DB_PASSWORD | Environment (local) / Vault (prod) |
| Cloud | AWS_ACCESS_KEY_ID | IAM roles (no credentials) |
Proposed Configuration
# Provider selection (auto, aws, vault, environment)
idempiere.hub.credentials.provider=${CREDENTIAL_PROVIDER:auto}
# AWS Secrets Manager
idempiere.hub.credentials.aws.enabled=true
idempiere.hub.credentials.aws.region=${AWS_REGION:us-east-1}
idempiere.hub.credentials.aws.prefix=idempiere-hub/
# HashiCorp Vault
idempiere.hub.credentials.vault.enabled=false
idempiere.hub.credentials.vault.address=${VAULT_ADDR:}
idempiere.hub.credentials.vault.token=${VAULT_TOKEN:}
idempiere.hub.credentials.vault.path=secret/data/idempiere-hub
# Environment (always available as fallback)
idempiere.hub.credentials.environment.enabled=true
Secret Naming Convention
AWS Secrets Manager:
idempiere-hub/github-token
idempiere-hub/anthropic-api-key
idempiere-hub/oauth2/client-credentials
HashiCorp Vault:
secret/data/idempiere-hub/github-token
secret/data/idempiere-hub/anthropic-api-key
Environment Variables:
GITHUB_TOKEN
ANTHROPIC_API_KEY
OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET
GitHub Integration Flow
Scenario: Generate Plugin and Push to GitHub
┌─────────────────────────────────────────────────────────────────────────────┐
│ User/n8n: "Generate plugin org.idempiere.rating and push to GitHub" │
└───────────────────────────────────┬─────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 1. GENERATE PHASE (ADR-064) │
│ │
│ GeneratorToolLogic.generatePlugin(artifactId="org.idempiere.rating") │
│ │ │
│ ▼ │
│ GeneratorConfig.resolveOutputDir() │
│ │ │
│ ▼ │
│ GITHUB_ROOT=/Users/dev/github │
│ Output: /Users/dev/github/org.idempiere.rating/ │
│ │ │
│ ▼ │
│ Files Written: │
│ ├── pom.xml │
│ ├── src/main/java/... │
│ └── META-INF/MANIFEST.MF │
└───────────────────────────────────┬─────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 2. GIT INIT & PUSH PHASE (Uses Credentials) │
│ │
│ GitService.initAndPush(repoPath, remoteUrl) │
│ │ │
│ ▼ │
│ CredentialService.getSecret("github-token") │
│ │ │
│ ├─── AWS Secrets Manager ────────────────────────────────────┐ │
│ │ GET idempiere-hub/github-token │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ Returns: "ghp_xxxxxxxxxxxx" │ │
│ │ │ │
│ └─── OR Environment Variable ────────────────────────────────┤ │
│ $GITHUB_TOKEN = "ghp_xxxxxxxxxxxx" │ │
│ │ │
│ ◄────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ git init │
│ git remote add origin https://github.com/cloudempiere/org.idempiere.rating │
│ git add . │
│ git commit -m "Initial plugin structure" │
│ │ │
│ ▼ │
│ git push (with token authentication) │
│ URL: https://{GITHUB_TOKEN}@github.com/cloudempiere/org.idempiere.rating │
│ │
└───────────────────────────────────┬─────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 3. RESULT │
│ │
│ ✅ Plugin generated at: /Users/dev/github/org.idempiere.rating/ │
│ ✅ Repository created: https://github.com/cloudempiere/org.idempiere.rating│
│ ✅ Initial commit pushed │
└─────────────────────────────────────────────────────────────────────────────┘
AWS Secrets Manager Setup
┌─────────────────────────────────────────────────────────────────────────────┐
│ AWS Secrets Manager │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Secret: idempiere-hub/github-token │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ { │ │
│ │ "token": "ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", │ │
│ │ "username": "cloudempiere-bot", │ │
│ │ "org": "cloudempiere" │ │
│ │ } │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ Secret: idempiere-hub/github-app (Alternative: GitHub App) │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ { │ │
│ │ "app_id": "123456", │ │
│ │ "installation_id": "789012", │ │
│ │ "private_key": "-----BEGIN RSA PRIVATE KEY-----..." │ │
│ │ } │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Configuration Examples
n8n Server (Production)
{
"mcpServers": {
"idempiere": {
"env": {
"GITHUB_ROOT": "/opt/n8n/workspaces/plugins",
"CREDENTIAL_PROVIDER": "aws",
"AWS_REGION": "us-east-1"
}
}
}
}
n8n → iDempiere Hub → AWS Secrets Manager → GitHub
Developer Workstation (Simple)
# ~/.zshrc
export GITHUB_ROOT="$HOME/github"
export GITHUB_TOKEN="ghp_xxx" # Personal access token
# No AWS needed - uses environment variable
GitService Interface (Proposed)
public interface GitService {
/**
* Initialize repo and push to remote.
* Credentials resolved automatically via CredentialService.
*/
GitResult initAndPush(Path repoPath, String remoteUrl, String commitMessage);
/**
* Create GitHub repository via API.
*/
GitResult createRepository(String org, String repoName, boolean isPrivate);
/**
* Clone existing repository.
*/
GitResult clone(String remoteUrl, Path targetPath);
}
CredentialService Interface (Proposed)
public interface CredentialService {
/**
* Get secret by key. Tries providers in priority order.
*/
Optional<String> getSecret(String key);
/**
* Get structured secret (JSON parsed).
*/
<T> Optional<T> getSecret(String key, Class<T> type);
/**
* Check if a secret exists.
*/
boolean hasSecret(String key);
}
Usage Examples
n8n Workflow (AWS)
{
"env": {
"CREDENTIAL_PROVIDER": "aws",
"AWS_REGION": "us-east-1"
}
}
CI/CD (GitHub Actions)
env:
CREDENTIAL_PROVIDER: environment
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
Local Development
# ~/.zshrc - simple environment variables
export GITHUB_TOKEN="ghp_xxx"
export ANTHROPIC_API_KEY="sk-ant-xxx"
# No CREDENTIAL_PROVIDER needed - defaults to environment
Implementation Plan
Phase 1: Interface & Environment Provider
- [ ]
CredentialProviderinterface - [ ]
EnvironmentCredentialProviderimplementation - [ ] Integration with existing code
Phase 2: AWS Secrets Manager
- [ ]
AwsSecretsManagerProviderimplementation - [ ] Quarkus AWS extension integration
- [ ] Caching layer for performance
Phase 3: HashiCorp Vault (Optional)
- [ ]
VaultCredentialProviderimplementation - [ ] Quarkus Vault extension integration
Phase 4: Credential Rotation
- [ ] TTL-based cache invalidation
- [ ] Automatic refresh for expiring credentials
Related ADRs
- ADR-064: Generator Output Directory - Uses credentials for VCS push
- ADR-061: OAuth2 Token Manager - Token lifecycle management
- ADR-063: Hub Storage Adapter - May store cached credentials
References
Note: This ADR is proposed. Implementation priority depends on security requirements and deployment model (dev vs production).