• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

taosdata / TDengine / #5071

17 May 2026 01:15AM UTC coverage: 63.054% (-10.3%) from 73.326%
#5071

push

travis-ci

web-flow
feat (TDgpt): Dynamic Model Synchronization Enhancements (#35344)

* refactor: do some internal refactor.

* fix: fix multiprocess sync issue.

* feat: add dynamic anomaly detection and forecasting services

* fix: log error message for undeploying model in exception handling

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* fix: handle undeploy when model exists only on disk

Agent-Logs-Url: https://github.com/taosdata/TDengine/sessions/286aafa0-c3ce-4c27-b803-2707571e9dc1

Co-authored-by: hjxilinx <8252296+hjxilinx@users.noreply.github.com>

* fix: guard dynamic registry concurrent access

Agent-Logs-Url: https://github.com/taosdata/TDengine/sessions/5e4db858-6458-40f4-ac28-d1b1b7f97c18

Co-authored-by: hjxilinx <8252296+hjxilinx@users.noreply.github.com>

* fix: tighten service list locking scope

Agent-Logs-Url: https://github.com/taosdata/TDengine/sessions/5e4db858-6458-40f4-ac28-d1b1b7f97c18

Co-authored-by: hjxilinx <8252296+hjxilinx@users.noreply.github.com>

* fix: restore prophet support and update tests per review feedback

Agent-Logs-Url: https://github.com/taosdata/TDengine/sessions/92298ae1-7da6-4d07-b20e-101c7cd0b26b

Co-authored-by: hjxilinx <8252296+hjxilinx@users.noreply.github.com>

* fix: improve test name and move copy inside lock scope

Agent-Logs-Url: https://github.com/taosdata/TDengine/sessions/92298ae1-7da6-4d07-b20e-101c7cd0b26b

Co-authored-by: hjxilinx <8252296+hjxilinx@users.noreply.github.com>

* Potential fix for pull request finding

Co-au... (continued)

238317 of 377957 relevant lines covered (63.05%)

130539817.12 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

25.4
/source/dnode/mnode/impl/src/mndSecurityPolicy.c
1
/*
2
 * Copyright (c) 2019 TAOS Data, Inc. <jhtao@taosdata.com>
3
 *
4
 * This program is free software: you can use, redistribute, and/or modify
5
 * it under the terms of the GNU Affero General Public License, version 3
6
 * or later ("AGPL"), as published by the Free Software Foundation.
7
 *
8
 * This program is distributed in the hope that it will be useful, but WITHOUT
9
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10
 * FITNESS FOR A PARTICULAR PURPOSE.
11
 *
12
 * You should have received a copy of the GNU Affero General Public License
13
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
14
 */
15

16
#define _DEFAULT_SOURCE
17
#include "mndSecurityPolicy.h"
18
#include "mndCluster.h"
19
#include "mndMnode.h"
20
#include "mndPrivilege.h"
21
#include "mndRole.h"
22
#include "mndShow.h"
23
#include "mndTrans.h"
24
#include "mndUser.h"
25

26
// SDB raw format version.  Bump when adding new persistent fields.
27
#define SEC_POLICY_VER_NUMBE 1
28

29
static SSdbRaw *mndSecPolicyActionEncode(SSecurityPolicyObj *pObj);
30
static SSdbRow *mndSecPolicyActionDecode(SSdbRaw *pRaw);
31
static int32_t  mndSecPolicyActionInsert(SSdb *pSdb, SSecurityPolicyObj *pObj);
32
static int32_t  mndSecPolicyActionDelete(SSdb *pSdb, SSecurityPolicyObj *pObj);
33
static int32_t  mndSecPolicyActionUpdate(SSdb *pSdb, SSecurityPolicyObj *pOld, SSecurityPolicyObj *pNew);
34
static int32_t  mndCreateDefaultSecurityPolicy(SMnode *pMnode);
35
static int32_t  mndRetrieveSecurityPolicies(SRpcMsg *pMsg, SShowObj *pShow, SSDataBlock *pBlock, int32_t rows);
36
static void     mndCancelGetNextSecurityPolicy(SMnode *pMnode, void *pIter);
37
static int32_t  mndProcessEnforceSodImpl(SMnode *pMnode);
38

39
int32_t mndInitSecurityPolicy(SMnode *pMnode) {
404,715✔
40
  SSdbTable table = {
404,715✔
41
      .sdbType = SDB_SECURITY_POLICY,
42
      .keyType = SDB_KEY_INT32,
43
      .deployFp = (SdbDeployFp)mndCreateDefaultSecurityPolicy,
44
      .encodeFp = (SdbEncodeFp)mndSecPolicyActionEncode,
45
      .decodeFp = (SdbDecodeFp)mndSecPolicyActionDecode,
46
      .insertFp = (SdbInsertFp)mndSecPolicyActionInsert,
47
      .updateFp = (SdbUpdateFp)mndSecPolicyActionUpdate,
48
      .deleteFp = (SdbDeleteFp)mndSecPolicyActionDelete,
49
  };
50

51
  mndAddShowRetrieveHandle(pMnode, TSDB_MGMT_TABLE_SECURITY_POLICIES, mndRetrieveSecurityPolicies);
404,715✔
52
  mndAddShowFreeIterHandle(pMnode, TSDB_MGMT_TABLE_SECURITY_POLICIES, mndCancelGetNextSecurityPolicy);
404,715✔
53

54
  // Initialize MAC state cache — starts disabled, updated on successful activation
55
  pMnode->macActive = MAC_MODE_DISABLED;
404,715✔
56

57
  return sdbSetTable(pMnode->pSdb, table);
404,715✔
58
}
59

60
void mndCleanupSecurityPolicy(SMnode *pMnode) {}
404,715✔
61

62
// ---- SDB encode/decode ----
63

64
static SSdbRaw *mndSecPolicyActionEncode(SSecurityPolicyObj *pObj) {
2,199,114✔
65
  int32_t code = 0;
2,199,114✔
66
  int32_t lino = 0;
2,199,114✔
67
  terrno = TSDB_CODE_OUT_OF_MEMORY;
2,199,114✔
68

69
  SSdbRaw *pRaw = sdbAllocRaw(SDB_SECURITY_POLICY, SEC_POLICY_VER_NUMBE, sizeof(SSecurityPolicyObj));
2,199,114✔
70
  if (pRaw == NULL) goto _OVER;
2,199,114✔
71

72
  int32_t dataPos = 0;
2,199,114✔
73
  SDB_SET_INT32(pRaw, dataPos, pObj->type, _OVER)
2,199,114✔
74
  SDB_SET_INT64(pRaw, dataPos, pObj->createdTime, _OVER)
2,199,114✔
75
  SDB_SET_INT64(pRaw, dataPos, pObj->updateTime, _OVER)
2,199,114✔
76
  SDB_SET_INT64(pRaw, dataPos, pObj->activateTime, _OVER)
2,199,114✔
77
  SDB_SET_UINT8(pRaw, dataPos, pObj->status, _OVER)
2,199,114✔
78
  SDB_SET_BINARY(pRaw, dataPos, pObj->activator, TSDB_USER_LEN, _OVER)
2,199,114✔
79
  SDB_SET_BINARY(pRaw, dataPos, pObj->reserve, sizeof(pObj->reserve), _OVER)
2,199,114✔
80
  SDB_SET_DATALEN(pRaw, dataPos, _OVER);
2,199,114✔
81

82
  terrno = 0;
2,199,114✔
83

84
_OVER:
2,199,114✔
85
  if (terrno != 0) {
2,199,114✔
86
    mError("secpolicy:%d, failed to encode to raw:%p since %s", pObj->type, pRaw, terrstr());
×
87
    sdbFreeRaw(pRaw);
×
88
    return NULL;
×
89
  }
90

91
  mTrace("secpolicy:%d, encode to raw:%p, row:%p", pObj->type, pRaw, pObj);
2,199,114✔
92
  return pRaw;
2,199,114✔
93
}
94

95
static SSdbRow *mndSecPolicyActionDecode(SSdbRaw *pRaw) {
809,430✔
96
  int32_t code = 0;
809,430✔
97
  int32_t lino = 0;
809,430✔
98
  terrno = TSDB_CODE_OUT_OF_MEMORY;
809,430✔
99
  SSecurityPolicyObj *pObj = NULL;
809,430✔
100
  SSdbRow            *pRow = NULL;
809,430✔
101

102
  int8_t sver = 0;
809,430✔
103
  if (sdbGetRawSoftVer(pRaw, &sver) != 0) goto _OVER;
809,430✔
104

105
  if (sver != SEC_POLICY_VER_NUMBE) {
809,430✔
106
    terrno = TSDB_CODE_SDB_INVALID_DATA_VER;
×
107
    goto _OVER;
×
108
  }
109

110
  pRow = sdbAllocRow(sizeof(SSecurityPolicyObj));
809,430✔
111
  if (pRow == NULL) goto _OVER;
809,430✔
112

113
  pObj = sdbGetRowObj(pRow);
809,430✔
114
  if (pObj == NULL) goto _OVER;
809,430✔
115

116
  int32_t dataPos = 0;
809,430✔
117
  SDB_GET_INT32(pRaw, dataPos, &pObj->type, _OVER)
809,430✔
118
  SDB_GET_INT64(pRaw, dataPos, &pObj->createdTime, _OVER)
809,430✔
119
  SDB_GET_INT64(pRaw, dataPos, &pObj->updateTime, _OVER)
809,430✔
120
  SDB_GET_INT64(pRaw, dataPos, &pObj->activateTime, _OVER)
809,430✔
121
  SDB_GET_UINT8(pRaw, dataPos, &pObj->status, _OVER)
809,430✔
122
  SDB_GET_BINARY(pRaw, dataPos, pObj->activator, TSDB_USER_LEN, _OVER)
809,430✔
123
  SDB_GET_BINARY(pRaw, dataPos, pObj->reserve, sizeof(pObj->reserve), _OVER)
809,430✔
124

125
  terrno = 0;
809,430✔
126

127
_OVER:
809,430✔
128
  if (terrno != 0) {
809,430✔
129
    mError("secpolicy:%d, failed to decode from raw:%p since %s", pObj == NULL ? 0 : pObj->type, pRaw, terrstr());
×
130
    taosMemoryFreeClear(pRow);
×
131
    return NULL;
×
132
  }
133

134
  mTrace("secpolicy:%d, decode from raw:%p, row:%p", pObj->type, pRaw, pObj);
809,430✔
135
  return pRow;
809,430✔
136
}
137

138
static int32_t mndSecPolicyActionInsert(SSdb *pSdb, SSecurityPolicyObj *pObj) {
809,430✔
139
  mTrace("secpolicy:%d, perform insert action, row:%p", pObj->type, pObj);
809,430✔
140
  // Sync MAC state cache: fires on startup SDB replay, restoring persisted state
141
  if (pObj->type == TSDB_SECURITY_POLICY_MAC) {
809,430✔
142
    pSdb->pMnode->macActive = pObj->status;
404,715✔
143
  }
144
  return 0;
809,430✔
145
}
146

147
static int32_t mndSecPolicyActionDelete(SSdb *pSdb, SSecurityPolicyObj *pObj) {
809,430✔
148
  mTrace("secpolicy:%d, perform delete action, row:%p", pObj->type, pObj);
809,430✔
149
  return 0;
809,430✔
150
}
151

152
static int32_t mndSecPolicyActionUpdate(SSdb *pSdb, SSecurityPolicyObj *pOld, SSecurityPolicyObj *pNew) {
×
153
  mTrace("secpolicy:%d, perform update action, old row:%p new row:%p", pOld->type, pOld, pNew);
×
154
  pOld->updateTime = pNew->updateTime;
×
155
  pOld->activateTime = pNew->activateTime;
×
156
  pOld->status = pNew->status;
×
157
  tstrncpy(pOld->activator, pNew->activator, sizeof(pOld->activator));
×
158
  (void)memcpy(pOld->reserve, pNew->reserve, sizeof(pOld->reserve));
×
159
  // Sync MAC state cache: fires when Raft commit-log is applied — the true success point
160
  if (pOld->type == TSDB_SECURITY_POLICY_MAC) {
×
161
    pSdb->pMnode->macActive = pOld->status;
×
162
  }
163
  return 0;
×
164
}
165

166
// ---- Deploy helpers ----
167

168
// Append a single policy row as a commit-log entry in pTrans.
169
static int32_t mndAppendPolicyToTrans(STrans *pTrans, int32_t policyType) {
622,044✔
170
  SSecurityPolicyObj obj = {0};
622,044✔
171
  obj.type = policyType;
622,044✔
172
  obj.createdTime = taosGetTimestampMs();
622,044✔
173
  obj.updateTime = obj.createdTime;
622,044✔
174
  obj.activateTime = obj.createdTime;
622,044✔
175
  // status defaults to 0 (SEC_POLICY_STATUS_DEFAULT) for both SOD and MAC
176

177
  SSdbRaw *pRaw = mndSecPolicyActionEncode(&obj);
622,044✔
178
  if (pRaw == NULL) return terrno;
622,044✔
179

180
  int32_t code = sdbSetRawStatus(pRaw, SDB_STATUS_READY);
622,044✔
181
  if (code != 0) {
622,044✔
182
    sdbFreeRaw(pRaw);
×
183
    return code;
×
184
  }
185

186
  code = mndTransAppendCommitlog(pTrans, pRaw);
622,044✔
187
  if (code != 0) {
622,044✔
188
    // pRaw ownership transferred on success; free only on failure
189
    sdbFreeRaw(pRaw);
×
190
    return code;
×
191
  }
192

193
  // Second sdbSetRawStatus marks it ready-for-commit (matches cluster deploy pattern)
194
  return sdbSetRawStatus(pRaw, SDB_STATUS_READY);
622,044✔
195
}
196

197
static int32_t mndCreateDefaultSecurityPolicy(SMnode *pMnode) {
311,022✔
198
  // One transaction creates both SOD and MAC rows.
199
  // key = int32 policyType, no dependency on clusterId → deploy order safe.
200
  STrans *pTrans = mndTransCreate(pMnode, TRN_POLICY_RETRY, TRN_CONFLICT_NOTHING, NULL, "create-security-policy");
311,022✔
201
  if (pTrans == NULL) return terrno;
311,022✔
202

203
  int32_t code = 0;
311,022✔
204
  if ((code = mndAppendPolicyToTrans(pTrans, TSDB_SECURITY_POLICY_SOD)) != 0) goto _OVER;
311,022✔
205
  if ((code = mndAppendPolicyToTrans(pTrans, TSDB_SECURITY_POLICY_MAC)) != 0) goto _OVER;
311,022✔
206

207
  mInfo("trans:%d, used to create default security policies (SOD + MAC)", pTrans->id);
311,022✔
208

209
  if ((code = mndTransPrepare(pMnode, pTrans)) != 0) {
311,022✔
210
    mError("trans:%d, failed to prepare since %s", pTrans->id, tstrerror(code));
×
211
  }
212

213
_OVER:
311,022✔
214
  mndTransDrop(pTrans);
311,022✔
215
  return code;
311,022✔
216
}
217

218
// ---- SDB acquire/release helpers ----
219

220
static SSecurityPolicyObj *mndAcquireSecPolicy(SMnode *pMnode, int32_t policyType) {
10,610✔
221
  return (SSecurityPolicyObj *)sdbAcquire(pMnode->pSdb, SDB_SECURITY_POLICY, &policyType);
10,610✔
222
}
223

224
static void mndReleaseSecPolicy(SMnode *pMnode, SSecurityPolicyObj *pObj) { sdbRelease(pMnode->pSdb, pObj); }
10,610✔
225

226
// ---- Accessor functions ----
227

228
int32_t mndGetClusterSoDMode(SMnode *pMnode) {
10,610✔
229
  int32_t             sodMode = SOD_MODE_ENABLED;
10,610✔
230
  SSecurityPolicyObj *pObj = mndAcquireSecPolicy(pMnode, TSDB_SECURITY_POLICY_SOD);
10,610✔
231
  if (pObj != NULL) {
10,610✔
232
    sodMode = pObj->status;
10,610✔
233
    mndReleaseSecPolicy(pMnode, pObj);
10,610✔
234
  }
235
  return sodMode;
10,610✔
236
}
237

238
// ---- show security_policies ----
239

240
static const char *_SoDMandatoryInfo[3][2] = {
241
    {"mandatory", "system is operational, root disabled permanently"},
242
    {"mandatory(initial)", "Initial phase: mandatory roles missing, only account setup operations are allowed"},
243
    {"mandatory(enforcing)", "Enforce phase: transitioning mode, account destructive operations are blocked"},
244
};
245

246
static int32_t mndRetrieveSecurityPolicies(SRpcMsg *pMsg, SShowObj *pShow, SSDataBlock *pBlock, int32_t rows) {
×
247
  SMnode             *pMnode = pMsg->info.node;
×
248
  SSdb               *pSdb = pMnode->pSdb;
×
249
  int32_t             code = 0, lino = 0;
×
250
  int32_t             numOfRows = 0;
×
251
  SSecurityPolicyObj *pObj = NULL;
×
252
  char                buf[128 + VARSTR_HEADER_SIZE] = {0};
×
253

254
  while (numOfRows < rows) {
×
255
    pShow->pIter = sdbFetch(pSdb, SDB_SECURITY_POLICY, pShow->pIter, (void **)&pObj);
×
256
    if (pShow->pIter == NULL) break;
×
257

258
    int32_t cols = 0;
×
259

260
    if (pObj->type == TSDB_SECURITY_POLICY_SOD) {
×
261
      int32_t sodPhase = mndGetSoDPhase(pMnode);
×
262
      bool    sodEnabled = (pObj->status == SOD_MODE_ENABLED);
×
263

264
      STR_WITH_MAXSIZE_TO_VARSTR(buf, "SoD", pShow->pMeta->pSchemas[cols].bytes);
×
265
      SColumnInfoData *pColInfo = taosArrayGet(pBlock->pDataBlock, cols++);
×
266
      COL_DATA_SET_VAL_GOTO(buf, false, pObj, pShow->pIter, _OVER);
×
267

268
      STR_WITH_MAXSIZE_TO_VARSTR(buf, sodEnabled ? "enabled" : _SoDMandatoryInfo[sodPhase][0],
×
269
                                 pShow->pMeta->pSchemas[cols].bytes);
270
      pColInfo = taosArrayGet(pBlock->pDataBlock, cols++);
×
271
      COL_DATA_SET_VAL_GOTO(buf, false, pObj, pShow->pIter, _OVER);
×
272

273
      STR_WITH_MAXSIZE_TO_VARSTR(buf, sodEnabled ? "SYSTEM" : pObj->activator, pShow->pMeta->pSchemas[cols].bytes);
×
274
      pColInfo = taosArrayGet(pBlock->pDataBlock, cols++);
×
275
      COL_DATA_SET_VAL_GOTO(buf, false, pObj, pShow->pIter, _OVER);
×
276

277
      pColInfo = taosArrayGet(pBlock->pDataBlock, cols++);
×
278
      COL_DATA_SET_VAL_GOTO((const char *)&pObj->updateTime, false, pObj, pShow->pIter, _OVER);
×
279

280
      STR_WITH_MAXSIZE_TO_VARSTR(buf, sodEnabled ? "non-mandatory, root not disabled" : _SoDMandatoryInfo[sodPhase][1],
×
281
                                 pShow->pMeta->pSchemas[cols].bytes);
282
      pColInfo = taosArrayGet(pBlock->pDataBlock, cols++);
×
283
      COL_DATA_SET_VAL_GOTO(buf, false, pObj, pShow->pIter, _OVER);
×
284

285
    } else if (pObj->type == TSDB_SECURITY_POLICY_MAC) {
×
286
      bool macActive = (pObj->status == MAC_MODE_MANDATORY);
×
287

288
      STR_WITH_MAXSIZE_TO_VARSTR(buf, "MAC", pShow->pMeta->pSchemas[cols].bytes);
×
289
      SColumnInfoData *pColInfo = taosArrayGet(pBlock->pDataBlock, cols++);
×
290
      COL_DATA_SET_VAL_GOTO(buf, false, pObj, pShow->pIter, _OVER);
×
291

292
      STR_WITH_MAXSIZE_TO_VARSTR(buf, macActive ? "mandatory" : "disabled", pShow->pMeta->pSchemas[cols].bytes);
×
293
      pColInfo = taosArrayGet(pBlock->pDataBlock, cols++);
×
294
      COL_DATA_SET_VAL_GOTO(buf, false, pObj, pShow->pIter, _OVER);
×
295

296
      STR_WITH_MAXSIZE_TO_VARSTR(buf, macActive ? pObj->activator : "SYSTEM", pShow->pMeta->pSchemas[cols].bytes);
×
297
      pColInfo = taosArrayGet(pBlock->pDataBlock, cols++);
×
298
      COL_DATA_SET_VAL_GOTO(buf, false, pObj, pShow->pIter, _OVER);
×
299

300
      pColInfo = taosArrayGet(pBlock->pDataBlock, cols++);
×
301
      COL_DATA_SET_VAL_GOTO((const char *)&pObj->updateTime, false, pObj, pShow->pIter, _OVER);
×
302

303
      STR_WITH_MAXSIZE_TO_VARSTR(buf, macActive ? "security levels 0-4; activated, irreversible" : "not activated",
×
304
                                 pShow->pMeta->pSchemas[cols].bytes);
305
      pColInfo = taosArrayGet(pBlock->pDataBlock, cols++);
×
306
      COL_DATA_SET_VAL_GOTO(buf, false, pObj, pShow->pIter, _OVER);
×
307

308
    } else {
309
      // Unknown policy type — skip row silently
310
      sdbRelease(pSdb, pObj);
×
311
      continue;
×
312
    }
313

314
    sdbRelease(pSdb, pObj);
×
315
    ++numOfRows;
×
316
  }
317

318
  pShow->numOfRows += numOfRows;
×
319

320
_OVER:
×
321
  if (code != 0) {
×
322
    mError("failed to retrieve security policies at line %d since %s", lino, tstrerror(code));
×
323
    TAOS_RETURN(code);
×
324
  }
325
  return numOfRows;
×
326
}
327

328
static void mndCancelGetNextSecurityPolicy(SMnode *pMnode, void *pIter) {
×
329
  SSdb *pSdb = pMnode->pSdb;
×
330
  sdbCancelFetchByType(pSdb, pIter, SDB_SECURITY_POLICY);
×
331
}
×
332

333
// ---- SoD config handler ----
334

335
// #ifdef TD_ENTERPRISE
336
int32_t mndProcessConfigSoDReq(SMnode *pMnode, SRpcMsg *pReq, SMCfgClusterReq *pCfg) {
×
337
  int32_t code = 0, lino = 0;
×
338
#ifdef TD_ENTERPRISE
339
  SSecurityPolicyObj obj = {0};
×
340
  STrans            *pTrans = NULL;
×
341
  SUserObj          *pRootUser = NULL;
×
342
  SUserObj           newRootUser = {0};
×
343

344
  TAOS_CHECK_EXIT(mndCheckOperPrivilege(pMnode, RPC_MSG_USER(pReq), RPC_MSG_TOKEN(pReq), MND_OPER_CONFIG_SOD));
×
345

346
  if (taosStrncasecmp(pCfg->value, "mandatory", 10) != 0) {
×
347
    TAOS_CHECK_EXIT(TSDB_CODE_INVALID_CFG_VALUE);
×
348
  }
349

350
  SSecurityPolicyObj *pObj = mndAcquireSecPolicy(pMnode, TSDB_SECURITY_POLICY_SOD);
×
351
  if (!pObj) {
×
352
    TAOS_CHECK_EXIT(TSDB_CODE_APP_IS_STARTING);
×
353
  }
354

355
  if (pObj->status == SOD_MODE_MANDATORY) {
×
356
    mndReleaseSecPolicy(pMnode, pObj);
×
357
    TAOS_RETURN(0);
×
358
  }
359

360
  if ((code = mndCheckManagementRoleStatus(pMnode, NULL, 0))) {
×
361
    mndReleaseSecPolicy(pMnode, pObj);
×
362
    TAOS_CHECK_EXIT(code);
×
363
  }
364

365
  mInfo("update security policy SoD mode to mandatory by %s", RPC_MSG_USER(pReq));
×
366
  (void)memcpy(&obj, pObj, sizeof(SSecurityPolicyObj));
×
367
  obj.status = SOD_MODE_MANDATORY;
×
368
  obj.activateTime = taosGetTimestampMs();
×
369
  obj.updateTime = obj.activateTime;
×
370
  tstrncpy(obj.activator, RPC_MSG_USER(pReq), sizeof(obj.activator));
×
371
  mndReleaseSecPolicy(pMnode, pObj);
×
372

373
  TAOS_CHECK_EXIT(mndAcquireUser(pMnode, "root", &pRootUser));
×
374
  TAOS_CHECK_EXIT(mndUserDupObj(pRootUser, &newRootUser));
×
375
  newRootUser.enable = 0;
×
376

377
  pTrans = mndTransCreate(pMnode, TRN_POLICY_ROLLBACK, TRN_CONFLICT_ROLE, pReq, "update-sod-mode");
×
378
  if (pTrans == NULL) {
×
379
    TAOS_CHECK_EXIT(terrno);
×
380
  }
381

382
  SSdbRaw *pCommitRaw = mndSecPolicyActionEncode(&obj);
×
383
  if (pCommitRaw == NULL || mndTransAppendCommitlog(pTrans, pCommitRaw) != 0) {
×
384
    if (pCommitRaw) sdbFreeRaw(pCommitRaw);
×
385
    TAOS_CHECK_EXIT(terrno);
×
386
  }
387
  TAOS_CHECK_EXIT(sdbSetRawStatus(pCommitRaw, SDB_STATUS_READY));
×
388

389
  SSdbRaw *pCommitRawRoot = mndUserActionEncode(&newRootUser);
×
390
  if (pCommitRawRoot == NULL || mndTransAppendCommitlog(pTrans, pCommitRawRoot) != 0) {
×
391
    if (pCommitRawRoot) sdbFreeRaw(pCommitRawRoot);
×
392
    TAOS_CHECK_EXIT(terrno);
×
393
  }
394
  TAOS_CHECK_EXIT(sdbSetRawStatus(pCommitRawRoot, SDB_STATUS_READY));
×
395

396
  mndSetSoDPhase(pMnode, TSDB_SOD_PHASE_ENFORCE);
×
397
  mndTransSetCb(pTrans, 0, TRANS_STOP_FUNC_SOD, NULL, 0);
×
398
  if ((code = mndTransPrepare(pMnode, pTrans)) != 0) {
×
399
    mndSetSoDPhase(pMnode, TSDB_SOD_PHASE_STABLE);
×
400
    TAOS_CHECK_EXIT(code);
×
401
  }
402

403
_exit:
×
404
  if (pRootUser) mndReleaseUser(pMnode, pRootUser);
×
405
  mndUserFreeObj(&newRootUser);
×
406
  mndTransDrop(pTrans);
×
407
  if (code < 0 && code != TSDB_CODE_ACTION_IN_PROGRESS) {
×
408
    mError("%s failed at line %d since %s", __func__, lino, tstrerror(code));
×
409
  }
410
#endif
411
  TAOS_RETURN(code);
×
412
}
413
// #endif
414

415
// ---- MAC config handler ----
416

417
// #ifdef TD_ENTERPRISE
418
int32_t mndProcessConfigMacReq(SMnode *pMnode, SRpcMsg *pReq, SMCfgClusterReq *pCfg) {
×
419
  int32_t            code = 0, lino = 0;
×
420
  SSecurityPolicyObj obj = {0};
×
421
  STrans            *pTrans = NULL;
×
422
  SUserObj          *pScanUser = NULL;
×
423
  void              *pScanIter = NULL;
×
424

425
  TAOS_CHECK_EXIT(mndCheckOperPrivilege(pMnode, RPC_MSG_USER(pReq), RPC_MSG_TOKEN(pReq), MND_OPER_CONFIG_MAC));
×
426

427
  if (taosStrncasecmp(pCfg->value, "mandatory", 10) != 0) {
×
428
    TAOS_CHECK_EXIT(TSDB_CODE_INVALID_CFG_VALUE);
×
429
  }
430

431
  SSecurityPolicyObj *pObj = mndAcquireSecPolicy(pMnode, TSDB_SECURITY_POLICY_MAC);
×
432
  if (!pObj) {
×
433
    TAOS_CHECK_EXIT(TSDB_CODE_APP_IS_STARTING);
×
434
  }
435

436
  if (pObj->status == MAC_MODE_MANDATORY) {
×
437
    mInfo("cluster MAC is already mandatory, ignoring repeated activation by %s", RPC_MSG_USER(pReq));
×
438
    mndReleaseSecPolicy(pMnode, pObj);
×
439
    TAOS_RETURN(0);
×
440
  }
441

442
  // Pre-activation check:
443
  // (A) Every user holding any system role (SYSSEC/SYSAUDIT/SYSAUDIT_LOG/SYSDBA) must have
444
  //     both minSecLevel and maxSecLevel satisfying the role's floor constraints:
445
  //       SYSSEC/SYSAUDIT/SYSAUDIT_LOG: min floor=4, max floor=4
446
  //       SYSDBA: min floor=0, max floor=3
447
  // (B) Any user directly holding PRIV_SECURITY_POLICY_ALTER (not via role) must have maxSecLevel=4.
448
  //     (No constraint on minSecLevel for this check.)
449
  // On the first failing user found, immediately abort and return its name in the error message.
450
  {
451
    SSdb *pSdb = pMnode->pSdb;
×
452

453
    while ((pScanIter = sdbFetch(pSdb, SDB_USER, pScanIter, (void **)&pScanUser)) != NULL) {
×
454
      if (pScanUser->superUser) {
×
455
        sdbRelease(pSdb, pScanUser);
×
456
        pScanUser = NULL;
×
457
        continue;
×
458
      }
459
      int8_t floorMaxLevel = mndGetUserRoleFloorMaxLevel(pScanUser->roles);
×
460
      int8_t floorMinLevel = mndGetUserRoleFloorMinLevel(pScanUser->roles);
×
461
      bool   hasDirectPriv = PRIV_HAS(&pScanUser->sysPrivs, PRIV_SECURITY_POLICY_ALTER);
×
462
      // Skip users with neither a system role nor direct PRIV_SECURITY_POLICY_ALTER
463
      if (floorMaxLevel == 0 && floorMinLevel == 0 && !hasDirectPriv) {
×
464
        sdbRelease(pSdb, pScanUser);
×
465
        pScanUser = NULL;
×
466
        continue;
×
467
      }
468
      char   reason[256] = {0};
×
469
      int8_t hintMin = 0, hintMax = 0;
×
470
      if (pScanUser->maxSecLevel < floorMaxLevel) {
×
471
        snprintf(reason, sizeof(reason), "maxSecLevel(%d) < required maxFloor(%d) (role constraint)",
×
472
                 (int32_t)pScanUser->maxSecLevel, (int32_t)floorMaxLevel);
×
473
        hintMin = floorMinLevel;
×
474
        hintMax = floorMaxLevel;
×
475
      } else if (pScanUser->minSecLevel < floorMinLevel) {
×
476
        snprintf(reason, sizeof(reason), "minSecLevel(%d) < required minFloor(%d) (role constraint)",
×
477
                 (int32_t)pScanUser->minSecLevel, (int32_t)floorMinLevel);
×
478
        hintMin = floorMinLevel;
×
479
        hintMax = floorMaxLevel;
×
480
      } else if (hasDirectPriv && pScanUser->maxSecLevel < TSDB_MAX_SECURITY_LEVEL) {
×
481
        // (B): direct PRIV_SECURITY_POLICY_ALTER holder must have maxSecLevel=4
482
        snprintf(reason, sizeof(reason),
×
483
                 "maxSecLevel(%d) < %d (direct PRIV_SECURITY_POLICY_ALTER holder must have maxSecLevel=%d)",
484
                 (int32_t)pScanUser->maxSecLevel, (int32_t)TSDB_MAX_SECURITY_LEVEL, (int32_t)TSDB_MAX_SECURITY_LEVEL);
×
485
        hintMin = pScanUser->minSecLevel;  // min is already acceptable; keep it
×
486
        hintMax = TSDB_MAX_SECURITY_LEVEL;
×
487
      }
488
      if (reason[0] != '\0') {
×
489
        mError("MAC preflight: user '%s' %s", pScanUser->user, reason);
×
490
        char detail[512];
×
491
        snprintf(detail, sizeof(detail),
×
492
                 "Cannot enable MAC: user '%s' %s. "
493
                 "Please ALTER USER %s SECURITY_LEVEL %d,%d to satisfy constraints first.",
494
                 pScanUser->user, reason, pScanUser->user, (int32_t)hintMin, (int32_t)hintMax);
×
495
        sdbRelease(pSdb, pScanUser);
×
496
        pScanUser = NULL;
×
497
        sdbCancelFetch(pSdb, pScanIter);
×
498
        pScanIter = NULL;
×
499

500
        int32_t detailLen = strlen(detail) + 1;
×
501
        void   *pRsp = rpcMallocCont(detailLen);
×
502
        if (pRsp != NULL) {
×
503
          memcpy(pRsp, detail, detailLen);
×
504
          pReq->info.rspLen = detailLen;
×
505
          pReq->info.rsp = pRsp;
×
506
        }
507
        mndReleaseSecPolicy(pMnode, pObj);
×
508
        code = TSDB_CODE_MAC_PRECHECK_FAILED;
×
509
        goto _exit;
×
510
      }
511
      sdbRelease(pSdb, pScanUser);
×
512
      pScanUser = NULL;
×
513
    }
514
    pScanIter = NULL;
×
515
  }
516

517
  mInfo("activating cluster MAC by %s", RPC_MSG_USER(pReq));
×
518
  (void)memcpy(&obj, pObj, sizeof(SSecurityPolicyObj));
×
519
  obj.status = MAC_MODE_MANDATORY;
×
520
  obj.activateTime = taosGetTimestampMs();
×
521
  obj.updateTime = obj.activateTime;
×
522
  tstrncpy(obj.activator, RPC_MSG_USER(pReq), sizeof(obj.activator));
×
523
  mndReleaseSecPolicy(pMnode, pObj);
×
524
  pObj = NULL;
×
525

526
  pTrans = mndTransCreate(pMnode, TRN_POLICY_ROLLBACK, TRN_CONFLICT_ROLE, pReq, "activate-mac");
×
527
  if (pTrans == NULL) {
×
528
    TAOS_CHECK_EXIT(terrno);
×
529
  }
530

531
  SSdbRaw *pCommitRaw = mndSecPolicyActionEncode(&obj);
×
532
  if (pCommitRaw == NULL || mndTransAppendCommitlog(pTrans, pCommitRaw) != 0) {
×
533
    if (pCommitRaw) sdbFreeRaw(pCommitRaw);
×
534
    TAOS_CHECK_EXIT(terrno);
×
535
  }
536
  TAOS_CHECK_EXIT(sdbSetRawStatus(pCommitRaw, SDB_STATUS_READY));
×
537

538
  if ((code = mndTransPrepare(pMnode, pTrans)) != 0) {
×
539
    TAOS_CHECK_EXIT(code);
×
540
  }
541

542
_exit:
×
543
  if (pScanIter) sdbCancelFetch(pMnode->pSdb, pScanIter);
×
544
  if (pScanUser) sdbRelease(pMnode->pSdb, pScanUser);
×
545
  mndTransDrop(pTrans);
×
546
  if (code < 0 && code != TSDB_CODE_ACTION_IN_PROGRESS) {
×
547
    mError("%s failed at line %d since %s", __func__, lino, tstrerror(code));
×
548
  }
549
  TAOS_RETURN(code);
×
550
}
551
// #endif
552

553
// ---- SoD enforcement ----
554

555
void mndSodTransStop(SMnode *pMnode, void *param, int32_t paramLen) {
×
556
#ifdef TD_ENTERPRISE
557
  mInfo("SoD trans stop, set SoD phase to %d", TSDB_SOD_PHASE_STABLE);
×
558
  mndSetSoDPhase(pMnode, TSDB_SOD_PHASE_STABLE);
×
559
#endif
560
}
×
561

562
void mndSodGrantRoleStop(SMnode *pMnode, void *param, int32_t paramLen) {
×
563
#ifdef TD_ENTERPRISE
564
  if (mndGetSoDPhase(pMnode) != TSDB_SOD_PHASE_INITIAL) {
×
565
    return;
×
566
  }
567

568
  if (mndCheckManagementRoleStatus(pMnode, NULL, 0) == 0) {
×
569
    mInfo("SoD role check completed, all mandatory roles satisfied, trigger enforce SoD");
×
570
    (void)mndProcessEnforceSodImpl(pMnode);
×
571
  }
572
#endif
573
}
574

575
#ifdef TD_ENTERPRISE
576
static int32_t mndProcessEnforceSodImpl(SMnode *pMnode) {
×
577
  int32_t code = 0, lino = 0;
×
578
  int32_t contLen = 0;
×
579
  void   *pCont = NULL;
×
580

581
  SMCfgClusterReq cfgReq = {0};
×
582
  tsnprintf(cfgReq.config, sizeof(cfgReq.config), "SoD");
×
583
  tsnprintf(cfgReq.value, sizeof(cfgReq.value), "mandatory");
×
584
  contLen = tSerializeSMCfgClusterReq(NULL, 0, &cfgReq);
×
585
  TAOS_CHECK_EXIT(contLen);
×
586
  if (!(pCont = rpcMallocCont(contLen))) {
×
587
    TAOS_CHECK_EXIT(TSDB_CODE_OUT_OF_MEMORY);
×
588
  }
589

590
  if ((code = tSerializeSMCfgClusterReq(pCont, contLen, &cfgReq)) != contLen) {
×
591
    rpcFreeCont(pCont);
×
592
    TAOS_CHECK_EXIT(code);
×
593
  }
594

595
  SRpcMsg rpcMsg = {.pCont = pCont,
×
596
                    .contLen = contLen,
597
                    .msgType = TDMT_MND_CONFIG_CLUSTER,
598
                    .info.ahandle = 0,
599
                    .info.notFreeAhandle = 1};
600
  SEpSet  epSet = {0};
×
601
  mndGetMnodeEpSet(pMnode, &epSet);
×
602
  TAOS_CHECK_EXIT(tmsgSendReq(&epSet, &rpcMsg));
×
603
_exit:
×
604
  if (code < 0) {
×
605
    mError("failed at line %d to enforce SoD since %s", lino, tstrerror(code));
×
606
  }
607
  TAOS_RETURN(code);
×
608
}
609

610
int32_t mndProcessEnforceSod(SMnode *pMnode) {
×
611
  int32_t code = 0, lino = 0;
×
612

613
  SSecurityPolicyObj *pObj = mndAcquireSecPolicy(pMnode, TSDB_SECURITY_POLICY_SOD);
×
614
  if (pObj == NULL) {
×
615
    TAOS_CHECK_EXIT(terrno);
×
616
  }
617

618
  if (pObj->status == SOD_MODE_MANDATORY) {
×
619
    mInfo("cluster is already in SoD mandatory mode");
×
620
    mndReleaseSecPolicy(pMnode, pObj);
×
621
    TAOS_RETURN(0);
×
622
  }
623

624
  mndSetSoDPhase(pMnode, TSDB_SOD_PHASE_INITIAL);
×
625
  mInfo("start to enforce SoD from initial phase, secpolicy type:%d", pObj->type);
×
626
  if ((code = mndCheckManagementRoleStatus(pMnode, NULL, 0))) {
×
627
    mndReleaseSecPolicy(pMnode, pObj);
×
628
    TAOS_CHECK_EXIT(code);
×
629
  }
630
  mndReleaseSecPolicy(pMnode, pObj);
×
631

632
  TAOS_CHECK_EXIT(mndProcessEnforceSodImpl(pMnode));
×
633
  code = TSDB_CODE_ACTION_IN_PROGRESS;
×
634

635
_exit:
×
636
  if (code < 0 && code != TSDB_CODE_ACTION_IN_PROGRESS) {
×
637
    mError("failed to enforce SoD at line %d since %s", lino, tstrerror(code));
×
638
  }
639
  TAOS_RETURN(code);
×
640
}
641
#endif
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc