Skip to main content

Secrets Management

ERPL-Web integrates with DuckDB's secrets management system to keep your credentials secure. Never hardcode passwords or API tokens again.

Overview

DuckDB secrets provide a secure way to store and manage authentication credentials. ERPL-Web extends this with custom secret types and providers for SAP and cloud services.

What you get:

  • ✅ Encrypted storage of credentials
  • ✅ OAuth2 flows with automatic token refresh
  • ✅ Multiple provider types (oauth2, config, file)
  • ✅ Secret scoping and isolation
  • ✅ No plaintext passwords in SQL
  • ✅ Integration with all ERPL-Web functions

Quick Start

-- Create a simple HTTP basic auth secret
CREATE SECRET my_api (
TYPE http_basic,
username 'api_user',
password 'api_password'
);

-- Use it automatically in requests
SELECT * FROM http_get('https://api.example.com/data');

-- Or specify explicitly
SELECT * FROM http_get(
'https://api.example.com/data',
secret := 'my_api'
);

Secret Types

http_basic

For HTTP Basic Authentication (username/password).

Usage:

CREATE SECRET sap_system (
TYPE http_basic,
username 'SAP_USER',
password 'SAP_PASSWORD'
);

-- Used in ODP, OData, HTTP functions
SELECT * FROM odp_odata_read('https://sap/odata/...', secret='sap_system');

http_bearer

For HTTP Bearer Token Authentication (API tokens).

Usage:

CREATE SECRET api_token (
TYPE http_bearer,
token 'your-bearer-token-here'
);

-- Used in HTTP requests
SELECT * FROM http_get('https://api.example.com/data');

datasphere

For SAP Datasphere OAuth2 authentication (custom type).

Usage:

CREATE SECRET datasphere (
TYPE datasphere,
PROVIDER oauth2,
TENANT_NAME 'your-tenant',
DATA_CENTER 'eu10',
SCOPE 'default'
);

-- Used in Datasphere functions
SELECT * FROM datasphere_show_spaces();

Secret Providers

Providers control how secrets are created and managed.

oauth2 Provider

Interactive OAuth2 flows for cloud services.

Features:

  • Opens browser for authentication
  • Stores access and refresh tokens
  • Automatic token refresh
  • Supports authorization_code and client_credentials flows

Authorization Code Flow (Interactive):

CREATE SECRET datasphere (
TYPE datasphere,
PROVIDER oauth2,
TENANT_NAME 'acme-corp',
DATA_CENTER 'eu10',
SCOPE 'default',
REDIRECT_URI 'http://localhost:65000' -- Optional, default shown
);

What happens:

  1. Local server starts on port 65000
  2. Browser opens to OAuth2 provider
  3. You log in with your credentials
  4. Authorization code is captured
  5. Tokens are exchanged and stored
  6. Secret is ready to use

Client Credentials Flow (Service Account):

CREATE SECRET datasphere_svc (
TYPE datasphere,
PROVIDER oauth2,
TENANT_NAME 'acme-corp',
DATA_CENTER 'eu10',
CLIENT_ID 'service-account-id',
CLIENT_SECRET 'service-account-secret',
GRANT_TYPE 'client_credentials',
SCOPE 'default'
);

No browser needed - uses service account credentials directly.


config Provider

Load secrets from INI-style configuration files.

Config File Format:

# ~/.erpl/sap-prod.conf
[sap_system]
username=SAP_USER
password=SAP_PASSWORD

[datasphere]
tenant_name=acme-corp
data_center=eu10
client_id=abc123
client_secret=secret456

Create Secret from Config:

CREATE SECRET sap_prod (
TYPE http_basic,
PROVIDER config,
CONFIG_FILE '/home/user/.erpl/sap-prod.conf',
SECTION 'sap_system' -- Optional, defaults to secret name
);

Benefits:

  • Separate credentials from code
  • Easy to manage multiple environments
  • Version control friendly (exclude config files)
  • Rotate credentials without changing SQL

file Provider

Load secrets from JSON or text files.

JSON File Format:

{
"tenant_name": "acme-corp",
"data_center": "eu10",
"access_token": "eyJhbGc...",
"refresh_token": "eyJhbGc...",
"expires_at": "2024-01-15T15:30:00Z"
}

Create Secret from File:

CREATE SECRET datasphere_prod (
TYPE datasphere,
PROVIDER file,
FILEPATH '/secure/credentials/datasphere-prod.json'
);

Use Cases:

  • Store pre-acquired tokens
  • Integration with secret managers (Vault, AWS Secrets Manager)
  • Share credentials across team
  • Backup/restore secrets

Managing Secrets

List Secrets

-- Show all secrets
SELECT * FROM duckdb_secrets();

Output:

┌─────────────┬──────────────┬───────────┬─────────┐
│ name │ type │ provider │ scope │
├─────────────┼──────────────┼───────────┼─────────┤
│ my_api │ http_basic │ inline │ user │
│ datasphere │ datasphere │ oauth2 │ user │
└─────────────┴──────────────┴───────────┴─────────┘

Update a Secret

-- Drop and recreate
DROP SECRET my_api;

CREATE SECRET my_api (
TYPE http_basic,
username 'new_user',
password 'new_password'
);

Remove a Secret

DROP SECRET my_api;

Temporary vs Persistent Secrets

-- Temporary (session only)
CREATE TEMPORARY SECRET temp_api (
TYPE http_basic,
username 'user',
password 'pass'
);

-- Persistent (saved to database)
CREATE SECRET persistent_api (
TYPE http_basic,
username 'user',
password 'pass'
);

OAuth2 Flows in Detail

Authorization Code Flow

Best for interactive use (desktop, notebooks).

CREATE SECRET interactive_ds (
TYPE datasphere,
PROVIDER oauth2,
TENANT_NAME 'mytenant',
DATA_CENTER 'eu10',
SCOPE 'default'
);

