feat: let administrators view and copy encrypted integration credentials

This commit is contained in:
2026-09-27 13:48:26 +08:00
parent 5555a7b342
commit d54bdd9847
38 changed files with 905 additions and 71 deletions

View File

@@ -0,0 +1,85 @@
package mysql
import (
"context"
"database/sql"
"git.yuntuo.hk/k2safe/orbit/internal/consoleauth"
"git.yuntuo.hk/k2safe/orbit/internal/fault"
core "git.yuntuo.hk/k2safe/orbit/model"
"git.yuntuo.hk/k2safe/orbit/platform/auth"
)
var _ consoleauth.CredentialTokens = (*ConsoleApplicationStore)(nil)
// AccessCredentialToken 认证、版本核对、密文读取/补存和审计在同一事务完成。
// 导入相同当前秘密可重复调用,不变更认证 revision、到期时间或旧代过渡截止。
func (s *ConsoleApplicationStore) AccessCredentialToken(ctx context.Context, session auth.Token, namespace, credential core.ID, in consoleauth.CredentialTokenInput, importCurrent bool) (consoleauth.CredentialToken, error) {
var out consoleauth.CredentialToken
if namespace.Validate() != nil || credential.Validate() != nil || in.ExpectedRevision == 0 || (!importCurrent && in.Token != "") {
return out, fault.Invalid("invalid credential token request")
}
err := WithTx(ctx, s.db, func(tx *sql.Tx) error {
actor, err := s.admin(ctx, tx, session)
if err != nil {
return err
}
if s.vault == nil {
return fault.New("UNAVAILABLE", "Token 保管密钥尚未配置,请联系部署管理员")
}
var revision uint64
var revoked sql.NullTime
var digest core.Digest
var keyID sql.NullString
var ciphertext []byte
err = tx.QueryRowContext(ctx, `SELECT revision,revoked_at,secret_digest,secret_key_id,secret_ciphertext FROM orbit_credentials WHERE namespace_id=? AND id=? FOR UPDATE`, namespace, credential).Scan(&revision, &revoked, &digest, &keyID, &ciphertext)
if err == sql.ErrNoRows {
return fault.NotFound
}
if err != nil {
return err
}
if revision != in.ExpectedRevision || revoked.Valid {
return fault.New("STATE_CONFLICT", "凭据已变更或撤销,请刷新后重试")
}
raw := in.Token
action := "console.credential.token.view"
if !importCurrent {
if !keyID.Valid {
return fault.New("STATE_CONFLICT", "此历史凭据尚未保管 Token,请先导入当前 Token")
}
raw, err = s.vault.Open(namespace, credential, keyID.String, ciphertext)
if err != nil {
return fault.New("UNAVAILABLE", "Token 解密失败,请检查保管密钥文件")
}
}
parsed, err := auth.Parse(raw)
if err != nil || parsed.NamespaceID != namespace || parsed.CredentialID != credential || subtleCompareDigest(parsed.Digest, digest) != 1 {
return fault.Invalid("Token 与当前凭据不匹配;不能导入过渡期旧 Token")
}
if importCurrent {
action = "console.credential.token.import"
id, encrypted, err := s.vault.Seal(namespace, credential, raw)
if err != nil {
return err
}
if _, err := tx.ExecContext(ctx, `UPDATE orbit_credentials SET secret_key_id=?,secret_ciphertext=?,updated_at=UTC_TIMESTAMP(6) WHERE namespace_id=? AND id=?`, id, encrypted, namespace, credential); err != nil {
return err
}
}
operation, err := core.NewID()
if err != nil {
return err
}
if err := appendAuditActor(ctx, tx, namespace, actor, operation, action, credential, "administrator access to current integration configuration", nil, map[string]any{"revision": revision}); err != nil {
return err
}
out = consoleauth.CredentialToken{NamespaceID: namespace, CredentialID: credential, Revision: revision, Token: raw}
return nil
})
// 包括提交结果未知:只有确认审计提交成功,才允许向调用方交付秘密。
if err != nil {
return consoleauth.CredentialToken{}, err
}
return out, nil
}