credential

package
v0.12.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 12, 2026 License: MIT Imports: 15 Imported by: 0

Documentation

Index

Constants

View Source
const (
	// DefaultCacheCapacity 是默认缓存容量上限。
	DefaultCacheCapacity = 64
)

Variables

View Source
var (
	// ErrCredentialNotFound 表示凭据项在指定的凭据存储中未找到。
	ErrCredentialNotFound = errors.New("credential not found")

	// ErrCredentialStoreLocked 表示目标凭据存储处于锁定状态,需要用户解锁。
	ErrCredentialStoreLocked = errors.New("credential store is locked")

	// ErrCredentialStoreUnavailable 表示凭据存储当前不可达或未就绪。
	ErrCredentialStoreUnavailable = errors.New("credential store is unavailable")

	// ErrCredentialAccessDenied 表示当前用户或进程无权访问该凭据存储。
	ErrCredentialAccessDenied = errors.New("credential access denied")

	// ErrCredentialStoreReadOnly 表示该凭据源为只读,不支持写入或删除操作。
	ErrCredentialStoreReadOnly = errors.New("credential store is read-only")

	// ErrInteractionRequired 表示非交互场景下需要用户交互输入凭据,执行失败关闭。
	ErrInteractionRequired = errors.New("interaction required")

	// ErrConfigConflict 表示并发写入或版本 CAS 校验发生冲突。
	ErrConfigConflict = errors.New("configuration conflict")

	// ErrInvalidRef 表示凭据引用格式非法或 storeID/itemID 不完整。
	ErrInvalidRef = errors.New("invalid credential reference")

	// ErrStoreNotFound 表示指定的 StoreID 未在 Registry 中注册。
	ErrStoreNotFound = errors.New("credential store not found")

	// ErrStoreAlreadyRegistered 表示指定的 StoreID 已存在注册项。
	ErrStoreAlreadyRegistered = errors.New("credential store already registered")

	// ErrJournalCorrupted 表示凭据恢复日志格式损坏或校验失败。
	ErrJournalCorrupted = errors.New("credential recovery journal corrupted")

	// ErrSchemaValidation 表示配置 Schema v2 校验失败。
	ErrSchemaValidation = errors.New("schema validation failed")

	// ErrUnsupportedSchemaVersion 表示不支持的配置文件 schema_version。
	ErrUnsupportedSchemaVersion = errors.New("unsupported schema version")
)
View Source
var ErrLockContended = errors.New("transaction lock contended")

ErrLockContended 表示事务锁已被其他并发进程持有。

Functions

func GenerateItemID

func GenerateItemID() string

GenerateItemID 生成全局唯一、不可变的 32 字符十六进制随机 ItemID。

func GenerateJournalID

func GenerateJournalID() string

GenerateJournalID 生成全局唯一的随机日志 ID。

func InteractionDisabled

func InteractionDisabled(ctx context.Context) bool

InteractionDisabled reports the request's non-interactive access policy.

func WithoutInteraction

func WithoutInteraction(ctx context.Context) context.Context

WithoutInteraction marks a request as forbidden from opening any credential or unlock prompt. Backends must reject requests they cannot serve silently.

Types

type Cache

type Cache struct {
	// contains filtered or unexported fields
}

Cache 提供有界的内存凭据缓存,采用 LRU 淘汰与惰性过期(lazy expiration)机制。 本缓存不启动任何后台清理 Goroutine,确保零资源泄漏。

func NewCache

func NewCache(opts CacheOptions) *Cache

NewCache 创建一个新的有界凭据缓存。 capacity <= 0 或 DefaultTTL <= 0 明确表示完全禁用缓存。

func (*Cache) ActiveFetchesCount

func (c *Cache) ActiveFetchesCount() int

ActiveFetchesCount 返回当前正在跟踪的在途读取令牌数量(用于有界性测试)。

func (*Cache) CancelFetch

func (c *Cache) CancelFetch(ref Ref, ft FetchToken)

CancelFetch 在读取底层存储失败时,主动释放在途读取令牌。

func (*Cache) Clear

func (c *Cache) Clear()

Clear 清空全部缓存条目,递增全局清空代数使所有在途读取作废,回收全部活跃元数据,并对所有机密字节执行 Zero 清零。

func (*Cache) Get

