Skip to content

Commit 1ce8a46

Browse files
committed
Add HTTPS mode to setup wizard
1 parent 04fc81a commit 1ce8a46

File tree

8 files changed

+313
-9
lines changed

8 files changed

+313
-9
lines changed

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ stellar-disbursement-platform-backend
2727
.idea
2828
.cursor
2929

30+
# Local HTTPS certificates
31+
dev/certs/
32+
3033
# Rust & Soroban
3134
target/
32-
test_snapshots/
35+
test_snapshots/

dev/README.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
- [Pre-requisites](#pre-requisites)
1010
- [Clone the repository:](#clone-the-repository)
1111
- [Update local DNS](#update-local-dns)
12+
- [Set up local HTTPS (optional)](#set-up-local-https-optional)
1213
- [Automated Stellar Account Creation and .env Configuration](#automated-stellar-account-creation-and-env-configuration)
1314
- [Start/Stop Local Environment](#startstop-local-environment)
1415
- [Login to the SDP and send a Disbursement](#login-to-the-sdp-and-send-a-disbursement)
@@ -62,6 +63,23 @@ To include them, you can run command `sudo nano /etc/hosts` and insert the lines
6263
127.0.0.1 pinkcorp.stellar.local
6364
```
6465

66+
### Set up local HTTPS (optional)
67+
68+
HTTPS is required for working with WebAuthn/passkeys. If you want the wizard to launch the dashboard over HTTPS (`https://<tenant>.stellar.local:3443`):
69+
70+
1. Install [mkcert](https://web.dev/articles/how-to-use-local-https):
71+
```sh
72+
brew install mkcert
73+
mkcert -install
74+
```
75+
2. Generate local TLS certs (run from the repo root once):
76+
```sh
77+
mkdir -p dev/certs
78+
mkcert -key-file dev/certs/stellar.local-key.pem -cert-file dev/certs/stellar.local.pem \
79+
"*.stellar.local" localhost 127.0.0.1 ::1
80+
```
81+
3. When the setup wizard asks, choose HTTPS.
82+
6583
### Automated Stellar Account Creation and .env Configuration
6684

6785
Use the unified setup wizard to generate accounts and a ready-to-use `.env`:
@@ -98,7 +116,7 @@ make setup
98116
The setup wizard will:
99117
1. Create or select an `.env` configuration
100118
2. Generate Stellar accounts if needed (with testnet funding)
101-
3. Optionally launch the Docker environment immediately
119+
3. Optionally launch the Docker environment immediately (with either HTTP on :3000 or HTTPS on :3443 if you generated certs)
102120
4. Initialize tenants and test users
103121

104122
For existing configurations, you can launch directly by selecting from available `.env` files in the `dev/` directory.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
version: '3.8'
2+
services:
3+
sdp-frontend:
4+
extends:
5+
file: docker-compose-frontend.yml
6+
service: sdp-frontend
7+
sdp-frontend-proxy:
8+
image: nginx:1.25-alpine
9+
container_name: sdp-frontend-mtn-https
10+
depends_on:
11+
- sdp-frontend
12+
ports:
13+
- "3443:443"
14+
- "3080:80"
15+
extra_hosts:
16+
- "host.docker.internal:host-gateway"
17+
volumes:
18+
- ./nginx-https.conf:/etc/nginx/conf.d/default.conf:ro
19+
- ./certs:/etc/nginx/ssl:ro
20+
- ./env-config-${NETWORK_TYPE:-testnet}.js:/etc/nginx/html-env/env-config.js:ro

dev/nginx-https.conf

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
map $http_upgrade $connection_upgrade {
2+
default upgrade;
3+
'' close;
4+
}
5+
6+
upstream frontend_dev {
7+
server host.docker.internal:3000;
8+
keepalive 16;
9+
}
10+
11+
upstream frontend_static {
12+
server sdp-frontend:80;
13+
keepalive 16;
14+
}
15+
16+
server {
17+
listen 80;
18+
server_name localhost *.stellar.local;
19+
return 301 https://$host$request_uri;
20+
}
21+
22+
server {
23+
listen 443 ssl;
24+
server_name localhost *.stellar.local;
25+
26+
ssl_certificate /etc/nginx/ssl/stellar.local.pem;
27+
ssl_certificate_key /etc/nginx/ssl/stellar.local-key.pem;
28+
ssl_protocols TLSv1.2 TLSv1.3;
29+
30+
gzip on;
31+
gzip_types application/javascript application/rss+xml application/x-font-opentype application/x-font-truetype application/x-font-ttf application/xhtml+xml application/xml font/opentype font/otf font/ttf image/svg+xml image/x-icon text/css text/javascript text/plain text/xml;
32+
33+
location = /settings/env-config.js {
34+
alias /etc/nginx/html-env/env-config.js;
35+
add_header Cache-Control "no-store";
36+
}
37+
38+
location / {
39+
proxy_http_version 1.1;
40+
proxy_set_header Host $host;
41+
proxy_set_header X-Real-IP $remote_addr;
42+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
43+
proxy_set_header X-Forwarded-Proto https;
44+
proxy_set_header Upgrade $http_upgrade;
45+
proxy_set_header Connection $connection_upgrade;
46+
proxy_cache_bypass $http_upgrade;
47+
proxy_read_timeout 300s;
48+
proxy_connect_timeout 5s;
49+
proxy_pass http://frontend_dev;
50+
proxy_intercept_errors on;
51+
error_page 502 503 504 = @frontend_static;
52+
}
53+
54+
location @frontend_static {
55+
proxy_http_version 1.1;
56+
proxy_set_header Host $host;
57+
proxy_set_header X-Real-IP $remote_addr;
58+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
59+
proxy_set_header X-Forwarded-Proto https;
60+
proxy_pass http://frontend_static;
61+
}
62+
}

tools/sdp-setup/internal/config/env.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package config
33

44
import (
55
"fmt"
6+
"net/url"
67
"os"
78
"path/filepath"
89
"strconv"
@@ -21,6 +22,8 @@ const (
2122
DefaultAdminKey = "api_key_1234567890"
2223
DefaultAdminURL = "http://localhost:8003"
2324
DefaultWorkDir = "dev"
25+
DefaultHTTPSPort = "3443"
26+
DefaultHTTPPort = "3000"
2427
)
2528

2629
// Config represents the environment configuration for SDP
@@ -37,6 +40,11 @@ type Config struct {
3740
SetupName string // Optional setup name for multi-config support
3841
SingleTenantMode bool // Whether to use single-tenant mode
3942

43+
// Frontend HTTPS settings
44+
UseHTTPS bool
45+
FrontendProtocol string // http or https
46+
FrontendPort string // exposed port for the UI (3000/3443)
47+
4048
// Following config doesn't get written to env file
4149
WorkDir string
4250
EnvFilePath string
@@ -125,6 +133,7 @@ type ConfigOpts struct {
125133
Network utils.NetworkType
126134
SingleTenantMode bool
127135
Accounts accounts.Info
136+
EnableHTTPS bool
128137
}
129138

130139
// NewConfig creates a new Config
@@ -134,6 +143,9 @@ func NewConfig(opts ConfigOpts) Config {
134143
cfg.SingleTenantMode = opts.SingleTenantMode
135144
cfg.EnvFilePath = opts.EnvPath
136145
cfg.DockerProject = ComposeProjectName(opts.SetupName)
146+
if opts.EnableHTTPS {
147+
cfg.EnableHTTPS()
148+
}
137149
return cfg
138150
}
139151

@@ -163,6 +175,11 @@ func Load(path string) (Config, error) {
163175
AdminKey: DefaultAdminKey,
164176
AdminURL: DefaultAdminURL,
165177
WorkDir: DefaultWorkDir,
178+
179+
// Frontend defaults
180+
UseHTTPS: false,
181+
FrontendProtocol: "http",
182+
FrontendPort: DefaultHTTPPort,
166183
}
167184

168185
// Parse SINGLE_TENANT_MODE
@@ -173,6 +190,29 @@ func Load(path string) (Config, error) {
173190
}
174191
}
175192

193+
// Parse USE_HTTPS (optional)
194+
if useHTTPS := strings.TrimSpace(envMap["USE_HTTPS"]); useHTTPS != "" {
195+
if cfg.UseHTTPS, err = strconv.ParseBool(useHTTPS); err != nil {
196+
return Config{}, fmt.Errorf("parsing USE_HTTPS: %w", err)
197+
}
198+
}
199+
200+
// Parse SDP_UI_BASE_URL (optional)
201+
if base := strings.TrimSpace(envMap["SDP_UI_BASE_URL"]); base != "" {
202+
if u, parseErr := url.Parse(base); parseErr == nil && u.Scheme != "" {
203+
cfg.FrontendProtocol = u.Scheme
204+
if port := u.Port(); port != "" {
205+
cfg.FrontendPort = port
206+
}
207+
}
208+
}
209+
210+
if cfg.UseHTTPS {
211+
cfg.EnableHTTPS()
212+
} else {
213+
cfg.DisableHTTPS()
214+
}
215+
176216
return cfg, nil
177217
}
178218

@@ -201,6 +241,9 @@ func Write(cfg Config, path string) error {
201241
"DISTRIBUTION_SEED": cfg.DistributionSeed,
202242
"CHANNEL_ACCOUNT_ENCRYPTION_PASSPHRASE": cfg.DistributionSeed,
203243
"DISTRIBUTION_ACCOUNT_ENCRYPTION_PASSPHRASE": cfg.DistributionSeed,
244+
"USE_HTTPS": strconv.FormatBool(cfg.UseHTTPS),
245+
"SDP_UI_BASE_URL": cfg.FrontendBaseURL("localhost"),
246+
"BASE_URL": "http://localhost:8000",
204247
}
205248

206249
if cfg.NetworkType == "pubnet" {
@@ -228,6 +271,10 @@ func fromAccounts(networkType utils.NetworkType, acc accounts.Info) Config {
228271
AdminKey: DefaultAdminKey,
229272
AdminURL: DefaultAdminURL,
230273
WorkDir: DefaultWorkDir,
274+
275+
UseHTTPS: false,
276+
FrontendProtocol: "http",
277+
FrontendPort: DefaultHTTPPort,
231278
}
232279

233280
switch networkType {
@@ -259,3 +306,32 @@ func ComposeProjectName(setupName string) string {
259306
}
260307
return fmt.Sprintf("%s-%s", DefaultProject, setupName)
261308
}
309+
310+
// EnableHTTPS toggles HTTPS related defaults on the config.
311+
func (cfg *Config) EnableHTTPS() {
312+
cfg.UseHTTPS = true
313+
cfg.FrontendProtocol = "https"
314+
if cfg.FrontendPort == "" || cfg.FrontendPort == DefaultHTTPPort {
315+
cfg.FrontendPort = DefaultHTTPSPort
316+
}
317+
}
318+
319+
// DisableHTTPS forces HTTP defaults on the config.
320+
func (cfg *Config) DisableHTTPS() {
321+
cfg.UseHTTPS = false
322+
cfg.FrontendProtocol = "http"
323+
cfg.FrontendPort = DefaultHTTPPort
324+
}
325+
326+
// FrontendBaseURL builds a UI base URL for the provided host (e.g., localhost or bluecorp.stellar.local)
327+
func (cfg Config) FrontendBaseURL(host string) string {
328+
protocol := cfg.FrontendProtocol
329+
if protocol == "" {
330+
protocol = "http"
331+
}
332+
port := cfg.FrontendPort
333+
if port == "" {
334+
port = DefaultHTTPPort
335+
}
336+
return fmt.Sprintf("%s://%s:%s", protocol, host, port)
337+
}

tools/sdp-setup/internal/docker/service.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"errors"
66
"fmt"
7+
"os"
78
"os/exec"
89
"path/filepath"
910
"strings"
@@ -19,20 +20,33 @@ var ErrUserDeclined = errors.New("user declined to proceed")
1920
type Service struct {
2021
cfg config.Config
2122
executor CommandExecutor
23+
files []string
2224
}
2325

2426
// NewService creates a new Docker service.
2527
func NewService(cfg config.Config) (*Service, error) {
28+
if cfg.WorkDir != "" {
29+
if abs, err := filepath.Abs(cfg.WorkDir); err == nil {
30+
cfg.WorkDir = abs
31+
}
32+
}
2633
if cfg.EnvFilePath != "" {
2734
if abs, err := filepath.Abs(cfg.EnvFilePath); err == nil {
2835
cfg.EnvFilePath = abs
2936
}
3037
}
38+
39+
files := []string{"docker-compose.yml"}
40+
if cfg.UseHTTPS {
41+
files = append(files, "docker-compose-https-frontend.yml")
42+
}
43+
3144
return &Service{
3245
cfg: cfg,
3346
executor: &DefaultExecutor{
3447
cfg: cfg,
3548
},
49+
files: files,
3650
}, nil
3751
}
3852

@@ -65,6 +79,12 @@ func (s *Service) StartDockerStack(ctx context.Context) error {
6579
return err
6680
}
6781

82+
if s.cfg.UseHTTPS {
83+
if err := s.prepareHTTPS(); err != nil {
84+
return err
85+
}
86+
}
87+
6888
if err := s.composeDown(ctx); err != nil {
6989
return err
7090
}
@@ -75,13 +95,32 @@ func (s *Service) StartDockerStack(ctx context.Context) error {
7595
return nil
7696
}
7797

98+
// prepareHTTPS validates that HTTPS certs already exist.
99+
func (s *Service) prepareHTTPS() error {
100+
certsDir := filepath.Join(s.cfg.WorkDir, "certs")
101+
certPath := filepath.Join(certsDir, "stellar.local.pem")
102+
keyPath := filepath.Join(certsDir, "stellar.local-key.pem")
103+
104+
if _, err := os.Stat(certPath); err == nil {
105+
if _, err := os.Stat(keyPath); err == nil {
106+
fmt.Println("Using TLS certs from dev/certs (HTTPS enabled)")
107+
return nil
108+
}
109+
}
110+
111+
return fmt.Errorf("https selected but TLS certs are missing; expected dev/certs/stellar.local.pem and dev/certs/stellar.local-key.pem (generate with mkcert; see dev/README.md)")
112+
}
113+
78114
// composeDown runs 'docker compose down' with the given config.
79115
func (s *Service) composeDown(ctx context.Context) error {
80116
fmt.Println("====> docker compose down")
81117

82118
args := []string{"compose", "-p", s.cfg.DockerProject}
83119
args = append(args, "--env-file", s.cfg.EnvFilePath)
120+
args = append(args, "-f", "docker-compose.yml")
121+
args = append(args, "-f", "docker-compose-https-frontend.yml")
84122
args = append(args, "down")
123+
args = append(args, "--remove-orphans")
85124

86125
if err := s.executor.Execute(ctx, "docker", args...); err != nil {
87126
return fmt.Errorf("running docker compose down: %w", err)
@@ -95,6 +134,9 @@ func (s *Service) composeUp(ctx context.Context) error {
95134

96135
args := []string{"compose", "-p", s.cfg.DockerProject}
97136
args = append(args, "--env-file", s.cfg.EnvFilePath)
137+
for _, file := range s.files {
138+
args = append(args, "-f", file)
139+
}
98140
args = append(args, "up", "-d")
99141

100142
if err := s.executor.Execute(ctx, "docker", args...); err != nil {

0 commit comments

Comments
 (0)