SecureValue API
Тип SecureValue используется для безопасного хранения чувствительных данных, предоставляя блокировку памяти, автоочистку и маскирование.
Потокобезопасность
Все методы SecureValue потокобезопасны и могут использоваться конкурентно из нескольких goroutine:
- Методы чтения (
String(),Bytes(),Length(),Masked()) используют блокировку чтения, поддерживая конкурентное чтение - Методы закрытия (
Close(),Release()) используют блокировку записи, обеспечивая безопасное обнуление - Проверка состояния (
IsClosed(),IsMemoryLocked()) использует атомарные операции
secret := env.GetSecure("API_KEY")
if secret != nil {
defer secret.Release()
// Безопасное конкурентное чтение
go func() { fmt.Println(secret.Masked()) }()
go func() { fmt.Println(secret.Length()) }()
}Внимание
Close() и Release() должны вызываться только один раз. Повторные вызовы безопасны, но неэффективны.
Создание
NewSecureValue
func NewSecureValue(value string) *SecureValueСоздаёт обёртку безопасного значения.
Параметры:
value- строковое значение для защиты
Возвращает:
*SecureValue- объект безопасного значения
Поведение:
- Использует пул объектов для уменьшения аллокаций
- Устанавливает GC-финализатор для автоочистки
- Если блокировка памяти включена, пытается заблокировать память (при неудаче молча игнорирует)
secret := env.NewSecureValue("my-secret-password")
defer secret.Release() // или Close()NewSecureValueStrict
func NewSecureValueStrict(value string) (*SecureValue, error)Создаёт безопасное значение; возвращает ошибку при неудаче блокировки памяти.
Параметры:
value- строковое значение для защиты
Возвращает:
*SecureValue- объект безопасного значенияerror- ошибка блокировки памяти (только в строгом режиме)
env.SetMemoryLockEnabled(true)
env.SetMemoryLockStrict(true)
secret, err := env.NewSecureValueStrict("my-secret")
if err != nil {
// Блокировка памяти не удалась
log.Printf("Warning: %v", err)
}
if secret != nil {
defer secret.Release()
}GetSecure (метод Loader)
func (l *Loader) GetSecure(key string) *SecureValueПолучает безопасное значение из загрузчика.
Параметры:
key- имя ключа
Возвращает:
*SecureValue- оборонительная копия безопасного значения; вызывающий отвечает за освобождение; возвращает nil, если ключ не существует или загрузчик закрыт
secret := loader.GetSecure("API_KEY")
if secret != nil {
defer secret.Release()
// Использование secret
}Оборонительная копия
GetSecure возвращает копию исходного значения, независимую от родительского Loader. Вызывающий отвечает за вызов Release() или Close() для освобождения.
Методы
String
func (sv *SecureValue) String() stringВозвращает маскированное представление, безопасное для логов и форматирования. Реализует интерфейс fmt.Stringer, предотвращая случайную утечку секрета через fmt.Printf, log.Println или обёртку ошибок.
Возвращает:
string- маскированное представление (например[SECURE:32 bytes]), для nil возвращает[NIL]
secret := env.GetSecure("PASSWORD")
if secret != nil {
log.Printf("Password: %s", secret) // Безопасно, выводится маскированное представление
// Эквивалентно log.Printf("Password: %s", secret.Masked())
}Внимание
String() возвращает маскированное представление, а не открытый текст. Для получения открытого текста используйте Reveal().
Reveal
func (sv *SecureValue) Reveal() stringВозвращает значение в открытом виде. Вызывающий отвечает за безопасную обработку возвращённой строки — избегайте логирования, сериализации или сохранения в постоянное хранилище. Используйте только когда необходимо фактическое значение для криптографических операций, вызовов API или аналогичной безопасной обработки.
Возвращает:
string- значение в открытом виде; для закрытого или nil возвращает пустую строку
secret := env.GetSecure("API_KEY")
if secret != nil {
defer secret.Release()
plaintext := secret.Reveal() // Получение открытого текста
// Использование plaintext для вызовов API и других безопасных операций
_ = plaintext
}Опасность
Reveal() возвращает строку в открытом виде. Строки Go неизменяемы и не могут быть обнулены вручную. Используйте только при необходимости и избегайте логирования или хранения возвращённого значения.
Bytes
func (sv *SecureValue) Bytes() []byteВозвращает копию значения в виде среза байтов. Вызывающий отвечает за обнуление через ClearBytes.
Возвращает:
[]byte- байтовая копия значения; для закрытого возвращает nil
secret := env.GetSecure("API_KEY")
if secret != nil {
data := secret.Bytes()
defer env.ClearBytes(data) // Обнуление после использования
// Использование data
}Length
func (sv *SecureValue) Length() intВозвращает длину значения, не раскрывая содержимое.
Возвращает:
int- длина значения; для закрытого возвращает 0
secret := env.GetSecure("API_KEY")
if secret != nil {
fmt.Printf("API Key length: %d\n", secret.Length())
}Masked
func (sv *SecureValue) Masked() stringВозвращает маскированное значение для вывода в лог.
Возвращает:
string- маскированное представление
Форматы вывода:
- Закрыто:
[CLOSED] - Пустое значение:
[SECURE:0 bytes] - Нормальное:
[SECURE:N bytes]или[SECURE:N bytes locked]или[SECURE:N bytes lock-failed]или[SECURE:N bytes unlocked]
secret := env.GetSecure("API_KEY")
if secret != nil {
log.Printf("API Key: %s", secret.Masked())
// Вывод: API Key: [SECURE:32 bytes]
// Примечание: маска добавляет суффикс " locked" только при включённой блокировке
// памяти (SetMemoryLockEnabled(true)) и успешной блокировке
// (также возможны " lock-failed" / " unlocked")
}MarshalJSON
func (sv *SecureValue) MarshalJSON() ([]byte, error)Реализует интерфейс json.Marshaler. Возвращает маскированное представление, предотвращая случайную утечку секрета через основанные на отражении сериализаторы вроде json.Marshal. Открытый текст никогда не появится в JSON-выводе.
Возвращает:
[]byte- JSON-безопасная маскированная строка (например"[SECURE:32 bytes]"), для nil возвращает"null"error- всегда возвращает nil
type Response struct {
APIKey *env.SecureValue `json:"api_key"`
}
resp := Response{APIKey: env.NewSecureValue("sk-1234567890")}
data, _ := json.Marshal(resp)
// {"api_key":"[SECURE:16 bytes]"}
// Открытый текст не появится в выводеБезопасный дизайн
MarshalJSON гарантирует, что даже при встраивании SecureValue в структуру и JSON-сериализации открытый текст не утечёт. Вывод согласуется с String() / Masked().
MarshalText
func (sv *SecureValue) MarshalText() ([]byte, error)Реализует интерфейс encoding.TextMarshaler. Возвращает маскированное представление, согласованное с String(), предотвращая случайную утечку секрета через текстовые кодировщики вроде encoding/xml, text/template, структурированные логи.
Возвращает:
[]byte- маскированная строка (например"[SECURE:32 bytes]"), для nil возвращает"[NIL]"error- всегда возвращает nil
type Config struct {
Token *env.SecureValue `xml:"token"`
}
cfg := Config{Token: env.NewSecureValue("Bearer xyz")}
data, _ := xml.Marshal(cfg)
// <Config><token>[SECURE:10 bytes]</token></Config>Close
func (sv *SecureValue) Close() errorБезопасно обнуляет память и закрывает объект.
Возвращает:
error- всегда возвращает nil
Поведение:
- Безопасно обнуляет внутренние данные
- Помечает как закрытый
- Не возвращает в пул объектов
secret := env.GetSecure("TOKEN")
if secret != nil {
defer secret.Close()
// После Close память обнуляется
}Release
func (sv *SecureValue) Release()Обнуляет память и возвращает объект в пул.
Поведение:
- Безопасно обнуляет внутренние данные
- Очищает GC-финализатор
- Возвращает в пул объектов для переиспользования
secret := env.GetSecure("KEY")
if secret != nil {
defer secret.Release()
// После Release память обнуляется, а объект возвращается в пул
}Close против Release
Close()- только обнуляет, не возвращает в пулRelease()- обнуляет и возвращает в пул (рекомендуется для высокочастотных сценариев)
IsClosed
func (sv *SecureValue) IsClosed() boolПроверяет, закрыт ли объект.
Возвращает:
bool- закрыт ли
if secret.IsClosed() {
// Объект закрыт, использовать нельзя
}IsMemoryLocked
func (sv *SecureValue) IsMemoryLocked() boolПроверяет, заблокирована ли память (предотвращая выгрузку на диск).
Возвращает:
bool- заблокирована ли
if secret.IsMemoryLocked() {
fmt.Println("Memory is locked, protected from swapping")
}MemoryLockError
func (sv *SecureValue) MemoryLockError() errorВозвращает ошибку попытки блокировки памяти (если есть).
Возвращает:
error- ошибка блокировки; возвращает nil при успехе или если попытки не было
if err := secret.MemoryLockError(); err != nil {
log.Printf("Memory lock failed: %v", err)
}Конфигурация блокировки памяти
SetMemoryLockEnabled
func SetMemoryLockEnabled(enabled bool)Глобально включает/отключает блокировку памяти. Влияет на все вновь создаваемые SecureValue.
Параметры:
enabled- включать ли
package main
import "github.com/cybergodev/env"
func main() {
// Включение при запуске приложения
env.SetMemoryLockEnabled(true)
// Все последующие SecureValue будут пытаться заблокировать
}IsMemoryLockEnabled
func IsMemoryLockEnabled() boolПроверяет, включена ли блокировка памяти.
Возвращает:
bool- включена ли
if env.IsMemoryLockEnabled() {
// Блокировка памяти включена
}SetMemoryLockStrict
func SetMemoryLockStrict(strict bool)Устанавливает строгий режим. При включении NewSecureValueStrict возвращает ошибку при неудаче блокировки.
Параметры:
strict- включать ли строгий режим
env.SetMemoryLockEnabled(true)
env.SetMemoryLockStrict(true)
secret, err := env.NewSecureValueStrict("sensitive-data")
if err != nil {
// Блокировка не удалась
}IsMemoryLockStrict
func IsMemoryLockStrict() boolПроверяет, включён ли строгий режим.
Возвращает:
bool- включён ли
strict := env.IsMemoryLockStrict()IsMemoryLockSupported
func IsMemoryLockSupported() boolПроверяет, поддерживает ли текущая платформа блокировку памяти.
Возвращает:
bool- поддерживается ли
| Платформа | Поддержка |
|---|---|
| Linux | ✅ |
| macOS | ✅ |
| Windows | ✅ |
| FreeBSD | ✅ |
| wasm | ❌ |
Внимание
Возврат true означает только поддержку платформой, а не наличие у процесса достаточных прав. Linux требует CAP_IPC_LOCK или root-прав.
if env.IsMemoryLockSupported() {
env.SetMemoryLockEnabled(true)
}Функции инструментов безопасности
ClearBytes
func ClearBytes(b []byte)Безопасно обнуляет срез байтов. Немедленно обнуляйте чувствительные данные после использования.
Параметры:
b- срез байтов для обнуления
sensitive := []byte("secret-data")
// Использование...
env.ClearBytes(sensitive)
// sensitive теперь все 0IsSensitiveKey
func IsSensitiveKey(key string) boolПроверяет, соответствует ли имя ключа чувствительным шаблонам.
Параметры:
key- имя ключа
Возвращает:
bool- является ли чувствительным
if env.IsSensitiveKey("DB_PASSWORD") {
// Чувствительный ключ, обрабатывайте безопасно
secret := env.GetSecure("DB_PASSWORD")
if secret != nil {
defer secret.Release()
}
}Чувствительные шаблоны: password, secret, token, key, api_key, credential и т. д.
MaskValue
func MaskValue(key, value string) stringВозвращает маскированное значение в зависимости от чувствительности ключа.
Параметры:
key- имя ключаvalue- исходное значение
Возвращает:
string- маскированное значение
// Чувствительный ключ — возвращает формат [MASKED:N chars]
masked := env.MaskValue("API_KEY", "secret123")
// Возвращает: [MASKED:9 chars]
// Нечувствительный ключ — возвращает исходное значение (обрезается, если > 20 символов)
masked := env.MaskValue("APP_NAME", "myapp")
// Возвращает: myappMaskKey
func MaskKey(key string) stringМаскирует имя ключа для логов.
Параметры:
key- имя ключа
Возвращает:
string- маскированное имя ключа
masked := env.MaskKey("DB_PASSWORD")
// Возвращает: DB***SanitizeForLog
func SanitizeForLog(s string) stringОчищает строкoку от информации о чувствительных парах ключ-значение. Автоматически обнаруживает и маскирует чувствительные значения в формате key=value.
Параметры:
s- исходная строка
Возвращает:
string- очищенная строка
// Автоматическое маскирование чувствительных пар ключ-значение
msg := "Connected with password=secret123 api_key=abc123"
clean := env.SanitizeForLog(msg)
// Возвращает: "Connected with password=[MASKED] api_key=[MASKED]"MaskSensitiveInString
func MaskSensitiveInString(s string) stringМаскирует потенциально чувствительное содержимое в строке. Обрезает строки длиннее 50 символов.
Параметры:
s- исходная строка
Возвращает:
string- маскированная строка
// Длинные строки будут обрезаны (сохраняются первые 47 символов и добавляется "...")
long := "This is a very long string that exceeds 50 characters"
clean := env.MaskSensitiveInString(long)
// Возвращает: "This is a very long string that exceeds 50 char..."Сценарий использования
Используется для обрезки длинных строк, которые могут содержать чувствительные данные. Для автоматического маскирования чувствительных пар ключ-значение используйте SanitizeForLog.
Полный пример
package main
import (
"fmt"
"log"
"github.com/cybergodev/env"
)
func main() {
// Проверка и включение блокировки памяти
if env.IsMemoryLockSupported() {
env.SetMemoryLockEnabled(true)
fmt.Println("Memory locking enabled")
}
// Загрузка переменных окружения
if err := env.Load(".env"); err != nil {
log.Printf("Warning: %v", err)
}
// Безопасное получение чувствительного значения
apiKey := env.GetSecure("API_KEY")
if apiKey == nil {
log.Fatal("API_KEY not found")
}
defer apiKey.Release()
// Безопасное использование
fmt.Printf("API Key length: %d\n", apiKey.Length())
fmt.Printf("API Key (masked): %s\n", apiKey.Masked())
// Проверка состояния блокировки памяти
if apiKey.IsMemoryLocked() {
fmt.Println("Memory is locked")
}
// Проверка ошибки блокировки
if err := apiKey.MemoryLockError(); err != nil {
fmt.Printf("Memory lock warning: %v\n", err)
}
// Передача в другую функцию
connectAPI(apiKey.Reveal())
// Использование функций инструментов безопасности
logMessage := "Processing with API_KEY=secret"
safeMessage := env.SanitizeForLog(logMessage)
fmt.Println(safeMessage) // Processing with API_KEY=[MASKED]
}
func connectAPI(key string) {
// Подключение с использованием ключа...
fmt.Printf("Connecting with key of length %d\n", len(key))
}Внутренняя реализация
Пул объектов
SecureValue использует sync.Pool для уменьшения аллокаций памяти:
var secureValuePool = sync.Pool{
New: func() interface{} {
return &SecureValue{}
},
}GC-финализатор
При создании устанавливается GC-финализатор, обеспечивающий автоочистку при сборке мусора:
runtime.SetFinalizer(sv, (*SecureValue).finalize)Безопасное обнуление
Использует unsafe.Pointer для предотвращения оптимизаций компилятора (должно вызываться при удержании блокировки sv.mu):
func (sv *SecureValue) clearDataLocked() {
if len(sv.data) == 0 {
return
}
// Разблокировка памяти (если заблокирована)
if sv.locked {
internal.UnlockMemory(sv.data)
sv.locked = false
}
dataPtr := unsafe.Pointer(&sv.data[0])
for i := range sv.data {
*(*byte)(unsafe.Pointer(uintptr(dataPtr) + uintptr(i))) = 0
}
runtime.KeepAlive(sv.data)
sv.data = nil
sv.lockErr = nil
}Связанная документация
- Константы и ошибки - запрещённые ключи, шаблоны чувствительных ключей, типы ошибок
- Обзор безопасности - архитектура безопасности и ключевые особенности
- Контрольный список для продакшена - проверка безопасности перед запуском
- Loader API - метод GetSecure