func (c *Cache) Get(ref Ref) (Secret, bool)

Get 从缓存中获取凭据副本。 若未命中或已过期,返回 (Secret{}, false)。 若命中且未过期,将该项提升至 LRU 头部并返回其深拷贝副本。

func (*Cache) Invalidate

func (c *Cache) Invalidate(ref Ref)

Invalidate 使指定引用的缓存条目立即失效,清除在途读取令牌,并清零内存。

func (*Cache) Len

func (c *Cache) Len() int

Len 返回当前有效缓存条目数量。

func (*Cache) Put

func (c *Cache) Put(ref Ref, secret Secret)

Put 将凭据写入缓存。

func (*Cache) PutIfMatch

func (c *Cache) PutIfMatch(ref Ref, secret Secret, ft FetchToken)

PutIfMatch 只有当缓存条目的令牌完全匹配且未发生 Clear/Invalidate 时才写入缓存。

func (*Cache) StartFetch

func (c *Cache) StartFetch(ref Ref) FetchToken

StartFetch 获取指定引用的当前在途读取令牌。

type CacheOptions

type CacheOptions struct {
	Capacity   int
	DefaultTTL time.Duration
	NowFunc    func() time.Time
}

CacheOptions 包含初始化凭据缓存的可选参数。

type CachedSource

type CachedSource struct {
	// contains filtered or unexported fields
}

CachedSource 将 Source 装饰为带本地有界缓存的只读凭据源。

func NewCachedSource

func NewCachedSource(source Source, cache *Cache) *CachedSource

NewCachedSource 创建带缓存的只读凭据源。

func (*CachedSource) Get

func (cs *CachedSource) Get(ctx context.Context, ref Ref) (Secret, error)

Get 优先从缓存获取凭据;未命中时在锁外调用底层存储,使用令牌栅栏防并发回填。

type CachedStore

type CachedStore struct {
	CachedSource
	// contains filtered or unexported fields
}

CachedStore 将 Store 装饰为带本地有界缓存的可读写凭据存储。

func NewCachedStore

func NewCachedStore(store Store, cache *Cache) *CachedStore

NewCachedStore 创建带缓存的可读写凭据存储。

func (*CachedStore) Delete

func (cs *CachedStore) Delete(ctx context.Context, ref Ref) error

Delete 从底层存储删除凭据,并在退出时严格保证缓存失效。

func (*CachedStore) Put

func (cs *CachedStore) Put(ctx context.Context, ref Ref, secret Secret) error

Put 向底层存储写入凭据。 无论成功还是遇到错误,在退出时都严格执行 Invalidate,防止在途并发读取将过时数据留在缓存中; 同时不在 Put 内部提前回填缓存,确保随后的写后读回校验(Get)能穿透到底层后端。

type CleanupError

type CleanupError struct {
	OldRef Ref
	Err    error
}

CleanupError 表示新凭据与新配置已经权威且持久化生效,但在删除旧凭据时失败。 该错误绝不能导致配置回滚,未清理的旧凭据将记录在 journal 中留待稍后后台 GC。

func (*CleanupError) Error

func (e *CleanupError) Error() string

func (*CleanupError) Unwrap

func (e *CleanupError) Unwrap() error

type ConfigUpdater

type ConfigUpdater interface {
	// ApplyCredentialRefAtVersion 使用版本号对配置中的凭据引用进行 CAS 原子提交。
	// 若 newRef 为 nil,表示解除该字段凭据引用(用于删除)。
	// 若版本发生冲突,必须返回 ErrConfigConflict。
	// 若配置已替换但父目录未完成落盘同步,必须返回 DurabilityError 且 outcome.Applied = true, outcome.Durable = false。
	ApplyCredentialRefAtVersion(ctx context.Context, target Target, expectedVersion string, newRef *Ref) (outcome MutationOutcome, newVersion string, err error)

	// CheckRefUnreferenced 检查指定 ref 在当前最新已生效配置中是否已完全解绑(全局无任何引用)。
	CheckRefUnreferenced(ctx context.Context, ref Ref) (bool, error)

	// ConfirmRefDurable 从底层权威持久化存储重新检查指定 target 的凭据引用是否已持久化生效(Durable)。
	ConfirmRefDurable(ctx context.Context, target Target, ref *Ref) (bool, error)
}

ConfigUpdater 定义凭据服务所需的配置版本化 CAS 提交与引用检测契约。 该接口由配置仓库层(如 pkg/config.Repository 适配器)实现,避免包间循环引用。

type CredentialRef

type CredentialRef = Ref

CredentialRef 是 Ref 的别名,与设计文档中的命名保持一致。

type EntryLock

type EntryLock struct {
	// contains filtered or unexported fields
}

EntryLock 包装单个事务条目的跨平台排他文件锁。

func (*EntryLock) Close

func (l *EntryLock) Close() error

Close 释放并关闭文件锁。

type FetchToken

type FetchToken struct {
	// contains filtered or unexported fields
}

FetchToken 标识单次在途读取的栅栏令牌,防止过时数据回填。

type JournalEntry

type JournalEntry struct {
	ID                       string        `json:"id"`
	Op                       OperationType `json:"op"`
	Stage                    Stage         `json:"stage"`
	OldRef                   *Ref          `json:"oldRef,omitempty"`
	NewRef                   *Ref          `json:"newRef,omitempty"`
	BaseVersion              string        `json:"baseVersion,omitempty"`
	TargetNode               string        `json:"targetNode,omitempty"`
	TargetIdentity           string        `json:"targetIdentity,omitempty"`
	TargetKind               Kind          `json:"targetKind,omitempty"`
	KeyPath                  string        `json:"keyPath,omitempty"`
	KeyFingerprint           string        `json:"keyFingerprint,omitempty"`
	AuthType                 string        `json:"authType,omitempty"`
	ClearKeyPath             bool          `json:"clearKeyPath,omitempty"`
	ClearLegacyLoginPassword bool          `json:"clearLegacyLoginPassword,omitempty"`
	ClearLegacyPassphrase    bool          `json:"clearLegacyPassphrase,omitempty"`
	CreatedAt                time.Time     `json:"createdAt"`
	UpdatedAt                time.Time     `json:"updatedAt"`
}

JournalEntry 记录凭据操作的恢复状态。 【安全红线】本结构严禁包含任何机密明文或可逆密文字节,仅记录非敏感的引用元数据与阶段。

func (*JournalEntry) Target

func (j *JournalEntry) Target() Target

Target 返回与该日志条目关联的目标标识。

func (*JournalEntry) Validate

func (j *JournalEntry) Validate() error

Validate 校验 JournalEntry 基础合法性。

type JournalStore

type JournalStore struct {
	// contains filtered or unexported fields
}

JournalStore 管理非敏感凭据恢复日志文件的持久化与扫描。

func NewJournalStore

func NewJournalStore(dir string) (*JournalStore, error)

NewJournalStore 创建一个日志存储管理器,确保目录存在且权限为 0700。

func (*JournalStore) AcquireEntryLock

func (s *JournalStore) AcquireEntryLock(id string) (*EntryLock, error)

AcquireEntryLock 获取指定日志条目的排他文件锁。若已被持有,返回 ErrLockContended。

func (*JournalStore) Get

func (s *JournalStore) Get(id string) (*JournalEntry, error)

Get 获取指定 ID 的日志项。

func (*JournalStore) ListPending

func (s *JournalStore) ListPending() ([]JournalEntry, error)

ListPending 扫描并返回所有未完成的日志条目(按创建时间升序排列)。

func (*JournalStore) MarkAppliedUncertain

func (s *JournalStore) MarkAppliedUncertain(id string) error

MarkAppliedUncertain 将日志状态推进至 StageAppliedUncertain(配置已应用但持久化不确定)。

func (*JournalStore) MarkCleanup

func (s *JournalStore) MarkCleanup(id string) error

MarkCleanup 将日志状态推进至 StageCleanup(清理失败,标记待 GC)。

func (*JournalStore) MarkCommitted

func (s *JournalStore) MarkCommitted(id string) error

MarkCommitted 将日志状态推进至 StageCommitted(配置已持久化提交)。

func (*JournalStore) RecordIntent

func (s *JournalStore) RecordIntent(entry *JournalEntry) error

RecordIntent 记录操作意图。如果 entry.ID 为空,将自动生成唯一 ID。

func (*JournalStore) Remove

func (s *JournalStore) Remove(id string) error

Remove 成功完成清理或补偿后删除日志条目,并同步目录以保证 durability。

