feat: let administrators view and copy encrypted integration credentials
This commit is contained in:
85
internal/storage/mysql/console_credential_tokens.go
Normal file
85
internal/storage/mysql/console_credential_tokens.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user