Skip to main content

Connector Security Best Practices

This guide describes security best practices for TDengine connectors, including token authentication, SSL/TLS client configuration, dynamic token rotation, and implementation examples across different language connectors.


1. Security Architecture Design

1.1 Why a Security Architecture Is Required

Security Threats

In untrusted network environments, TDengine connections face these threats:

  • Data leakage: Sensitive time-series data can be intercepted in transit.
  • Man-in-the-middle attacks: Attackers can impersonate server or client to steal or tamper with data.
  • Credential leakage: Hard-coded username/password can leak and are hard to revoke quickly.
  • Replay attacks: Captured legitimate requests can be replayed for unauthorized operations.

Design Goals

Security architecture goals for TDengine connectors:

  • Confidentiality: Encrypt all data in transit.
  • Integrity: Prevent tampering during transport.
  • Availability: Maintain business continuity with token rotation.
  • Auditability: Track and audit access via independent tokens.
  • Least privilege: Use different tokens for different applications.

1.2 Three-Layer Component Architecture

The TDengine connector security architecture contains three layers:

  1. Application layer: Business applications manage connection pools and token lifecycle, including SQL execution, parameter binding, schemaless writes, subscriptions, pool management (for example HikariCP), and token rotators that listen to config-center changes.

  2. Configuration center: Centralized storage/distribution of tokens via Nacos, Vault, or Kubernetes Secrets, with dynamic token push and shared configuration across instances.

  3. Server layer: TDengine Enterprise validates token legitimacy and handles encrypted requests, with optional load balancing by Nginx/HAProxy.

1.3 Overall Architecture Diagram

1.4 Core Design Principles

1. WebSocket + Token + SSL

The recommended TDengine connector security baseline is:

  • WebSocket connection: Cross-platform, stable, and supports SSL/TLS.
  • Token authentication: Replaces username/password, supports TTL and proactive revocation, and enables dynamic rotation.
  • SSL/TLS encryption: Client verifies server certificate to prevent MITM attacks and encrypt all traffic.

2. Prefer Direct Connector Access Over Nginx Forwarding

DimensionOption A: Nginx ForwardingOption B: Direct Connector Access (Recommended)
Load balancingNginx/HAProxyBuilt-in connector strategy
SSL handlingSSL offload at NginxEnd-to-end SSL at client
Failover5-10s health detectionAuto reconnect with minimal impact
PerformanceExtra network hopLower latency / better throughput
Certificate managementCentralizedPer-node configuration
TMQ stabilityPotential instabilityMore stable
Operations complexityExtra component requiredSimpler setup

Recommendation:

  • Prefer Option B (direct connector access).
  • Choose Option A (Nginx forwarding) only when you need centralized ingress/governance or strict integration with existing infrastructure.

3. Dynamic Token Rotation for Continuity

Tokens have TTL (typically 24 hours). After expiration, connections fail. Dynamic token rotation ensures:

  • Automatic rotation before expiration (for example at 80% TTL)
  • Transparent switchover
  • Automatic rollback on failure

2. SSL/TLS Configuration

Enabling SSL/TLS ensures confidentiality and integrity in data transport.

Responsibility Split
  • Server-side configuration: Certificate generation and taosAdapter SSL setup (taosadapter.toml), see SSL Configuration Guide
  • Client-side configuration: TrustStore and SSL connection parameters, covered in this section

2.1 Certificate Validation Principles

When establishing SSL/TLS, clients verify server certificates with:

  1. Certificate chain validation: Whether cert chain is issued by trusted CA.
  2. Hostname validation: Whether CN/SAN matches the target host or IP.
  3. Validity period validation: Whether certificate is currently valid.
  4. Revocation validation: Optional OCSP/CRL checks.

2.2 Client SSL/TLS Configuration

Java stores trusted certificates in a truststore. Use a dedicated truststore whenever possible to avoid polluting the system default truststore.

Import server certificate:

# Option 1: Import into JVM system truststore (requires JAVA_HOME)
sudo keytool -import -alias tdengine-server \
-file /etc/taos/server.crt \
-keystore $JAVA_HOME/lib/security/cacerts \
-storepass changeit -noprompt