func (*JournalStore) TryLockEntry

func (s *JournalStore) TryLockEntry(id string) (*EntryLock, error)

TryLockEntry 尝试非阻塞获取日志条目排他锁,用于判定是否为活跃进程持有的在途事务。

type Kind

type Kind string

Kind 标识凭据的种类。

const (
	// KindLoginPassword 标识 SSH 登录密码。
	KindLoginPassword Kind = "login_password"
	// KindPassphrase 标识 SSH 私钥密码短语。
	KindPassphrase Kind = "passphrase"
	// KindPrivilegePassword 标识提权密码(sudo 或 su)。
	KindPrivilegePassword Kind = "privilege_password"
)

func (Kind) Validate

func (k Kind) Validate() error

Validate 验证 Kind 是否合法。

type MutationOutcome

type MutationOutcome struct {
	Applied bool
	Durable bool
}

MutationOutcome 描述配置提交的结果状态。

type NoneStore

type NoneStore struct{}

NoneStore 实现固定 fail-closed 语义的凭据后端。 任何读取均报告 ErrCredentialNotFound,任何写入均被拒绝为 ErrCredentialStoreReadOnly。

func NewNoneStore

func NewNoneStore() *NoneStore

NewNoneStore 创建一个 NoneStore 实例。

func (*NoneStore) Delete

func (n *NoneStore) Delete(_ context.Context, _ Ref) error

Delete 固定返回 ErrCredentialStoreReadOnly,禁止执行删除。

func (*NoneStore) Get

func (n *NoneStore) Get(_ context.Context, _ Ref) (Secret, error)

Get 固定返回 ErrCredentialNotFound,确保调用方在无引用或缺失凭据时安全失败关闭。

func (*NoneStore) Put

func (n *NoneStore) Put(_ context.Context, _ Ref, _ Secret) error

Put 固定返回 ErrCredentialStoreReadOnly,禁止向 none 后端持久化凭据。

type OperationType

type OperationType string

OperationType 定义凭据事务操作类型。

const (
	// OpRotate 表示凭据轮换操作(旧引用切换为新引用)。
	OpRotate OperationType = "rotate"
	// OpDelete 表示凭据删除操作(删除现有引用及后端机密)。
	OpDelete OperationType = "delete"
	// OpCreate 表示新增凭据操作。
	OpCreate OperationType = "create"
	// OpAssetDelete tracks cleanup after an atomic inventory deletion. Recovery
	// checks global reference reachability because the target entity is gone.
	OpAssetDelete OperationType = "asset_delete"
)

type RecoveryAction

type RecoveryAction string

RecoveryAction 描述崩溃恢复时采取的具体处置动作。

const (
	RecoveryActionCompensatedNewRef RecoveryAction = "compensated_new_ref"
	RecoveryActionCommittedCleaned  RecoveryAction = "committed_cleaned_old_ref"
	RecoveryActionScheduledForGC    RecoveryAction = "scheduled_for_gc"
	RecoveryActionRemovedNoOp       RecoveryAction = "removed_noop"
	RecoveryActionSkippedActive     RecoveryAction = "skipped_active"
)

type RecoveryResult

type RecoveryResult struct {
	EntryID string
	Op      OperationType
	Stage   Stage
	Action  RecoveryAction
	Err     error
}

RecoveryResult 记录单条恢复操作的结果详情。

type Ref

type Ref struct {
	StoreID string `yaml:"store_id" json:"storeID"`
	ItemID  string `yaml:"item_id" json:"itemID"`
}

Ref 定义凭据在外部凭据存储中的引用。 引用由 StoreID 和不可变的 ItemID 组成。

func (*Ref) Clone

func (r *Ref) Clone() *Ref

Clone 返回引用的深拷贝指针。如果接收者为 nil 则返回 nil。

func (Ref) IsComplete

func (r Ref) IsComplete() bool

IsComplete 检查引用是否完整(StoreID 和 ItemID 均非空)。

func (Ref) IsEmpty

func (r Ref) IsEmpty() bool

IsEmpty 检查引用是否为空(StoreID 和 ItemID 均为空)。

func (Ref) String

func (r Ref) String() string

String 返回引用的可读非敏感标识(storeID/itemID)。空引用返回空字符串。

func (Ref) Validate

func (r Ref) Validate() error

