Skip to content
Open
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
16 changes: 16 additions & 0 deletions cmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ type EnvironmentConfig struct {
MigrationTools MigrationToolsConfig `yaml:"migration_tools"`
Clickhouse ClickhouseConfig `yaml:"clickhouse"`
Fluentbit FluentbitConfig `yaml:"fluentbit"`
Nginx NginxConfig `yaml:"nginx"`
}

type GlobalConfig struct {
Expand All @@ -72,6 +73,11 @@ type FeatureConfig struct {
Utapi UtapiFeatureConfig `yaml:"utapi"`
Migration MigrationFeatureConfig `yaml:"migration"`
AccessLogging AccessLoggingFeatureConfig `yaml:"access_logging"`
S3Frontend S3FrontendFeatureConfig `yaml:"s3_frontend"`
}

type S3FrontendFeatureConfig struct {
Enabled bool `yaml:"enabled"`
}

type ScubaFeatureConfig struct {
Expand Down Expand Up @@ -228,6 +234,12 @@ type AccessLoggingFeatureConfig struct {
Enabled bool `yaml:"enabled"`
}

type NginxConfig struct {
Image string `yaml:"image"`
HTTPPort uint16 `yaml:"http_port"`
SSLPort uint16 `yaml:"ssl_port"`
}

func DefaultEnvironmentConfig() EnvironmentConfig {
return EnvironmentConfig{
Global: GlobalConfig{
Expand Down Expand Up @@ -290,6 +302,10 @@ func DefaultEnvironmentConfig() EnvironmentConfig {
MigrationTools: MigrationToolsConfig{},
Clickhouse: ClickhouseConfig{},
Fluentbit: FluentbitConfig{},
Nginx: NginxConfig{
HTTPPort: 80,
SSLPort: 443,
},
}
}

Expand Down
98 changes: 98 additions & 0 deletions cmd/configure.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
package main

import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"math/big"
"os"
"path/filepath"
"time"

"github.com/rs/zerolog/log"
)
Expand Down Expand Up @@ -77,6 +85,7 @@ func configureEnv(cfg EnvironmentConfig, envDir string) error {
generateMigrationToolsConfig,
generateClickhouseConfig,
generateFluentbitConfig,
generateNginxConfig,
}

configDir := filepath.Join(envDir, "config")
Expand Down Expand Up @@ -234,3 +243,92 @@ func generateFluentbitConfig(cfg EnvironmentConfig, path string) error {

return renderTemplates(cfg, "templates/fluentbit", filepath.Join(path, "fluentbit"), templates)
}

func generateNginxConfig(cfg EnvironmentConfig, path string) error {
if !cfg.Features.S3Frontend.Enabled {
return nil
}

nginxDir := filepath.Join(path, "nginx")

if err := renderTemplateToFile(
getTemplates(),
"templates/nginx/nginx.conf",
cfg,
filepath.Join(nginxDir, "nginx.conf"),
); err != nil {
return err
}

return generateTLSCertificate(nginxDir, "s3-frontend.key", "s3-frontend.crt")
}

// generateTLSCertificate creates a self-signed TLS certificate and key pair.
func generateTLSCertificate(dir, keyName, certName string) error {
keyPath := filepath.Join(dir, keyName)
certPath := filepath.Join(dir, certName)

// Skip if both files already exist
_, keyErr := os.Stat(keyPath)
_, certErr := os.Stat(certPath)
if keyErr == nil && certErr == nil {
log.Debug().Str("dir", dir).Msg("TLS certificate already exists, skipping generation")
return nil
}

key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return fmt.Errorf("failed to generate TLS key: %w", err)
}

serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
if err != nil {
return fmt.Errorf("failed to generate serial number: %w", err)
}

template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{"Scality Workbench"},
},
DNSNames: []string{"localhost", "s3.docker.test"},
NotBefore: time.Now(),
NotAfter: time.Now().Add(10 * 365 * 24 * time.Hour),
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
}

certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key)
if err != nil {
return fmt.Errorf("failed to create TLS certificate: %w", err)
}

certFile, err := os.Create(certPath)
if err != nil {
return fmt.Errorf("failed to create cert file: %w", err)
}
defer func() { _ = certFile.Close() }()

if err := pem.Encode(certFile, &pem.Block{Type: "CERTIFICATE", Bytes: certDER}); err != nil {
return fmt.Errorf("failed to write TLS certificate: %w", err)
}

keyDER, err := x509.MarshalECPrivateKey(key)
if err != nil {
return fmt.Errorf("failed to marshal TLS key: %w", err)
}

keyFile, err := os.OpenFile(keyPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return fmt.Errorf("failed to create key file: %w", err)
}
defer func() { _ = keyFile.Close() }()

if err := pem.Encode(keyFile, &pem.Block{Type: "EC PRIVATE KEY", Bytes: keyDER}); err != nil {
return fmt.Errorf("failed to write TLS key: %w", err)
}

