Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions cmd/certificate/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package main

import (
"bytes"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"log"
"math/big"
"net"
"os"
"time"
)

func main() {
// создаём шаблон сертификата
cert := &x509.Certificate{
// указываем уникальный номер сертификата
SerialNumber: big.NewInt(1658),
// заполняем базовую информацию о владельце сертификата
Subject: pkix.Name{
Organization: []string{"Yandex.Praktikum"},
Country: []string{"RU"},
},
// разрешаем использование сертификата для 127.0.0.1 и ::1
IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1), net.IPv6loopback},
// сертификат верен, начиная со времени создания
NotBefore: time.Now(),
// время жизни сертификата — 10 лет
NotAfter: time.Now().AddDate(10, 0, 0),
SubjectKeyId: []byte{1, 2, 3, 4, 6},
// устанавливаем использование ключа для цифровой подписи,
// а также клиентской и серверной авторизации
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageDigitalSignature,
}

// создаём новый приватный RSA-ключ длиной 4096 бит
// обратите внимание, что для генерации ключа и сертификата
// используется rand.Reader в качестве источника случайных данных
privateKey, err := rsa.GenerateKey(rand.Reader, 4096)
if err != nil {
log.Fatal(err)
}

// создаём сертификат x.509
certBytes, err := x509.CreateCertificate(rand.Reader, cert, cert, &privateKey.PublicKey, privateKey)
if err != nil {
log.Fatal(err)
}

// кодируем сертификат и ключ в формате PEM, который
// используется для хранения и обмена криптографическими ключами
var certPEM bytes.Buffer
err = pem.Encode(&certPEM, &pem.Block{
Type: "CERTIFICATE",
Bytes: certBytes,
})
if err != nil {
log.Fatal(err)
}

var privateKeyPEM bytes.Buffer
err = pem.Encode(&privateKeyPEM, &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(privateKey),
})
if err != nil {
log.Fatal(err)
}

// Сохраняем сертификат и приватный ключ в файлы cert.pem и private.pem
if err = os.WriteFile("cert.pem", certPEM.Bytes(), 0644); err != nil {
log.Fatal(err)
}

if err = os.WriteFile("key.pem", privateKeyPEM.Bytes(), 0644); err != nil {
log.Fatal(err)
}
}
5 changes: 5 additions & 0 deletions internal/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type App struct {
router *gin.Engine
urlHandler *handler.URLHandler
deleteWorker *worker.DeleteWorker
enableTLS bool
}

// New создаёт и настраивает новое приложение:
Expand Down Expand Up @@ -152,6 +153,7 @@ func New() (*App, error) {
router: router,
urlHandler: urlHandler,
deleteWorker: deleteWorker,
enableTLS: cfg.Enable_HTTPS,
}, nil
}