Validate 验证引用的完整性和合法性。 空引用被视作合法(表示未配置引用)。 引用若非空,必须保证 StoreID 与 ItemID 均非空,且不包含危险路径或注入字符。

type Registry

type Registry struct {
	// contains filtered or unexported fields
}

Registry 管理 StoreID 到凭据源(Source / Store)的映射。 Registry 的读写锁仅用于保护内部映射表的并发安全,严禁跨外部 I/O 持有锁。

func NewRegistry

func NewRegistry() *Registry

NewRegistry 创建一个空的凭据存储注册表。

func (*Registry) Get

func (r *Registry) Get(storeID string) (Source, error)

Get 根据 StoreID 获取只读凭据源 Source。 注意:锁在检索到对象后立即释放,调用方使用返回的 Source 进行外部 I/O 时不持有 Registry 锁。

func (*Registry) GetStore

func (r *Registry) GetStore(storeID string) (Store, error)

GetStore 根据 StoreID 获取可读写的凭据存储 Store。 如果目标仅实现了只读 Source,返回包装了 ErrCredentialStoreReadOnly 的错误。

func (*Registry) Has

func (r *Registry) Has(storeID string) bool

Has 检查指定的 StoreID 是否已注册。

func (*Registry) List

func (r *Registry) List() []string

List 返回已注册的所有 StoreID 列表,结果按字母字典序排序。

func (*Registry) Register

func (r *Registry) Register(storeID string, source Source) error

Register 注册一个凭据源(Source 或 Store)。如果已存在同名 StoreID,返回 ErrStoreAlreadyRegistered。

func (*Registry) Resolve

func (r *Registry) Resolve(ctx context.Context, ref Ref) (Secret, error)

Resolve 根据 Ref 中的 StoreID 查找对应的凭据源并读取凭据

func (*Registry) Unregister

func (r *Registry) Unregister(storeID string)

Unregister 注销一个已注册的凭据源,主要用于测试或动态重新加载。

type Secret

type Secret struct {
	Value     []byte     `json:"-"`
	ExpiresAt *time.Time `json:"expiresAt,omitempty"`
}

Secret 包含从凭据存储解析的机密字节和可选的过期时间。

func NewSecret

func NewSecret(val []byte) Secret

NewSecret 创建一个没有明确过期时间的机密对象。

func NewSecretWithExpiry

func NewSecretWithExpiry(val []byte, expiresAt time.Time) Secret

NewSecretWithExpiry 创建一个带有过期时间的机密对象。

func (Secret) Clone

func (s Secret) Clone() Secret

Clone 返回机密对象的独立深拷贝,调用方可以安全修改或清零而不影响原对象。

func (Secret) IsExpired

func (s Secret) IsExpired(now time.Time) bool

IsExpired 判断该机密在指定时间点是否已过期。

func (*Secret) Zero

func (s *Secret) Zero()

Zero 尽力将底层机密字节清零,并将引用置空,防止明文驻留堆内存。

type Service

type Service struct {
	// contains filtered or unexported fields
}

Service 协调凭据存储与配置引用的双存储原子写入、轮换、删除与崩溃恢复。

func NewService

func NewService(registry *Registry, journal *JournalStore, config ConfigUpdater, cache *Cache) (*Service, error)

NewService 创建双存储写入协调服务。

func (*Service) Create

func (s *Service) Create(
	ctx context.Context,
	target Target,
	expectedVersion string,
	storeID string,
	secret Secret,
) (*Ref, string, error)

Create 执行新增凭据事务(oldRef 为空)。

func (*Service) Delete

func (s *Service) Delete(
	ctx context.Context,
	target Target,
	expectedVersion string,
	refToDelete Ref,
) (string, error)

Delete 执行删除凭据事务: 1. 记录 journal intent(OpDelete, StageIntent); 2. Repository 先删除配置引用并 durable; 3. 确认没有任何引用; 4. 删除 Store 项; 5. 删除失败时保留 cleanup journal,不回滚配置。

func (*Service) DeleteAssets

func (s *Service) DeleteAssets(ctx context.Context, refs []Ref, commit func(context.Context) (MutationOutcome, error)) (outcome MutationOutcome, retErr error)

DeleteAssets journals every candidate ref before a single inventory commit. The callback must preserve its exact entity version preconditions. No backend I/O runs inside that callback or the repository's configuration locks.