log.Info().Str("dir", dir).Msg("Generated self-signed TLS certificate")
return nil
}
4 changes: 4 additions & 0 deletions cmd/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ func getComposeProfiles(cfg EnvironmentConfig) []string {
profiles = append(profiles, "feature-access-logging")
}

if cfg.Features.S3Frontend.Enabled {
profiles = append(profiles, "feature-s3-frontend")
}

return profiles
}

Expand Down
7 changes: 7 additions & 0 deletions templates/cloudserver/config-v9.json
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,16 @@
}
},
"requests": {
{{- if .Features.S3Frontend.Enabled }}
"viaProxy": true,
"trustedProxyCIDRs": ["127.0.0.1/8"],
"extractClientIPFromHeader": "x-forwarded-for",
"extractProtocolFromHeader": "x-forwarded-proto"
{{- else }}
"viaProxy": false,
"trustedProxyCIDRs": [],
"extractClientIPFromHeader": ""
{{- end }}
},
{{ if .Features.BucketNotifications.Enabled }}
"bucketNotificationDestinations": [
Expand Down
1 change: 1 addition & 0 deletions templates/global/defaults.env
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ UTAPI_IMAGE="{{ .Utapi.Image }}"
MIGRATION_TOOLS_IMAGE="{{ .MigrationTools.Image }}"
CLICKHOUSE_IMAGE="{{ .Clickhouse.Image }}"
FLUENTBIT_IMAGE="{{ .Fluentbit.Image }}"
NGINX_IMAGE="{{ .Nginx.Image }}"

METADATA_S3_DB_VERSION="{{ .S3Metadata.VFormat }}"
CLOUDSERVER_ENABLE_NULL_VERSION_COMPAT_MODE="{{ .Cloudserver.EnableNullVersionCompatMode }}"
11 changes: 11 additions & 0 deletions templates/global/docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,17 @@ services:
profiles:
- base

s3-frontend:
image: ${NGINX_IMAGE}
container_name: workbench-s3-frontend
network_mode: host
volumes:
- ./config/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./config/nginx/s3-frontend.crt:/certs/s3-frontend.crt:ro
- ./config/nginx/s3-frontend.key:/certs/s3-frontend.key:ro
profiles:
- feature-s3-frontend

fluentbit:
image: ${FLUENTBIT_IMAGE}
container_name: workbench-fluentbit
Expand Down
8 changes: 8 additions & 0 deletions templates/global/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ features:
access_logging:
enabled: false

s3_frontend:
enabled: false

cloudserver:
image: ghcr.io/scality/cloudserver:9.2.22

Expand Down Expand Up @@ -63,3 +66,8 @@ clickhouse:

fluentbit:
image: fluent/fluent-bit:3.2.2

nginx:
image: nginx:1.27-alpine
http_port: 80
ssl_port: 443
98 changes: 98 additions & 0 deletions templates/nginx/nginx.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
worker_processes auto;

events {
worker_connections 1024;
}

http {
map $http_authorization $is_iam {
default "0";
"~Credential=[^/]+\/[^/]+\/[^/]+\/iam\/aws4_request" "1";
}

map $http_authorization $is_sts {
default "0";
"~Credential=[^/]+\/[^/]+\/[^/]+\/sts\/aws4_request" "1";
}

large_client_header_buffers 4 8224;

proxy_cache off;
tcp_nodelay on;
underscores_in_headers on;
ignore_invalid_headers off;

upstream s3 {
keepalive 256;
server 127.0.0.1:8000 fail_timeout=0s max_fails=0;
}

{{ if .Features.Scuba.Enabled }}
upstream scuba {
keepalive 256;
server 127.0.0.1:9000;
}
{{ end }}

upstream iam {
keepalive 256;
server 127.0.0.1:8600;
}

upstream sts {
keepalive 256;
server 127.0.0.1:8800;
}

server {
listen {{ .Nginx.HTTPPort }};
listen {{ .Nginx.SSLPort }} ssl;

server_tokens off;

ssl_certificate /certs/s3-frontend.crt;
ssl_certificate_key /certs/s3-frontend.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_session_timeout 5m;
ssl_session_cache shared:SSL:10m;

location / {
proxy_request_buffering off;
proxy_buffering off;

proxy_pass http://s3;

proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header Connection "";

proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-SSL-Cipher $ssl_cipher;
proxy_set_header X-SSL-Protocol $ssl_protocol;

proxy_send_timeout 300s;
proxy_read_timeout 300s;

client_max_body_size 0;

if ($is_iam) {
proxy_pass http://iam;
}

if ($is_sts) {
proxy_pass http://sts;
}

{{ if .Features.Scuba.Enabled }}
location /_/sur/ {
proxy_set_header Host $http_host;
proxy_set_header proxy_path $uri;
proxy_pass http://scuba/;
}
{{ end }}
}
}
}
Loading