Step-by-Step:

  1. Secret creation starts OAuth2 flow
  2. Local callback server starts (default: http://localhost:65000)
  3. Browser opens to authorization URL
  4. User authenticates with username/password
  5. User grants permissions
  6. Browser redirects to callback with authorization code
  7. Extension exchanges code for tokens
  8. Tokens stored in secret (encrypted)

Token Refresh

Tokens expire (typically after 1 hour). ERPL-Web handles refresh automatically:

-- First use after token expiry
SELECT * FROM datasphere_show_spaces();
-- Automatically refreshes token if expired

-- You never need to manually refresh!

Client Credentials Flow

Best for automation (scripts, services, CI/CD).

CREATE SECRET service_ds (
TYPE datasphere,
PROVIDER oauth2,
TENANT_NAME 'mytenant',
DATA_CENTER 'eu10',
CLIENT_ID 'service-id',
CLIENT_SECRET 'service-secret',
GRANT_TYPE 'client_credentials',
TOKEN_URL 'https://mytenant.authentication.eu10.hana.ondemand.com/oauth/token', -- Optional
SCOPE 'default'
);

No browser interaction - ideal for:

  • Scheduled jobs
  • Docker containers
  • CI/CD pipelines
  • Backend services

Security Best Practices

1. Never Commit Secrets

# .gitignore
*.conf
*-credentials.json
.env
secrets/

2. Use Environment Variables

-- Good: Load from environment
CREATE SECRET api_key (
TYPE http_bearer,
token getenv('API_TOKEN')
);

-- Bad: Hardcoded
-- CREATE SECRET api_key (TYPE http_bearer, token 'abc123');

3. Restrict File Permissions

# Only owner can read secret files
chmod 600 ~/.erpl/credentials.conf
chmod 600 /secure/datasphere.json

4. Use Different Secrets Per Environment

-- Development
CREATE SECRET datasphere_dev (
TYPE datasphere,
PROVIDER oauth2,
TENANT_NAME 'acme-dev',
DATA_CENTER 'eu10'
);

-- Production
CREATE SECRET datasphere_prod (
TYPE datasphere,
PROVIDER oauth2,
TENANT_NAME 'acme-prod',
DATA_CENTER 'eu10'
);

-- Use explicitly
SELECT * FROM datasphere_show_spaces(secret := 'datasphere_prod');

5. Rotate Credentials Regularly

-- Update secret with new credentials
DROP SECRET old_api;
CREATE SECRET new_api (...);

-- Update all queries to use new secret

6. Use Temporary Secrets for Testing

-- Temporary secret (not saved to disk)
CREATE TEMPORARY SECRET test_api (
TYPE http_basic,
username 'test',
password 'test123'
);

-- Automatically dropped at end of session

Advanced Patterns

Multiple Tenants

-- Create secrets for each tenant
CREATE SECRET tenant_a (
TYPE datasphere,
PROVIDER oauth2,
TENANT_NAME 'tenant-a',
DATA_CENTER 'eu10'
);

CREATE SECRET tenant_b (
TYPE datasphere,
PROVIDER oauth2,
TENANT_NAME 'tenant-b',
DATA_CENTER 'us10'
);

-- Query specific tenant
SELECT * FROM datasphere_show_spaces(secret := 'tenant_a');
SELECT * FROM datasphere_show_spaces(secret := 'tenant_b');

Secret Rotation Without Downtime

-- Create new secret with new credentials
CREATE SECRET api_v2 (
TYPE http_basic,
username 'new_user',
password 'new_pass'
);

-- Test new secret
SELECT * FROM http_get('https://api.example.com/test', secret := 'api_v2');

-- If successful, make it the default
DROP SECRET api_v1;
ALTER SECRET api_v2 RENAME TO api_v1; -- If supported

-- Or update queries to use api_v2

Config File with Multiple Environments

# config.conf
[dev]
tenant_name=dev-tenant
data_center=eu10
client_id=dev-client

[staging]
tenant_name=staging-tenant
data_center=eu10
client_id=staging-client

[prod]
tenant_name=prod-tenant
data_center=eu10
client_id=prod-client
-- Load different environments
CREATE SECRET datasphere_dev (
TYPE datasphere,
PROVIDER config,
CONFIG_FILE 'config.conf',
SECTION 'dev'
);

CREATE SECRET datasphere_prod (
TYPE datasphere,
PROVIDER config,
CONFIG_FILE 'config.conf',
SECTION 'prod'
);

Integration with External Secret Managers

#!/bin/bash
# fetch_secret.sh - Retrieve from AWS Secrets Manager

aws secretsmanager get-secret-value \
--secret-id datasphere/prod \
--query SecretString \
--output text > /tmp/datasphere-creds.json
-- Use the fetched secret
CREATE SECRET datasphere (
TYPE datasphere,
PROVIDER file,
FILEPATH '/tmp/datasphere-creds.json'
);

-- Clean up after use
!rm /tmp/datasphere-creds.json

Troubleshooting

Secret Not Found

Issue: Error: Secret 'xyz' not found

Solutions:

-- List all secrets
SELECT name FROM duckdb_secrets();

-- Create the missing secret
CREATE SECRET xyz (...);

OAuth2 Flow Fails

Issue: Browser doesn't open or redirect fails

Solutions:

-- Try different redirect port
CREATE SECRET datasphere (
TYPE datasphere,
PROVIDER oauth2,
TENANT_NAME 'mytenant',
DATA_CENTER 'eu10',
REDIRECT_URI 'http://localhost:8080' -- Try 8080, 3000, etc.
);

-- Check firewall settings
-- Ensure localhost can bind to the port

Token Expired

Issue: 401 Unauthorized after period of inactivity

Solution:

-- Tokens auto-refresh, but if refresh fails:
DROP SECRET datasphere;
CREATE SECRET datasphere (...); -- Re-authenticate

Config File Not Found

Issue: Error: Could not read config file

Solutions:

# Check file exists
ls -la /path/to/config.conf

# Check permissions
chmod 600 /path/to/config.conf

# Use absolute path
CREATE SECRET (..., CONFIG_FILE '/home/user/.erpl/config.conf');

Wrong Credentials in Config

Issue: Authentication fails with config-based secret

Solutions:

# Verify config file format
[section_name]
key=value # No spaces around =
key2=value2

# Not this:
# key = value (spaces cause issues in some parsers)

Examples by Use Case

Local Development

-- Simple inline secret for local testing
CREATE TEMPORARY SECRET local_sap (
TYPE http_basic,
username 'DEV_USER',
password 'dev123'
);

Team Shared Config

# team-config.conf (committed to repo)
[sap_dev]
base_url=https://sap-dev.example.com
# Sensitive values in environment variables

[datasphere_dev]
tenant_name=team-dev-tenant
data_center=eu10
# Client ID/secret in environment
CREATE SECRET sap_dev (
TYPE http_basic,
PROVIDER config,
CONFIG_FILE 'team-config.conf',
SECTION 'sap_dev',
username getenv('SAP_DEV_USER'),
password getenv('SAP_DEV_PASS')
);

Production Deployment

# Use secret manager
export DATASPHERE_CREDS=$(vault kv get -field=json secret/datasphere/prod)
echo "$DATASPHERE_CREDS" > /run/secrets/datasphere.json
-- Load from mounted secret
CREATE SECRET datasphere (
TYPE datasphere,
PROVIDER file,
FILEPATH '/run/secrets/datasphere.json'
);

Next Steps


Summary

ERPL-Web's secrets management provides:

Secure - Encrypted storage, no plaintext passwords
Flexible - Multiple providers (oauth2, config, file)
Automatic - OAuth2 token refresh built-in
DuckDB Native - Integrated with DuckDB secrets system
Production Ready - Environment separation and rotation

Keep your credentials secure while querying APIs and SAP systems!