Привет, друзья!

Очень важно думать о том, чтобы приложения были надежными и защищёнными. Go — язык, который известен своей простотой и производительностью. Но ни один язык не безопасен сам по себе и об этом нужно заботится самостоятельно.

В этой статье мы поделимся с вами методами, которые помогут сделать ваши Go-приложения неприступными крепостями.

Валидация пользовательского ввода

Валидация пользовательского ввода – одно из базовых действий списке задач. Программа должна рассматривать входные данные как потенциально вредоносные.

SQL-инъекции, XSS-атаки и другие подобные угрозы возникают из-за недообдуманных действий при обработке данных.

Для валидации входных данных можно использовать библиотеку Go Validator, которая позволяет проверять структуры, используя теги валидации:

package main

import (
    "fmt"
    "github.com/go-playground/validator"
)

type User struct {
    Email string `validate:"required,email"`
    Age   int    `validate:"gte=18"`
}

func main() {
    validate := validator.New()

    user := &User{
        Email: "invalid email",
        Age:   16,
    }

    err := validate.Struct(user)
    if err != nil {
        fmt.Println("Validation failed:", err)
    } else {
        fmt.Println("Validation passed!")
    }
}
  • Email: Использует валидатор для проверки формата электронной почты.

  • Age: Проверяет, что возраст пользователя не меньше 18 лет.

Этим шагом уже можно избежать большинства проблем еще на раннем этапе, защищая приложение от некорректного ввода.

Безопасное управление конфигурацией

Одна из главных уязвимостей приложений — это утечка конфиденциальной информации, такой как пароли, API-ключи и другие конфигурационные данные. В Go есть несколько способов управления конфигурацией и один из них – переменные окружения.

Это один из самых популярных подходов, позволяющий хранить конфиденциальные данные за пределами исходного кода.

Пример:

package main

import (
    "fmt"
    "os"
)

func main() {
    apiKey := os.Getenv("API_KEY")
    if apiKey == "" {
        fmt.Println("API key not set")
        return
    }
    fmt.Println("API key:", apiKey)
}
  • os.Getenv: Получает значение переменной окружения.

  • API key not set: Проверяет, установлена ли переменная окружения, и предотвращает запуск приложения с некорректными настройками.

Используйте .env файлы чтобы хранить конфигурации, которые не должны попадать в систему контроля версий, а так же скрывайте конфиденциальные данные.

Аутентификация и авторизация

Без надежной аутентификации и авторизации приложение превращается в открытую дверь для злоумышленников. В Go можно использовать пакеты, такие как jwt-go и gorilla/sessions, для управления сессиями и токенами.

JSON Web Tokens

JWT — это способ передачи безопасных данных между двумя сторонами в виде JSON-объектов.

Пример:

package main

import (
    "fmt"
    "github.com/dgrijalva/jwt-go"
    "time"
)

var mySigningKey = []byte("secret")

func GenerateJWT() (string, error) {
    token := jwt.New(jwt.SigningMethodHS256)
    claims := token.Claims.(jwt.MapClaims)
    claims["authorized"] = true
    claims["user"] = "user1"
    claims["exp"] = time.Now().Add(time.Minute * 30).Unix()

    tokenString, err := token.SignedString(mySigningKey)
    if err != nil {
        return "", err
    }

    return tokenString, nil
}

func main() {
    token, err := GenerateJWT()
    if err != nil {
        fmt.Println("Error generating token:", err)
        return
    }

    fmt.Println("Generated Token:", token)
}
  • jwt.New: Создает новый JWT-токен.

  • claims["exp"]: Устанавливает время жизни токена, чтобы он автоматически становился недействительным через 30 минут.

Шифрование данных

Хранение и передача данных в зашифрованном виде — неотъемлемая часть безопасности. В Go есть библиотекаcrypto, которая позволяет шифровать и расшифровывать данные.

Пример симметричного шифрования:

package main

import (
    "crypto/aes"
    "crypto/cipher"
    "crypto/rand"
    "encoding/hex"
    "fmt"
    "io"
)

func encrypt(text, key string) (string, error) {
    block, err := aes.NewCipher([]byte(key))
    if err != nil {
        return "", err
    }

    ciphertext := make([]byte, aes.BlockSize+len(text))
    iv := ciphertext[:aes.BlockSize]
    if _, err := io.ReadFull(rand.Reader, iv); err != nil {
        return "", err
    }

    stream := cipher.NewCFBEncrypter(block, iv)
    stream.XORKeyStream(ciphertext[aes.BlockSize:], []byte(text))

    return hex.EncodeToString(ciphertext), nil
}

func main() {
    text := "Hello, world!"
    key := "mysecretpasswordmysecretpassword" // 32 bytes

    encrypted, err := encrypt(text, key)
    if err != nil {
        fmt.Println("Error encrypting:", err)
        return
    }

    fmt.Println("Encrypted:", encrypted)
}
  • aes.NewCipher: Создает новый AES-блок шифра.

  • cipher.NewCFBEncrypter: Создает новый шифр для симметричного шифрования.

Управление зависимостями

Библиотеки, которые вы используете, могут содержать уязвимости, которые будут затрагивать само приложение. Go имеет отличный инструменты для управления зависимостями – Go Modules.

Модули должны быть включены, чтобы отслеживать и управлять зависимостями:

go mod init myapp
go get github.com/some/package

Обновляйте зависимости регулярно: следите за новыми версиями библиотек.

Используйте инструменты анализа безопасности: пример – Dependabot..


Безопасность — это не набор конкретных инструментов, а скорее процесс постоянного улучшения и адаптации к новым вызовам.

Если у вас есть свои фишки по безопасности или вопросы, делитесь ими в комментариях. Будем рады обсудить и обменяться опытом!

В заключение напоминаем про открытые уроки:

  • 14 августа: «Связь DR и HA в современных архитектурных решениях». Изучите взаимосвязь между аварийным восстановлением (DR) и высокой доступностью (HA) в современных системах. Записаться

  • 20 августа: «Модели межсервисного взаимодействия». Изучите различные модели взаимодействия между микросервисами и выберите оптимальный подход для вашего проекта. Записаться

Комментарии (1)


  1. Antgor
    13.08.2024 10:52
    +2

    Хранение "чувствительной" информации в env - плохая идея. Если приложение исполняется не в контейнере, (вот совсем legacy инфра) то его env будет виден всем админам. Если это k8s, то env всё равно должен быть в git где описаны деплойменты и будет виден админам инфры. Хранение секретов нужно выносить в хранилку секретов. Vault, Infiscal, AWS Secret manager. Вариантов много.