From c08b7c1f630669d9a7be30a94b74621fb052bdc7 Mon Sep 17 00:00:00 2001 From: pavel Date: Thu, 14 May 2026 22:59:25 +0200 Subject: [PATCH] fix again --- internal/app/integrations.go | 145 ++++++++++++++++++++++++++++++++ internal/app/project_service.go | 6 ++ 2 files changed, 151 insertions(+) diff --git a/internal/app/integrations.go b/internal/app/integrations.go index 79bd4c1..8adb56d 100644 --- a/internal/app/integrations.go +++ b/internal/app/integrations.go @@ -101,6 +101,151 @@ func (a *App) fetchCaddyConfig(ctx context.Context) (map[string]any, error) { return out, nil } +func (a *App) ensureCaddyTLSSubject(ctx context.Context, host string) error { + host = strings.TrimSpace(host) + if host == "" { + return nil + } + cfg, err := a.fetchCaddyConfig(ctx) + if err != nil { + return err + } + + idx, subjects, found, err := findTLSPolicyForHost(cfg, host) + if err != nil { + return err + } + if found { + return nil + } + if idx < 0 { + return errors.New("no suitable Caddy TLS automation policy found") + } + + // Preferred: append one subject. + appendURL := fmt.Sprintf("%s/config/apps/tls/automation/policies/%d/subjects", a.cfg.CaddyAdminURL, idx) + body, _ := json.Marshal(host) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, appendURL, strings.NewReader(string(body))) + if err != nil { + return err + } + req.Header.Set("Content-Type", "application/json") + resp, err := http.DefaultClient.Do(req) + if err == nil { + defer resp.Body.Close() + if resp.StatusCode < 300 { + return nil + } + } + + // Fallback: replace the subjects array with host appended. + subjects = append(subjects, host) + replaceBody, _ := json.Marshal(subjects) + putReq, err := http.NewRequestWithContext(ctx, http.MethodPut, appendURL, strings.NewReader(string(replaceBody))) + if err != nil { + return err + } + putReq.Header.Set("Content-Type", "application/json") + putResp, err := http.DefaultClient.Do(putReq) + if err != nil { + return fmt.Errorf("caddy tls subjects update failed: %w", err) + } + defer putResp.Body.Close() + if putResp.StatusCode >= 300 { + b, _ := io.ReadAll(putResp.Body) + return fmt.Errorf("caddy tls subjects update error: status=%d body=%s", putResp.StatusCode, string(b)) + } + return nil +} + +func findTLSPolicyForHost(cfg map[string]any, host string) (policyIdx int, subjects []string, alreadyPresent bool, err error) { + policyIdx = -1 + apps, ok := cfg["apps"].(map[string]any) + if !ok { + return -1, nil, false, errors.New("caddy config missing apps") + } + tlsObj, ok := apps["tls"].(map[string]any) + if !ok { + return -1, nil, false, errors.New("caddy config missing apps.tls") + } + automation, ok := tlsObj["automation"].(map[string]any) + if !ok { + return -1, nil, false, errors.New("caddy config missing apps.tls.automation") + } + policiesAny, ok := automation["policies"].([]any) + if !ok || len(policiesAny) == 0 { + return -1, nil, false, errors.New("caddy config missing tls automation policies") + } + + // Prefer a non-internal policy with subjects list. + for i, pAny := range policiesAny { + pol, ok := pAny.(map[string]any) + if !ok { + continue + } + subjectsRaw, ok := pol["subjects"].([]any) + if !ok { + continue + } + cur := make([]string, 0, len(subjectsRaw)) + for _, s := range subjectsRaw { + if sv, ok := s.(string); ok { + cur = append(cur, sv) + if strings.EqualFold(strings.TrimSpace(sv), host) { + return i, cur, true, nil + } + } + } + if !policyHasInternalIssuer(pol) && policyIdx < 0 { + policyIdx = i + subjects = cur + } + } + // Fallback to first policy that has subjects. + if policyIdx < 0 { + for i, pAny := range policiesAny { + pol, ok := pAny.(map[string]any) + if !ok { + continue + } + subjectsRaw, ok := pol["subjects"].([]any) + if !ok { + continue + } + cur := make([]string, 0, len(subjectsRaw)) + for _, s := range subjectsRaw { + if sv, ok := s.(string); ok { + cur = append(cur, sv) + if strings.EqualFold(strings.TrimSpace(sv), host) { + return i, cur, true, nil + } + } + } + policyIdx = i + subjects = cur + break + } + } + return policyIdx, subjects, false, nil +} + +func policyHasInternalIssuer(pol map[string]any) bool { + issuersAny, ok := pol["issuers"].([]any) + if !ok { + return false + } + for _, iAny := range issuersAny { + iss, ok := iAny.(map[string]any) + if !ok { + continue + } + if mod, ok := iss["module"].(string); ok && strings.EqualFold(strings.TrimSpace(mod), "internal") { + return true + } + } + return false +} + func (a *App) ensureForgejoDeployWebhook(ctx context.Context, p Project) error { if a.cfg.ForgejoBaseURL == "" || a.cfg.ForgejoToken == "" || p.RepoURL == "" { return nil diff --git a/internal/app/project_service.go b/internal/app/project_service.go index cf115c1..36e4a44 100644 --- a/internal/app/project_service.go +++ b/internal/app/project_service.go @@ -132,6 +132,12 @@ func (a *App) provisionProject(ctx context.Context, p *Project) error { p.ProvisionError = err.Error() return err } + if err := a.ensureCaddyTLSSubject(ctx, p.RouteHost); err != nil { + _ = a.updateProvisioningState(ctx, p.ID, "failed", err.Error()) + p.ProvisionState = "failed" + p.ProvisionError = err.Error() + return err + } if err := a.updateProvisioningState(ctx, p.ID, "provisioned", ""); err != nil { p.ProvisionState = "failed" p.ProvisionError = err.Error()