Expand Down Expand Up @@ -201,6 +203,9 @@ func (a *App) SetupRoutes() {

// Run запускает HTTP-сервер.
func (a *App) Run() error {
if a.enableTLS {
return a.server.ListenAndServeTLS("cert.pem", "key.pem")
}
return a.server.ListenAndServe()
}

Expand Down
169 changes: 135 additions & 34 deletions internal/config/flag/config.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
// Package config обеспечивает загрузку конфигурации из флагов командной строки
// и переменных окружения. Переменные окружения имеют приоритет.
// Package config обеспечивает загрузку конфигурации из флагов командной строки,
// переменных окружения и JSON-файла конфигурации.
// Приоритет: переменные окружения > флаги > JSON-файл.
package config

import (
"encoding/json"
"flag"
"os"
"strconv"
Expand All @@ -21,6 +23,17 @@ type ServerConfig struct {
SecretKey string `env:"SECRET_KEY"`
AuditFile string `env:"AUDIT_FILE"`
AuditURL string `env:"AUDIT_URL"`
Enable_HTTPS bool `env:"ENABLE_HTTPS"`
ConfigJSON string `env:"CONFIG"`
}

// JSONFileConfig — структура JSON-файла конфигурации.
type JSONFileConfig struct {
ServerAddress string `json:"server_address"`
BaseURL string `json:"base_url"`
FileStoragePath string `json:"file_storage_path"`
DatabaseDSN string `json:"database_dsn"`
EnableHTTPS *bool `json:"enable_https"`
}

var (
Expand All @@ -34,7 +47,10 @@ var (
secretKey *string
auditFile *string
auditURL *string
enableHTTPS *bool
flagsOnce sync.Once
jsonConfig *string
jsonConfigLong *string
)

func initFlags() {
Expand All @@ -43,55 +59,140 @@ func initFlags() {
envValue = flag.String("e", "dev", "environment")
fileValue = flag.String("f", "data.json", "file to store data")
dsnValue = flag.String("d", "", "database DSN")
deleteWorkerCount = flag.Int("w", 2, "number of delete worker channels")
deleteChannelSize = flag.Int("c", 1000, "size of each delete channel buffer")
secretKey = flag.String("s", "my-super-secret-key-change-in-production", "secret key for signing cookies")
deleteWorkerCount = flag.Int("wc", 2, "number of delete worker channels")
deleteChannelSize = flag.Int("cs", 1000, "size of each delete channel buffer")
secretKey = flag.String("sk", "my-super-secret-key-change-in-production", "secret key for signing cookies")
auditFile = flag.String("audit-file", "", "path to audit log file")
auditURL = flag.String("audit-url", "", "URL of remote audit server")
enableHTTPS = flag.Bool("s", false, "enable HTTPS")
jsonConfig = flag.String("c", "", "path to JSON config file")
jsonConfigLong = flag.String("config", "", "path to JSON config file")
}

// loadJSONConfig загружает конфигурацию из JSON-файла.
func loadJSONConfig(path string) (*JSONFileConfig, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var cfg JSONFileConfig
if err := json.Unmarshal(data, &cfg); err != nil {
return nil, err
}
return &cfg, nil
}

// NewServerConfig создаёт конфигурацию, читая флаги и переменные окружения.
// NewServerConfig создаёт конфигурацию, читая флаги, JSON-файл и переменные окружения.
// Приоритет: переменная окружения > явно переданный флаг > JSON-конфиг > значение флага по умолчанию.
func NewServerConfig() *ServerConfig {
flagsOnce.Do(initFlags)

if !flag.Parsed() {
flag.Parse()
}

cfg := &ServerConfig{}
// Определяем, какие флаги были явно переданы
flagSet := make(map[string]bool)
flag.Visit(func(f *flag.Flag) {
flagSet[f.Name] = true
})

// Определяем путь к JSON-конфигурации: ENV CONFIG > флаг -c/-config
configPath := ""
if v, ok := os.LookupEnv("CONFIG"); ok {
configPath = v
} else if *jsonConfig != "" {
configPath = *jsonConfig
} else if *jsonConfigLong != "" {
configPath = *jsonConfigLong
}

if envAddr, ok := os.LookupEnv("SERVER_ADDRESS"); ok {
cfg.AddrServer = envAddr
} else {
// Загружаем JSON-конфиг, если путь задан
var jcfg *JSONFileConfig
if configPath != "" {
if loaded, err := loadJSONConfig(configPath); err == nil {
jcfg = loaded
}
}

cfg := &ServerConfig{
ConfigJSON: configPath,
}

// --- Поля с поддержкой JSON-конфига ---

// AddrServer
cfg.AddrServer = *addrServer
if jcfg != nil && jcfg.ServerAddress != "" {
cfg.AddrServer = jcfg.ServerAddress
}
if flagSet["a"] {
cfg.AddrServer = *addrServer
}
if v, ok := os.LookupEnv("SERVER_ADDRESS"); ok {
cfg.AddrServer = v
}

if envURL, ok := os.LookupEnv("BASE_URL"); ok {
cfg.AddrURL = envURL
} else {
// AddrURL
cfg.AddrURL = *addrURL
if jcfg != nil && jcfg.BaseURL != "" {
cfg.AddrURL = jcfg.BaseURL
}
if flagSet["b"] {
cfg.AddrURL = *addrURL
}

if envEnv, ok := os.LookupEnv("ENV"); ok {
cfg.Env = envEnv
} else {
cfg.Env = *envValue
if v, ok := os.LookupEnv("BASE_URL"); ok {
cfg.AddrURL = v
}

if envFile, ok := os.LookupEnv("FILE_STORAGE_PATH"); ok {
cfg.File = envFile
} else {
// File
cfg.File = *fileValue
if jcfg != nil && jcfg.FileStoragePath != "" {
cfg.File = jcfg.FileStoragePath
}
if flagSet["f"] {
cfg.File = *fileValue
}
if v, ok := os.LookupEnv("FILE_STORAGE_PATH"); ok {
cfg.File = v
}

if envDSN, ok := os.LookupEnv("DATABASE_DSN"); ok {
cfg.DSN = envDSN
} else {
// DSN
cfg.DSN = *dsnValue
if jcfg != nil && jcfg.DatabaseDSN != "" {
cfg.DSN = jcfg.DatabaseDSN
}
if flagSet["d"] {
cfg.DSN = *dsnValue
}
if v, ok := os.LookupEnv("DATABASE_DSN"); ok {
cfg.DSN = v
}

// Enable_HTTPS
cfg.Enable_HTTPS = *enableHTTPS
if jcfg != nil && jcfg.EnableHTTPS != nil {
cfg.Enable_HTTPS = *jcfg.EnableHTTPS
}
if flagSet["s"] {
cfg.Enable_HTTPS = *enableHTTPS
}
if v, ok := os.LookupEnv("ENABLE_HTTPS"); ok {
if val, err := strconv.ParseBool(v); err == nil {
cfg.Enable_HTTPS = val
}
}

// --- Поля без JSON-конфига ---

if v, ok := os.LookupEnv("ENV"); ok {
cfg.Env = v
} else {
cfg.Env = *envValue
}

if envWorkerCount, ok := os.LookupEnv("DELETE_WORKER_COUNT"); ok {
if count, err := parseIntFromEnv(envWorkerCount); err == nil {
if v, ok := os.LookupEnv("DELETE_WORKER_COUNT"); ok {
if count, err := parseIntFromEnv(v); err == nil {
cfg.DeleteWorkerCount = count
} else {
cfg.DeleteWorkerCount = *deleteWorkerCount
Expand All @@ -100,8 +201,8 @@ func NewServerConfig() *ServerConfig {
cfg.DeleteWorkerCount = *deleteWorkerCount
}

if envChannelSize, ok := os.LookupEnv("DELETE_CHANNEL_SIZE"); ok {
if size, err := parseIntFromEnv(envChannelSize); err == nil {
if v, ok := os.LookupEnv("DELETE_CHANNEL_SIZE"); ok {
if size, err := parseIntFromEnv(v); err == nil {
cfg.DeleteChannelSize = size
} else {
cfg.DeleteChannelSize = *deleteChannelSize
Expand All @@ -110,20 +211,20 @@ func NewServerConfig() *ServerConfig {
cfg.DeleteChannelSize = *deleteChannelSize
}

if envSecretKey, ok := os.LookupEnv("SECRET_KEY"); ok {
cfg.SecretKey = envSecretKey
if v, ok := os.LookupEnv("SECRET_KEY"); ok {
cfg.SecretKey = v
} else {
cfg.SecretKey = *secretKey
}

if envAuditFile, ok := os.LookupEnv("AUDIT_FILE"); ok {
cfg.AuditFile = envAuditFile
if v, ok := os.LookupEnv("AUDIT_FILE"); ok {
cfg.AuditFile = v
} else {
cfg.AuditFile = *auditFile
}

if envAuditURL, ok := os.LookupEnv("AUDIT_URL"); ok {
cfg.AuditURL = envAuditURL
if v, ok := os.LookupEnv("AUDIT_URL"); ok {
cfg.AuditURL = v
} else {
cfg.AuditURL = *auditURL
}
Expand Down
Loading
Loading