# Option 2 (recommended): Use a dedicated truststore
keytool -import -alias tdengine-server \
-file server.crt \
-keystore ./tdengine-truststore.jks \
-storepass tdengine -noprompt

# Verify import
keytool -list -keystore ./tdengine-truststore.jks -storepass tdengine

Specify truststore when running Java application:

java -Djavax.net.ssl.trustStore=/path/to/tdengine-truststore.jks \
-Djavax.net.ssl.trustStorePassword=tdengine \
-jar your-app.jar

Verify SSL connection:

openssl s_client \
-connect td1.internal.taosdata.com:6041 \
-servername td1.internal.taosdata.com \
-CAfile ./server.crt \
-verify_hostname td1.internal.taosdata.com \
-verify_return_error < /dev/null

# Expected output
Verify return code: 0 (ok)

3. Token Authentication

3.1 Why Use Tokens

Token authentication in TDengine Enterprise is a lightweight authentication mechanism with advantages over username/password:

  • Time-bounded validity: TTL-based automatic expiration.
  • Revocability: Tokens can be revoked immediately.
  • Least privilege: Different scopes for different applications.
  • Audit-friendliness: Token-level activity tracking.

3.2 Create a Token

-- Create a token with 1-day TTL
CREATE TOKEN my_app_token FROM USER root ENABLE 1 PROVIDER 'root' TTL 1;

-- Query tokens created by current user
SHOW TOKENS;

3.3 Connect with Tokens

Token authentication is the recommended method and is generally more secure than username/password.

When using token authentication, only the token parameter is required. Do not set username/password at the same time.


4. Dynamic Token Rotation

4.1 Rotation Scenario Overview

Tokens expire (typically after 24 hours). Dynamic rotation keeps services running continuously.

Two rotation scenarios:

  1. Regular connections (SQL execution, parameter binding, schemaless writes)

    • Relies on connection pool lifecycle (maxLifetime in HikariCP)
    • New connections use new token, old connections age out naturally
    • Business logic remains transparent
  2. Data subscription (TMQ consumer)

    • Requires proactive consumer rebuild
    • Must commit offsets before switching
    • Graceful switch avoids message loss

4.2 Rotation for Regular Connections

Rotation Principle

Physical connections in the pool have a lifecycle (maxLifetime). Rotation reuses this mechanism:

Config center token update -> listener receives change -> update token in pool configuration -> new connections use new token -> old connections expire at maxLifetime -> transparent switchover.

Rotation Strategy

Pre-rotation: rotate before expiration at 80% TTL. For TTL=24h, trigger around hour 19-20. Keep old token valid for 5 more minutes as grace period.

Canary validation: validate new token on a small set of connections before full rollout. If validation fails, rollback automatically.

Rollback mechanism: log error, send alert, rollback to old token, retry after 1 hour.

Config-Center Integration: Nacos

Deploy Nacos server:

# Download and start Nacos in standalone mode (cluster mode is recommended for production)
cd /tmp
wget https://github.com/alibaba/nacos/releases/download/2.3.2/nacos-server-2.3.2.zip
unzip nacos-server-2.3.2.zip
cd nacos/bin
./startup.sh -m standalone

# Verify startup
curl http://localhost:8848/nacos/v1/ns/instance/list?serviceName=nacos

Write token to Nacos:

# Create config
curl -X POST "http://localhost:8848/nacos/v1/cs/configs" \
-d "dataId=tdengine-credential" \
-d "group=DEFAULT_GROUP" \
-d "content=token=your_new_token_here"

Read token from Nacos:

import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;

// Create Nacos config service
ConfigService nacos = NacosFactory.createConfigService("localhost:8848");

// Read token
String config = nacos.getConfig("tdengine-credential", "DEFAULT_GROUP", 5000);
if (config == null || config.isEmpty()) {
throw new IllegalStateException("Nacos config is empty");
}
String token = "";
for (String line : config.split("\n")) {
line = line.trim();
if (line.startsWith("token=")) {
token = line.substring("token=".length()).trim();
break;
}
}
if (token.isEmpty()) {
throw new IllegalStateException("token= not found in Nacos config");
}

Add listener to rotate pool automatically when token changes:

// Register Nacos listener with graceful rotation
nacos.addListener(DATA_ID, GROUP, poolListener);

view source code

4.3 Rotation for Data Subscription

Rotation Principle

TMQ consumers must be rebuilt during token rotation:

Create consumer -> subscribe -> consume. If subscribe fails, exit early with error.

During consumption, periodically check whether current time reaches refreshAt (computed as 80% TTL + random jitter up to 10% TTL). When reached, rotate with this sequence: build and subscribe new consumer first -> best-effort commit on old consumer -> unsubscribe and close old consumer -> switch to new consumer.

Rotation Workflow

TMQ Token Rotation State Machine:

Implementation Key Points

Rotation triggers in this state machine: periodic now >= refreshAt (80% TTL + jitter) and auth failure (0x80000357).

Initial subscribe failure handling: if initial subscribe fails, exit without entering the consume loop.

Offset commit timing: commit old offsets after the new consumer is ready; commit is best-effort to avoid auth-expired deadlocks.

Graceful switchover: fetch new token -> create and subscribe new consumer -> commit old offsets (best effort) -> unsubscribe and close old consumer -> switch references.

Rotation failure handling by trigger source: proactive rotation failure keeps old consumer and retries later; auth-failure recovery rotation failure exits.

4.4 Reliability and Resource Cleanup Requirements

Production-grade rotation depends on strict lifecycle management. Use these rules as baseline:

  1. Reuse listener executors: getExecutor() must return a shared executor, never create a new executor per callback.
  2. Always remove listeners: call removeListener when app exits or demo step ends.
  3. Bidirectional pool cleanup: close old pool after successful recovery; close new unswitched pool on recovery failure.
  4. Close TMQ consumer in skip path: if buildConsumer succeeds but subscribe fails (for example topic unavailable), close consumer immediately.
  5. Unified shutdown in finally: scheduler, listener executor, pool, and consumer should be closed idempotently with logs.

5. Connector Implementations

This section provides complete implementation examples.

5.1 Basic Connection (Token + SSL)

/** Current Token - updated by config center; volatile ensures visibility across threads */
private static volatile String currentToken =
System.getenv().getOrDefault("TDENGINE_TOKEN", "");

/**
* Scenario 1: basic secure connection.
* - Token authentication (replaces username/password)
* - useSSL=true enables transport encryption
* - Root CA in system trust store, no cert path needed in code
*/
public static void basicConnect() throws Exception {
String url = SecurityUtils.buildJdbcUrl(HOST, PORT, DB, currentToken);

try (Connection conn = DriverManager.getConnection(url);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT SERVER_VERSION()")) {
if (rs.next()) {
System.out.println("Connected. Server version: " + rs.getString(1));
}
}
}

view source code

Key parameters:

  • bearerToken: Bearer Token (WebSocket-only)
  • useSSL: Enable SSL/TLS

5.2 Token Rotation for Regular Connections

Connection pool configuration (maxLifetime = Token TTL / 2, so old connections expire naturally and new connections use latest token):

/**
* Scenario 2a: create a HikariCP connection pool.
*
* Key parameter: maxLifetime = Token TTL / 2 (recommended range: 1/4 ~ 1/2).
* Connections retire naturally at maxLifetime; new ones use the latest Token.
*/
public static HikariDataSource createPool(long tokenTtlMs) {
return createPool(tokenTtlMs, currentToken);
}

private static HikariDataSource createPool(long tokenTtlMs, String token) {
HikariConfig cfg = new HikariConfig();
cfg.setJdbcUrl(SecurityUtils.buildJdbcUrl(HOST, PORT, DB, token));
cfg.setMaximumPoolSize(10);
cfg.setMinimumIdle(2);
cfg.setMaxLifetime(tokenTtlMs / 2); // half of Token TTL; connections won't outlive the token
cfg.setKeepaliveTime(60_000); // keepalive probe every 60 s
cfg.setConnectionTestQuery("SELECT SERVER_STATUS()");
return new HikariDataSource(cfg);
}

view source code

Token rotation (called when Nacos listener receives a new token, switch to new pool then close old pool):