func (*Service) GC

func (s *Service) GC(ctx context.Context) ([]RecoveryResult, error)

GC 是 Recover 的别名,用于周期性触发或 CLI gc 命令执行未清理孤儿凭据回收。

func (*Service) Recover

func (s *Service) Recover(ctx context.Context) ([]RecoveryResult, error)

Recover 扫描未决的恢复日志条目并执行自动恢复或补偿: - StageIntent: 检查 NewRef 是否在配置中生效;若未生效,清理孤儿 NewRef 并删除日志;若已生效,推进至 StageCommitted; - StageAppliedUncertain: 重新从底层权威存储确认配置是否 Durable,若已持久化则推进至 StageCommitted,否则保持新旧凭据; - StageCommitted / StageCleanup: 确认 OldRef 已无引用后尝试删除后端条目,成功则删除日志,失败则保持/推进 StageCleanup。

func (*Service) Rotate

func (s *Service) Rotate(
	ctx context.Context,
	target Target,
	expectedVersion string,
	oldRef *Ref,
	newStoreID string,
	secret Secret,
) (*Ref, string, error)

Rotate 执行凭据轮换事务: 1. 创建 journal intent,记录旧 ref、新 ref、配置前置版本和阶段; 2. Put(newRef, secret); 3. Get(newRef) 读回并常量时间比较; 4. Repository 使用配置版本 CAS 切换到新 ref; 5. 配置 durable 后,将 journal 标记为 committed; 6. 确认旧 ref 当前无引用后删除; 7. 清除 journal。

type Source

type Source interface {
	// Get 从底层存储中根据引用检索机密。
	Get(ctx context.Context, ref Ref) (Secret, error)
}

Source 定义只读凭据源契约。

type Stage

type Stage string

Stage 定义凭据事务日志所处的阶段。

const (
	// StageIntent 表示事务意图已记录,新凭据已写入但配置尚未原子提交。
	StageIntent Stage = "intent"
	// StageAppliedUncertain 表示配置变更已在快照应用,但父目录落盘同步不确定(Durability 失败)。
	// 在此阶段,新旧凭据均保留,绝不能删除旧凭据,也绝不能补偿删除新凭据。
	StageAppliedUncertain Stage = "applied_uncertain"
	// StageCommitted 表示配置变更已持久化生效,旧凭据待清理。
	StageCommitted Stage = "committed"
	// StageCleanup 表示旧凭据清理失败,留待稍后后台 GC。
	StageCleanup Stage = "cleanup"
)

type Store

type Store interface {
	Source
	// Put 向底层存储写入或更新凭据项。
	Put(ctx context.Context, ref Ref, secret Secret) error
	// Delete 从底层存储删除指定的凭据项。
	Delete(ctx context.Context, ref Ref) error
}

Store 定义可读写的凭据存储契约。

type Target

type Target struct {
	NodeID     string `json:"nodeID,omitempty"`
	IdentityID string `json:"identityID,omitempty"`
	Kind       Kind   `json:"kind"`
	// KeyPath is the non-secret private-key path to commit atomically with a
	// passphrase reference. It is ignored for other credential kinds.
	KeyPath        string `json:"keyPath,omitempty"`
	KeyFingerprint string `json:"keyFingerprint,omitempty"`
	// AuthType and ClearKeyPath let a deletion transaction switch
	// authentication metadata atomically with removing an obsolete reference.
	AuthType     string `json:"authType,omitempty"`
	ClearKeyPath bool   `json:"clearKeyPath,omitempty"`
	// ClearLegacyLoginPassword and ClearLegacyPassphrase clear only the legacy
	// plaintext fields while retaining any reference for a later cleanup
	// transaction.
	ClearLegacyLoginPassword bool `json:"clearLegacyLoginPassword,omitempty"`
	ClearLegacyPassphrase    bool `json:"clearLegacyPassphrase,omitempty"`
}

Target 标识凭据写入或引用的目标实体(Node 或 Identity,以及凭据种类)。

func (Target) TargetIdentifier

func (t Target) TargetIdentifier() string

TargetIdentifier 返回目标的非敏感字符串标识。

func (Target) Validate

func (t Target) Validate() error

Validate 校验 Target 的完整性与合法性。

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL