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 }