/**
* Scenario 2b: Token rotation - called by the config-center callback.
*
* Strategy:
* 1. Create a new pool with the new Token immediately (new requests go to new pool).
* 2. Close the old pool - waits for in-flight connections then shuts down gracefully.
* The two-pool overlap window is milliseconds; transparent to the application.
*/
public static HikariDataSource rotatePool(
HikariDataSource oldPool, String newToken, long tokenTtlMs) {
if (newToken == null || newToken.trim().isEmpty()) {
throw new IllegalArgumentException("newToken must not be null or empty");
}
HikariDataSource newPool = createPool(tokenTtlMs, newToken);
currentToken = newToken; // update the global Token after the new pool is ready
if (oldPool != null) {
oldPool.close(); // drain active connections, then close
}
return newPool;
}

view source code

Nacos listener integration:

// Register Nacos listener with graceful rotation
nacos.addListener(DATA_ID, GROUP, poolListener);

view source code

5.3 Token Rotation for Data Subscription

Consumer build (WebSocket + SSL + token parameters):

/** Current Token - updated by config center */
private static volatile String currentToken =
System.getenv().getOrDefault("TDENGINE_TOKEN", "");
private static volatile ConfigService nacosConfigService;

private static final TmqRotationManager<Map<String, Object>> TMQ_ROTATION_MANAGER =
new TmqRotationManager<Map<String, Object>>(SecurityTmqDemo::buildConsumerWithToken, TOPICS, logger);

/**
* Build a TMQ Consumer with the current Token and SSL (wss scheme).
* Using the same groupId after rotation resumes from the last committed offset.
*/
private static TaosConsumer<Map<String, Object>> buildConsumer() throws SQLException {
return buildConsumerWithToken(currentToken);
}

/**
* Build a TMQ Consumer with a specific Token and SSL (wss scheme).
*/
private static TaosConsumer<Map<String, Object>> buildConsumerWithToken(String token) throws SQLException {
Properties p = new Properties();

p.setProperty("bootstrap.servers", HOST + ":" + PORT);
p.setProperty("td.connect.type", "ws"); // use WebSocket consumer
p.setProperty("useSSL", "true"); // enable SSL for TMQ
p.setProperty("td.connect.token", token); // TMQ token for subscription auth
p.setProperty("auto.offset.reset", "earliest"); // consume from earliest offset for easy testing
p.setProperty("group.id", GROUP);
p.setProperty("enable.auto.commit", "false"); // manual commitSync
p.setProperty("msg.with.table.name", "true");
return new TaosConsumer<Map<String, Object>>(p);
}

view source code

Consume loop (proactive rotation + fallback on auth failure):

/**
* Consume loop with proactive rotation (80% TTL + jitter).
*
* Actual rotation order (implemented by {@link TmqRotationManager#tryRotate}):
* 1. fetchNewToken() - retrieve a fresh token from the config center
* 2. build + subscribe - create and verify a new consumer first
* 3. commitSync() - best-effort commit on old consumer
* 4. unsubscribe + close - release old consumer resources
* 5. switch reference - caller adopts the new consumer
*
* Note:
* - If step 2 fails, keep using the old consumer.
* - If step 3 fails, rotation still continues to avoid auth-expired deadlock.
*/
public static void consumeWithRotation(long tokenTtlMs) throws Exception {
currentToken = fetchNewToken();
if (currentToken == null || currentToken.isEmpty()) {
throw new IllegalStateException(
"No token found from Nacos config (dataId="
+ SecurityUtils.DATA_ID + ", group=" + SecurityUtils.GROUP + ")");
}

long createdAt = System.currentTimeMillis();
long refreshAt = nextRefreshAt(createdAt, tokenTtlMs);

TaosConsumer<Map<String, Object>> consumer =
TMQ_ROTATION_MANAGER.createAndSubscribe(currentToken, "[TMQ] initial");

try {
while (!Thread.currentThread().isInterrupted()) {
try {
ConsumerRecords<Map<String, Object>> records =
consumer.poll(Duration.ofMillis(500));
for (ConsumerRecord<Map<String, Object>> record : records) {
// process business logic here
Map<String, Object> map = record.value();
// processRecord(map);
}
if (!records.isEmpty()) {
consumer.commitSync(); // manual offset commit
}

// Proactive rotation: triggered at 80% of token lifetime (+ jitter)
if (System.currentTimeMillis() >= refreshAt) {
TmqRotationManager.RotationResult<Map<String, Object>> rotation =
rotateConsumer(consumer, "[TMQ] proactive-rotation");
if (!rotation.isSwitched()) {
logger.warn("[TMQ] proactive rotation failed: {}", rotation.getFailureReason());
refreshAt += SecurityUtils.ROTATION_RETRY_DELAY_MS;
continue;
}

consumer = rotation.getConsumer();
createdAt = System.currentTimeMillis();
refreshAt = nextRefreshAt(createdAt, tokenTtlMs);
logger.info("[TMQ] proactive rotation succeeded");
}

} catch (SQLException e) {
if (SecurityUtils.isAuthFailure(e)) {
logger.warn("[TMQ] authentication failure detected, attempting token rotation recovery: {}",
e.getMessage());
TmqRotationManager.RotationResult<Map<String, Object>> rotation =
rotateConsumer(consumer, "[TMQ] auth-failure-rotation");
if (!rotation.isSwitched()) {
logger.error("[TMQ] auth-failure rotation recovery failed: {}",
rotation.getFailureReason());
throw e;
}
consumer = rotation.getConsumer();
createdAt = System.currentTimeMillis();
refreshAt = nextRefreshAt(createdAt, tokenTtlMs);
logger.info("[TMQ] auth-failure rotation recovery succeeded");
continue;
}
if (SecurityUtils.isSecurityConnectError(e)) {
logger.error("[TMQ] security connection error (token/SSL). "
+ "Check token validity and TLS trust chain: {}", e.getMessage());
}
throw e;
}
}
} finally {
try {
consumer.commitSync();
} catch (SQLException e) {
logger.warn("[TMQ] final commit failed: {}", e.getMessage());
}
TMQ_ROTATION_MANAGER.closeQuietly(consumer, true, "[TMQ] final cleanup");
}
}

view source code


6. Advanced Topics

6.1 Performance Optimization

SSL/TLS Optimization

Certificate selection
Certificate TypeKey LengthPerformanceSecurityRecommended Use
RSA2048 bitsMediumHighGeneral purpose
RSA4096 bitsLowVery highHigh-security workloads
ECC256 bitsHighHighRecommended (best performance)
ECC384 bitsMediumVery highCompatibility-first deployments

Recommendation: prefer ECC certificates (for example ECDSA P-256). Compared with RSA 4096:

  • 50-70% less handshake time
  • ~60% lower CPU usage
  • Smaller certificates
TLS session resumption

Enable session resumption to reduce full handshakes:

# TDengine server-side configuration (if supported)
sslSessionCache internal
sslSessionCacheSize 10000
sslSessionTimeout 3600

Expected effect:

  • First handshake: ~100ms
  • Session resumption: ~10ms (about 90% reduction)
Cipher suite tuning

Disable weak ciphers and prioritize efficient ciphers:

# Recommended priority
1. TLS_AES_128_GCM_SHA256 (TLS 1.3, fastest)
2. TLS_CHACHA20_POLY1305_SHA256 (optimized for ARM)
3. TLS_AES_256_GCM_SHA384 (strong compatibility)

Connection Pool Optimization

HikariCP recommendations
ParameterRecommended ValueDescription
maximumPoolSize10-20Tune by CPU cores and concurrency
minimumIdle2-5Keep minimum idle connections to avoid cold start
maxLifetimeToken TTL / 2Critical: avoid connections outliving token
connectionTimeout3000030s timeout when acquiring connection
keepaliveTime600001-minute keepalive to detect dead connections
connectionTestQueryunsetIf unset, JDBC isValid is used

6.2 Certificate Management Best Practices

1. Wildcard certificates

Using wildcard certificates (for example *.yourdomain.com) can reduce certificate management complexity and update frequency. Limitation: only one subdomain level is covered, and private-key leakage has larger blast radius.

2. Automated certificate distribution

# 1. Generate certificates automatically with CFSSL or cert-manager
cfssl gencert -ca ca.crt -ca-key ca.key \
-config ca-config.json \
-hostname="td1.example.com,td2.example.com" \
-profile=server server-csr.json | cfssljson -bare server

# 2. Deploy automatically to TDengine servers
ansible-playbook deploy-ssl.yml --extra-vars "host=td1.example.com"

# 3. Restart service automatically
systemctl restart taosd

3. Certificate monitoring

# Periodically check certificate expiry time
for host in td1 td2 td3; do
expiry=$(echo | openssl s_client -connect $host.example.com:6041 2>/dev/null \
| openssl x509 -noout -enddate | cut -d= -f2)
echo "$host: $expiry"
done

# Integrate with monitoring systems (Prometheus/Zabbix)
# Alert 30 days before expiration

7. Security Checklist

Before production rollout, verify the following controls:

Transport Encryption

  • Enable SSL/TLS: all JDBC/REST/TMQ connections use encrypted transport (useSSL=true or equivalent).
  • Certificate validation: do not skip certificate verification in production.
  • Strong ciphers: TLS >= 1.2 and weak ciphers disabled (for example RC4, DES).

Token Management

  • Least privilege: grant only required permissions.
  • Reasonable TTL: not more than 7 days; 24 hours recommended.
  • No plaintext storage: avoid storing tokens in plaintext in code/config.
  • Secret management: use KMS/Vault or equivalent.
  • Regular rotation: rotate automatically before expiration.

Operations Security

  • Audit logs: record token creation/use/revocation.
  • Monitoring and alerts: token expiration, rotation failure, and connection anomalies.
  • Incident response: be able to revoke leaked tokens quickly.
  • Exposure isolation: use separate tokens per application.

Code Security

  • Log masking: avoid printing full tokens in logs.
  • Pool isolation: isolate pools by tenant/service where required.
  • Exception handling: fail gracefully on token invalidation without leaking sensitive details.

8. Troubleshooting

8.1 Common Error Quick Reference

Error MessageRoot CauseSolution
SSLHandshakeException: PKIX path building failedCertificate not trustedImport CA certificate into truststore:
keytool -import -alias tdengine -file ca.crt -keystore truststore.jks
SSLHandshakeException: hostname in certificate didn't matchCertificate CN/SAN does not match target host1. Regenerate certificate with correct CN/SAN
2. Or connect with matching host
SSLException: Connection reset by peertaosAdapter SSL is disabled or misconfigured1. Check [ssl] enable = true in taosadapter.toml
2. Check logs: journalctl -u taosadapter -n 100
SQLException: Token expiredToken expired1. Create new token:
CREATE TOKEN new_token FROM USER root TTL 1
2. Configure auto rotation
auth failure: Invalid password lengthWrong token parameter nameFor WebSocket use bearerToken, not token

8.2 Debug Commands

Verify SSL connectivity: use openssl s_client -connect td1.internal.taosdata.com:6041 -showcerts. Expected: Verify return code: 0 (ok), successful TLS handshake, and CN/SAN matching hostname.

Test token connectivity: use curl -v "http://td1.internal.taosdata.com:6041/rest/sql/%22SELECT%20SERVER_VERSION()%22" -H "Authorization: Bearer your_token_here" for REST token authentication.

Check Nacos config: use curl -X GET "http://localhost:8848/nacos/v1/cs/configs?dataId=tdengine-credential&group=DEFAULT_GROUP". Expected output contains token=your_actual_token.


9. FAQ

1. Token connection failed with auth failure: Invalid password length

Cause: wrong parameter name. For WebSocket connections, use bearerToken, not token.

Fix:

// Wrong
String url = "jdbc:TAOS-WS://host:6041/db?token=xxx";

// Correct
String url = "jdbc:TAOS-WS://host:6041/db?bearerToken=xxx";

2. SSL connection failed with certificate verify failed

Cause: client cannot validate the server certificate.

Checklist: verify server SSL with openssl s_client -connect host:port -showcerts, confirm CN/SAN matches target hostname, and confirm CA certificate is imported into client truststore.

3. Nacos listener is not effective

Cause: ConfigService is not initialized correctly or listener registration failed.

Checklist: ensure Nacos service is running (curl http://localhost:8848/nacos/v1/ns/instance/list), verify dataId/group, and verify network/firewall accessibility.


10. Reference Documents