feat: Enhance HTTPS proxy routing and certificate management
- Improve protocol detection for proxy routes using domain and container labels - Add proactive Let's Encrypt certificate request for HTTPS routes - Implement robust domain resolution and certificate request handling - Add detailed logging for certificate request process - Handle Let's Encrypt rate limiting and retry scenarios - Extract hostname from domain names with protocol prefixes
- commit
- 2445c7c8fef5ec98fe61f88c032544a38c76def3
- author
- bamen · · 2025-03-09 16:00 UTC
- parent
- 5620434
- signature
- unsigned
2 files · +181 -4
M internal/httpserve/handlers/endpoint_deploy.go +25 -1
| @@ -399,9 +399,32 @@ func addProxyRoute(a *server.App, containerID, containerIP, containerPort, targe | |||
| 399 | 399 | return fmt.Errorf("failed to create proxy: %w", err) | |
| 400 | 400 | } | |
| 401 | 401 | ||
| 402 | - | // Extract the protocol (default to http) | |
| 402 | + | // Extract the protocol from the target domain | |
| 403 | 403 | protocol := "http" | |
| 404 | 404 | ||
| 405 | + | // Check if the target domain specifies HTTPS | |
| 406 | + | if strings.HasPrefix(strings.ToLower(targetDomain), "https://") { | |
| 407 | + | protocol = "https" | |
| 408 | + | log.Debug("Using HTTPS protocol for proxy route based on target domain", | |
| 409 | + | "domain", targetDomain, | |
| 410 | + | "protocol", protocol) | |
| 411 | + | } else { | |
| 412 | + | // If target domain doesn't specify protocol, check the container labels | |
| 413 | + | containerInfo, err := docker.GetContainerInfo(containerID) | |
| 414 | + | if err == nil { | |
| 415 | + | // Check if gordon.proxy.ssl label is set to true | |
| 416 | + | if sslValue, exists := containerInfo.Config.Labels["gordon.proxy.ssl"]; exists && | |
| 417 | + | (sslValue == "true" || sslValue == "1" || sslValue == "yes") { | |
| 418 | + | protocol = "https" | |
| 419 | + | log.Debug("Using HTTPS protocol for proxy route based on container labels", | |
| 420 | + | "domain", targetDomain, | |
| 421 | + | "containerID", containerID, | |
| 422 | + | "label_value", sslValue, | |
| 423 | + | "protocol", protocol) | |
| 424 | + | } | |
| 425 | + | } | |
| 426 | + | } | |
| 427 | + | ||
| 405 | 428 | // Add the route | |
| 406 | 429 | if err := p.AddRoute(targetDomain, containerID, containerIP, containerPort, protocol, "/"); err != nil { | |
| 407 | 430 | return fmt.Errorf("failed to add proxy route: %w", err) | |
| @@ -411,6 +434,7 @@ func addProxyRoute(a *server.App, containerID, containerIP, containerPort, targe | |||
| 411 | 434 | "domain", targetDomain, | |
| 412 | 435 | "containerIP", containerIP, | |
| 413 | 436 | "containerPort", containerPort, | |
| 437 | + | "protocol", protocol, | |
| 414 | 438 | ) | |
| 415 | 439 | return nil | |
| 416 | 440 | } | |
| @@ -399,9 +399,32 @@ func addProxyRoute(a *server.App, containerID, containerIP, containerPort, targe | |||||
| 399 | return fmt.Errorf("failed to create proxy: %w", err) | 399 | return fmt.Errorf("failed to create proxy: %w", err) | ||
| 400 | } | 400 | } | ||
| 401 | 401 | ||||
| 402 | - | // Extract the protocol (default to http) | 402 | + | // Extract the protocol from the target domain |
| 403 | protocol := "http" | 403 | protocol := "http" | ||
| 404 | 404 | ||||
| 405 | + | // Check if the target domain specifies HTTPS | |||
| 406 | + | if strings.HasPrefix(strings.ToLower(targetDomain), "https://") { | |||
| 407 | + | protocol = "https" | |||
| 408 | + | log.Debug("Using HTTPS protocol for proxy route based on target domain", | |||
| 409 | + | "domain", targetDomain, | |||
| 410 | + | "protocol", protocol) | |||
| 411 | + | } else { | |||
| 412 | + | // If target domain doesn't specify protocol, check the container labels | |||
| 413 | + | containerInfo, err := docker.GetContainerInfo(containerID) | |||
| 414 | + | if err == nil { | |||
| 415 | + | // Check if gordon.proxy.ssl label is set to true | |||
| 416 | + | if sslValue, exists := containerInfo.Config.Labels["gordon.proxy.ssl"]; exists && | |||
| 417 | + | (sslValue == "true" || sslValue == "1" || sslValue == "yes") { | |||
| 418 | + | protocol = "https" | |||
| 419 | + | log.Debug("Using HTTPS protocol for proxy route based on container labels", | |||
| 420 | + | "domain", targetDomain, | |||
| 421 | + | "containerID", containerID, | |||
| 422 | + | "label_value", sslValue, | |||
| 423 | + | "protocol", protocol) | |||
| 424 | + | } | |||
| 425 | + | } | |||
| 426 | + | } | |||
| 427 | + | ||||
| 405 | // Add the route | 428 | // Add the route | ||
| 406 | if err := p.AddRoute(targetDomain, containerID, containerIP, containerPort, protocol, "/"); err != nil { | 429 | if err := p.AddRoute(targetDomain, containerID, containerIP, containerPort, protocol, "/"); err != nil { | ||
| 407 | return fmt.Errorf("failed to add proxy route: %w", err) | 430 | return fmt.Errorf("failed to add proxy route: %w", err) | ||
| @@ -411,6 +434,7 @@ func addProxyRoute(a *server.App, containerID, containerIP, containerPort, targe | |||||
| 411 | "domain", targetDomain, | 434 | "domain", targetDomain, | ||
| 412 | "containerIP", containerIP, | 435 | "containerIP", containerIP, | ||
| 413 | "containerPort", containerPort, | 436 | "containerPort", containerPort, | ||
| 437 | + | "protocol", protocol, | |||
| 414 | ) | 438 | ) | ||
| 415 | return nil | 439 | return nil | ||
| 416 | } | 440 | } | ||
M internal/proxy/proxy.go +156 -3
| @@ -552,6 +552,141 @@ func (p *Proxy) requestAdminCertificate() { | |||
| 552 | 552 | } | |
| 553 | 553 | } | |
| 554 | 554 | ||
| 555 | + | // requestDomainCertificate proactively requests a Let's Encrypt certificate | |
| 556 | + | // for a container domain | |
| 557 | + | func (p *Proxy) requestDomainCertificate(domain string) { | |
| 558 | + | if domain == "" { | |
| 559 | + | log.Warn("Empty domain provided to requestDomainCertificate") | |
| 560 | + | return | |
| 561 | + | } | |
| 562 | + | ||
| 563 | + | // Extract hostname from domain, resolving it to see if it's publicly accessible | |
| 564 | + | ips, err := net.LookupIP(domain) | |
| 565 | + | if err != nil { | |
| 566 | + | log.Error("Could not resolve domain, Let's Encrypt will likely fail", | |
| 567 | + | "domain", domain, | |
| 568 | + | "error", err.Error(), | |
| 569 | + | "solution", "Check DNS settings and ensure domain points to this server") | |
| 570 | + | return | |
| 571 | + | } | |
| 572 | + | ||
| 573 | + | var ipStrings []string | |
| 574 | + | for _, ip := range ips { | |
| 575 | + | ipStrings = append(ipStrings, ip.String()) | |
| 576 | + | } | |
| 577 | + | ||
| 578 | + | log.Info("Successfully resolved domain for certificate request", | |
| 579 | + | "domain", domain, | |
| 580 | + | "ips", ipStrings) | |
| 581 | + | ||
| 582 | + | // Check if we already have a valid certificate in the cache | |
| 583 | + | if p.checkCertificateInCache(domain) { | |
| 584 | + | log.Info("Using existing certificate from cache", | |
| 585 | + | "domain", domain, | |
| 586 | + | "action", "skipping Let's Encrypt request to avoid rate limits") | |
| 587 | + | return | |
| 588 | + | } | |
| 589 | + | ||
| 590 | + | // Check if environment is production | |
| 591 | + | mode := "staging" | |
| 592 | + | email := p.config.Email | |
| 593 | + | ||
| 594 | + | if !p.app.IsDevEnvironment() && email != "" { | |
| 595 | + | mode = "production" | |
| 596 | + | } | |
| 597 | + | ||
| 598 | + | // Log the certificate request intent | |
| 599 | + | log.Info("Initiating Let's Encrypt certificate request for container domain", | |
| 600 | + | "domain", domain, | |
| 601 | + | "email", email, | |
| 602 | + | "mode", mode) | |
| 603 | + | ||
| 604 | + | log.Info("⏳ Waiting for Let's Encrypt to validate domain ownership", | |
| 605 | + | "domain", domain, | |
| 606 | + | "validation_method", "HTTP-01 challenge", | |
| 607 | + | "requirements", "Domain must be publicly accessible on port 80", | |
| 608 | + | "timeout", "1 minute") | |
| 609 | + | ||
| 610 | + | // Create a context with timeout for the initial certificate request | |
| 611 | + | ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute) | |
| 612 | + | defer cancel() | |
| 613 | + | ||
| 614 | + | // Try to obtain the certificate with timeout | |
| 615 | + | var certResult *tls.Certificate | |
| 616 | + | var certErr error | |
| 617 | + | ||
| 618 | + | // Use a channel to handle the timeout | |
| 619 | + | certChan := make(chan struct { | |
| 620 | + | cert *tls.Certificate | |
| 621 | + | err error | |
| 622 | + | }) | |
| 623 | + | ||
| 624 | + | go func() { | |
| 625 | + | c, e := p.certManager.GetCertificate(&tls.ClientHelloInfo{ | |
| 626 | + | ServerName: domain, | |
| 627 | + | }) | |
| 628 | + | certChan <- struct { | |
| 629 | + | cert *tls.Certificate | |
| 630 | + | err error | |
| 631 | + | }{c, e} | |
| 632 | + | }() | |
| 633 | + | ||
| 634 | + | // Wait for either the certificate request to complete or the timeout | |
| 635 | + | select { | |
| 636 | + | case result := <-certChan: | |
| 637 | + | certResult = result.cert | |
| 638 | + | certErr = result.err | |
| 639 | + | case <-ctx.Done(): | |
| 640 | + | certErr = fmt.Errorf("certificate request timed out after 1 minute: %w", ctx.Err()) | |
| 641 | + | log.Error("Let's Encrypt certificate request timed out", | |
| 642 | + | "domain", domain, | |
| 643 | + | "timeout", "1 minute", | |
| 644 | + | "error", certErr) | |
| 645 | + | } | |
| 646 | + | ||
| 647 | + | if certErr != nil { | |
| 648 | + | // Check for rate limit errors | |
| 649 | + | if strings.Contains(strings.ToLower(certErr.Error()), "ratelimited") { | |
| 650 | + | // Extract the retry-after time if available in the error message | |
| 651 | + | retryAfterStr := "" | |
| 652 | + | re := regexp.MustCompile(`retry after ([^:]+)`) | |
| 653 | + | matches := re.FindStringSubmatch(certErr.Error()) | |
| 654 | + | if len(matches) > 1 { | |
| 655 | + | retryAfterStr = matches[1] | |
| 656 | + | } | |
| 657 | + | ||
| 658 | + | log.Error("Let's Encrypt rate limit reached", | |
| 659 | + | "domain", domain, | |
| 660 | + | "error", certErr, | |
| 661 | + | "retry_after", retryAfterStr, | |
| 662 | + | "solution", "Wait until the rate limit expires, then restart the server") | |
| 663 | + | ||
| 664 | + | // Don't retry on rate limits | |
| 665 | + | log.Info("Skipping certificate request retries due to rate limiting", | |
| 666 | + | "domain", domain) | |
| 667 | + | } else { | |
| 668 | + | log.Error("Failed to obtain Let's Encrypt certificate", | |
| 669 | + | "domain", domain, | |
| 670 | + | "error", certErr) | |
| 671 | + | ||
| 672 | + | log.Info("⏳ Retrying certificate request with exponential backoff", | |
| 673 | + | "domain", domain, | |
| 674 | + | "timeout", "2 minutes") | |
| 675 | + | // Implement retry logic with backoff | |
| 676 | + | go p.retryCertificateRequest(domain, 3, 10*time.Second) | |
| 677 | + | } | |
| 678 | + | } else if certResult != nil { | |
| 679 | + | log.Info("Successfully obtained Let's Encrypt certificate", | |
| 680 | + | "domain", domain) | |
| 681 | + | } else { | |
| 682 | + | log.Error("Unexpected error: received nil certificate but no error", | |
| 683 | + | "domain", domain, | |
| 684 | + | "timeout", "1 minute") | |
| 685 | + | // Implement retry logic with backoff | |
| 686 | + | go p.retryCertificateRequest(domain, 3, 10*time.Second) | |
| 687 | + | } | |
| 688 | + | } | |
| 689 | + | ||
| 555 | 690 | // retryCertificateRequest attempts to request a certificate with exponential backoff | |
| 556 | 691 | func (p *Proxy) retryCertificateRequest(domain string, maxRetries int, initialBackoff time.Duration) { | |
| 557 | 692 | backoff := initialBackoff | |
| @@ -1197,9 +1332,18 @@ func (p *Proxy) AddRoute(domainName, containerID, containerIP, containerPort, pr | |||
| 1197 | 1332 | } | |
| 1198 | 1333 | defer tx.Rollback() | |
| 1199 | 1334 | ||
| 1335 | + | // Extract the hostname from domainName if it contains a protocol | |
| 1336 | + | hostname := domainName | |
| 1337 | + | if strings.Contains(hostname, "://") { | |
| 1338 | + | parsedURL, err := url.Parse(hostname) | |
| 1339 | + | if err == nil && parsedURL.Host != "" { | |
| 1340 | + | hostname = parsedURL.Host | |
| 1341 | + | } | |
| 1342 | + | } | |
| 1343 | + | ||
| 1200 | 1344 | // Check if the domain exists | |
| 1201 | 1345 | var domainID string | |
| 1202 | - | err = tx.QueryRow("SELECT id FROM domain WHERE name = ?", domainName).Scan(&domainID) | |
| 1346 | + | err = tx.QueryRow("SELECT id FROM domain WHERE name = ?", hostname).Scan(&domainID) | |
| 1203 | 1347 | if err != nil { | |
| 1204 | 1348 | if err == sql.ErrNoRows { | |
| 1205 | 1349 | // Domain doesn't exist, create it | |
| @@ -1207,7 +1351,7 @@ func (p *Proxy) AddRoute(domainName, containerID, containerIP, containerPort, pr | |||
| 1207 | 1351 | now := time.Now().Format(time.RFC3339) | |
| 1208 | 1352 | _, err = tx.Exec( | |
| 1209 | 1353 | "INSERT INTO domain (id, name, created_at, updated_at) VALUES (?, ?, ?, ?)", | |
| 1210 | - | domainID, domainName, now, now, | |
| 1354 | + | domainID, hostname, now, now, | |
| 1211 | 1355 | ) | |
| 1212 | 1356 | if err != nil { | |
| 1213 | 1357 | return fmt.Errorf("failed to insert domain: %w", err) | |
| @@ -1268,10 +1412,19 @@ func (p *Proxy) AddRoute(domainName, containerID, containerIP, containerPort, pr | |||
| 1268 | 1412 | } | |
| 1269 | 1413 | ||
| 1270 | 1414 | log.Info("Added proxy route", | |
| 1271 | - | "domain", domainName, | |
| 1415 | + | "domain", hostname, | |
| 1272 | 1416 | "containerIP", containerIP, | |
| 1273 | 1417 | "containerPort", containerPort, | |
| 1274 | 1418 | ) | |
| 1419 | + | ||
| 1420 | + | // Request a certificate if this is an HTTPS route | |
| 1421 | + | if strings.ToLower(protocol) == "https" { | |
| 1422 | + | log.Info("Requesting Let's Encrypt certificate for new HTTPS route", | |
| 1423 | + | "domain", hostname) | |
| 1424 | + | // Run in a goroutine to avoid blocking | |
| 1425 | + | go p.requestDomainCertificate(hostname) | |
| 1426 | + | } | |
| 1427 | + | ||
| 1275 | 1428 | return nil | |
| 1276 | 1429 | } | |
| 1277 | 1430 | ||
| @@ -552,6 +552,141 @@ func (p *Proxy) requestAdminCertificate() { | |||||
| 552 | } | 552 | } | ||
| 553 | } | 553 | } | ||
| 554 | 554 | ||||
| 555 | + | // requestDomainCertificate proactively requests a Let's Encrypt certificate | |||
| 556 | + | // for a container domain | |||
| 557 | + | func (p *Proxy) requestDomainCertificate(domain string) { | |||
| 558 | + | if domain == "" { | |||
| 559 | + | log.Warn("Empty domain provided to requestDomainCertificate") | |||
| 560 | + | return | |||
| 561 | + | } | |||
| 562 | + | ||||
| 563 | + | // Extract hostname from domain, resolving it to see if it's publicly accessible | |||
| 564 | + | ips, err := net.LookupIP(domain) | |||
| 565 | + | if err != nil { | |||
| 566 | + | log.Error("Could not resolve domain, Let's Encrypt will likely fail", | |||
| 567 | + | "domain", domain, | |||
| 568 | + | "error", err.Error(), | |||
| 569 | + | "solution", "Check DNS settings and ensure domain points to this server") | |||
| 570 | + | return | |||
| 571 | + | } | |||
| 572 | + | ||||
| 573 | + | var ipStrings []string | |||
| 574 | + | for _, ip := range ips { | |||
| 575 | + | ipStrings = append(ipStrings, ip.String()) | |||
| 576 | + | } | |||
| 577 | + | ||||
| 578 | + | log.Info("Successfully resolved domain for certificate request", | |||
| 579 | + | "domain", domain, | |||
| 580 | + | "ips", ipStrings) | |||
| 581 | + | ||||
| 582 | + | // Check if we already have a valid certificate in the cache | |||
| 583 | + | if p.checkCertificateInCache(domain) { | |||
| 584 | + | log.Info("Using existing certificate from cache", | |||
| 585 | + | "domain", domain, | |||
| 586 | + | "action", "skipping Let's Encrypt request to avoid rate limits") | |||
| 587 | + | return | |||
| 588 | + | } | |||
| 589 | + | ||||
| 590 | + | // Check if environment is production | |||
| 591 | + | mode := "staging" | |||
| 592 | + | email := p.config.Email | |||
| 593 | + | ||||
| 594 | + | if !p.app.IsDevEnvironment() && email != "" { | |||
| 595 | + | mode = "production" | |||
| 596 | + | } | |||
| 597 | + | ||||
| 598 | + | // Log the certificate request intent | |||
| 599 | + | log.Info("Initiating Let's Encrypt certificate request for container domain", | |||
| 600 | + | "domain", domain, | |||
| 601 | + | "email", email, | |||
| 602 | + | "mode", mode) | |||
| 603 | + | ||||
| 604 | + | log.Info("⏳ Waiting for Let's Encrypt to validate domain ownership", | |||
| 605 | + | "domain", domain, | |||
| 606 | + | "validation_method", "HTTP-01 challenge", | |||
| 607 | + | "requirements", "Domain must be publicly accessible on port 80", | |||
| 608 | + | "timeout", "1 minute") | |||
| 609 | + | ||||
| 610 | + | // Create a context with timeout for the initial certificate request | |||
| 611 | + | ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute) | |||
| 612 | + | defer cancel() | |||
| 613 | + | ||||
| 614 | + | // Try to obtain the certificate with timeout | |||
| 615 | + | var certResult *tls.Certificate | |||
| 616 | + | var certErr error | |||
| 617 | + | ||||
| 618 | + | // Use a channel to handle the timeout | |||
| 619 | + | certChan := make(chan struct { | |||
| 620 | + | cert *tls.Certificate | |||
| 621 | + | err error | |||
| 622 | + | }) | |||
| 623 | + | ||||
| 624 | + | go func() { | |||
| 625 | + | c, e := p.certManager.GetCertificate(&tls.ClientHelloInfo{ | |||
| 626 | + | ServerName: domain, | |||
| 627 | + | }) | |||
| 628 | + | certChan <- struct { | |||
| 629 | + | cert *tls.Certificate | |||
| 630 | + | err error | |||
| 631 | + | }{c, e} | |||
| 632 | + | }() | |||
| 633 | + | ||||
| 634 | + | // Wait for either the certificate request to complete or the timeout | |||
| 635 | + | select { | |||
| 636 | + | case result := <-certChan: | |||
| 637 | + | certResult = result.cert | |||
| 638 | + | certErr = result.err | |||
| 639 | + | case <-ctx.Done(): | |||
| 640 | + | certErr = fmt.Errorf("certificate request timed out after 1 minute: %w", ctx.Err()) | |||
| 641 | + | log.Error("Let's Encrypt certificate request timed out", | |||
| 642 | + | "domain", domain, | |||
| 643 | + | "timeout", "1 minute", | |||
| 644 | + | "error", certErr) | |||
| 645 | + | } | |||
| 646 | + | ||||
| 647 | + | if certErr != nil { | |||
| 648 | + | // Check for rate limit errors | |||
| 649 | + | if strings.Contains(strings.ToLower(certErr.Error()), "ratelimited") { | |||
| 650 | + | // Extract the retry-after time if available in the error message | |||
| 651 | + | retryAfterStr := "" | |||
| 652 | + | re := regexp.MustCompile(`retry after ([^:]+)`) | |||
| 653 | + | matches := re.FindStringSubmatch(certErr.Error()) | |||
| 654 | + | if len(matches) > 1 { | |||
| 655 | + | retryAfterStr = matches[1] | |||
| 656 | + | } | |||
| 657 | + | ||||
| 658 | + | log.Error("Let's Encrypt rate limit reached", | |||
| 659 | + | "domain", domain, | |||
| 660 | + | "error", certErr, | |||
| 661 | + | "retry_after", retryAfterStr, | |||
| 662 | + | "solution", "Wait until the rate limit expires, then restart the server") | |||
| 663 | + | ||||
| 664 | + | // Don't retry on rate limits | |||
| 665 | + | log.Info("Skipping certificate request retries due to rate limiting", | |||
| 666 | + | "domain", domain) | |||
| 667 | + | } else { | |||
| 668 | + | log.Error("Failed to obtain Let's Encrypt certificate", | |||
| 669 | + | "domain", domain, | |||
| 670 | + | "error", certErr) | |||
| 671 | + | ||||
| 672 | + | log.Info("⏳ Retrying certificate request with exponential backoff", | |||
| 673 | + | "domain", domain, | |||
| 674 | + | "timeout", "2 minutes") | |||
| 675 | + | // Implement retry logic with backoff | |||
| 676 | + | go p.retryCertificateRequest(domain, 3, 10*time.Second) | |||
| 677 | + | } | |||
| 678 | + | } else if certResult != nil { | |||
| 679 | + | log.Info("Successfully obtained Let's Encrypt certificate", | |||
| 680 | + | "domain", domain) | |||
| 681 | + | } else { | |||
| 682 | + | log.Error("Unexpected error: received nil certificate but no error", | |||
| 683 | + | "domain", domain, | |||
| 684 | + | "timeout", "1 minute") | |||
| 685 | + | // Implement retry logic with backoff | |||
| 686 | + | go p.retryCertificateRequest(domain, 3, 10*time.Second) | |||
| 687 | + | } | |||
| 688 | + | } | |||
| 689 | + | ||||
| 555 | // retryCertificateRequest attempts to request a certificate with exponential backoff | 690 | // retryCertificateRequest attempts to request a certificate with exponential backoff | ||
| 556 | func (p *Proxy) retryCertificateRequest(domain string, maxRetries int, initialBackoff time.Duration) { | 691 | func (p *Proxy) retryCertificateRequest(domain string, maxRetries int, initialBackoff time.Duration) { | ||
| 557 | backoff := initialBackoff | 692 | backoff := initialBackoff | ||
| @@ -1197,9 +1332,18 @@ func (p *Proxy) AddRoute(domainName, containerID, containerIP, containerPort, pr | |||||
| 1197 | } | 1332 | } | ||
| 1198 | defer tx.Rollback() | 1333 | defer tx.Rollback() | ||
| 1199 | 1334 | ||||
| 1335 | + | // Extract the hostname from domainName if it contains a protocol | |||
| 1336 | + | hostname := domainName | |||
| 1337 | + | if strings.Contains(hostname, "://") { | |||
| 1338 | + | parsedURL, err := url.Parse(hostname) | |||
| 1339 | + | if err == nil && parsedURL.Host != "" { | |||
| 1340 | + | hostname = parsedURL.Host | |||
| 1341 | + | } | |||
| 1342 | + | } | |||
| 1343 | + | ||||
| 1200 | // Check if the domain exists | 1344 | // Check if the domain exists | ||
| 1201 | var domainID string | 1345 | var domainID string | ||
| 1202 | - | err = tx.QueryRow("SELECT id FROM domain WHERE name = ?", domainName).Scan(&domainID) | 1346 | + | err = tx.QueryRow("SELECT id FROM domain WHERE name = ?", hostname).Scan(&domainID) |
| 1203 | if err != nil { | 1347 | if err != nil { | ||
| 1204 | if err == sql.ErrNoRows { | 1348 | if err == sql.ErrNoRows { | ||
| 1205 | // Domain doesn't exist, create it | 1349 | // Domain doesn't exist, create it | ||
| @@ -1207,7 +1351,7 @@ func (p *Proxy) AddRoute(domainName, containerID, containerIP, containerPort, pr | |||||
| 1207 | now := time.Now().Format(time.RFC3339) | 1351 | now := time.Now().Format(time.RFC3339) | ||
| 1208 | _, err = tx.Exec( | 1352 | _, err = tx.Exec( | ||
| 1209 | "INSERT INTO domain (id, name, created_at, updated_at) VALUES (?, ?, ?, ?)", | 1353 | "INSERT INTO domain (id, name, created_at, updated_at) VALUES (?, ?, ?, ?)", | ||
| 1210 | - | domainID, domainName, now, now, | 1354 | + | domainID, hostname, now, now, |
| 1211 | ) | 1355 | ) | ||
| 1212 | if err != nil { | 1356 | if err != nil { | ||
| 1213 | return fmt.Errorf("failed to insert domain: %w", err) | 1357 | return fmt.Errorf("failed to insert domain: %w", err) | ||
| @@ -1268,10 +1412,19 @@ func (p *Proxy) AddRoute(domainName, containerID, containerIP, containerPort, pr | |||||
| 1268 | } | 1412 | } | ||
| 1269 | 1413 | ||||
| 1270 | log.Info("Added proxy route", | 1414 | log.Info("Added proxy route", | ||
| 1271 | - | "domain", domainName, | 1415 | + | "domain", hostname, |
| 1272 | "containerIP", containerIP, | 1416 | "containerIP", containerIP, | ||
| 1273 | "containerPort", containerPort, | 1417 | "containerPort", containerPort, | ||
| 1274 | ) | 1418 | ) | ||
| 1419 | + | ||||
| 1420 | + | // Request a certificate if this is an HTTPS route | |||
| 1421 | + | if strings.ToLower(protocol) == "https" { | |||
| 1422 | + | log.Info("Requesting Let's Encrypt certificate for new HTTPS route", | |||
| 1423 | + | "domain", hostname) | |||
| 1424 | + | // Run in a goroutine to avoid blocking | |||
| 1425 | + | go p.requestDomainCertificate(hostname) | |||
| 1426 | + | } | |||
| 1427 | + | ||||
| 1275 | return nil | 1428 | return nil | ||
| 1276 | } | 1429 | } | ||
| 1277 | 1430 | ||||