package rotator import ( "bytes" "context" "crypto/tls" "crypto/x509" "encoding/json" "errors" "fmt" "io" "net/http" "os" "strings" "time" ) func tlsClient(certificatePaths []string, serverName string) (*http.Client, error) { pool, err := certificatePool(certificatePaths) if err != nil { return nil, err } transport := http.DefaultTransport.(*http.Transport).Clone() transport.TLSClientConfig = &tls.Config{ MinVersion: tls.VersionTLS12, RootCAs: pool, ServerName: serverName, } return &http.Client{Transport: transport, Timeout: 20 * time.Second}, nil } func certificatePool(certificatePaths []string) (*x509.CertPool, error) { pool := x509.NewCertPool() for _, path := range certificatePaths { certificate, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("read CA certificate %s: %w", path, err) } if !pool.AppendCertsFromPEM(certificate) { return nil, fmt.Errorf("parse CA certificate %s", path) } } return pool, nil } func jsonRequest(ctx context.Context, client *http.Client, method, endpoint string, body any, headers map[string]string, output any) (*http.Response, error) { var payload io.Reader if body != nil { encoded, err := json.Marshal(body) if err != nil { return nil, fmt.Errorf("encode request: %w", err) } payload = bytes.NewReader(encoded) } request, err := http.NewRequestWithContext(ctx, method, endpoint, payload) if err != nil { return nil, fmt.Errorf("create request: %w", err) } if body != nil { request.Header.Set("Content-Type", "application/json") } for name, value := range headers { request.Header.Set(name, value) } response, err := client.Do(request) if err != nil { return nil, err } defer func() { _ = response.Body.Close() }() limited := io.LimitReader(response.Body, 1<<20) if response.StatusCode < 200 || response.StatusCode >= 300 { _, _ = io.Copy(io.Discard, limited) return response, fmt.Errorf("%s returned HTTP %d", request.URL.Path, response.StatusCode) } if output == nil { _, err = io.Copy(io.Discard, limited) return response, err } decoder := json.NewDecoder(limited) if err := decoder.Decode(output); err != nil { return response, fmt.Errorf("decode %s response: %w", request.URL.Path, err) } return response, nil } func readTrimmed(path string) (string, error) { value, err := os.ReadFile(path) if err != nil { return "", err } trimmed := strings.TrimSpace(string(value)) if trimmed == "" { return "", errors.New("file is empty") } return trimmed, nil }