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

Очень важно думать о том, чтобы приложения были надежными и защищёнными. 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 августа: «Модели межсервисного взаимодействия». Изучите различные модели взаимодействия между микросервисами и выберите оптимальный подход для вашего проекта. Записаться