Compare commits
13 Commits
efa92a73bd
...
custom-dom
Author | SHA1 | Date | |
---|---|---|---|
d2c564a000 | |||
|
6bc0071bdb | ||
|
a044cb86bd | ||
|
6ecc5d7784 | ||
|
ef0970044b | ||
|
38fe915b37 | ||
|
c9c1e81619 | ||
|
92e3f6baac | ||
|
0ef1a18331 | ||
|
849dacdc30 | ||
|
20607b54d5 | ||
|
cbfa97d480 | ||
|
dc95d8d3bb |
42
README.md
42
README.md
@@ -1,6 +1,6 @@
|
|||||||
# Cheese
|
# Cheese
|
||||||
|
|
||||||
A Certificate Transparency log configuration and deployment tool.
|
A Certificate Transparency log configuration and deployment tool for Google's [[TesseraCT](github.com/transparency-dev/tesseract)] implementation. It tries to look and feel a little like the one provided by [[Sunlight](https://github.com/FiloSottile/sunlight)].
|
||||||
|
|
||||||
## Configuration Generator
|
## Configuration Generator
|
||||||
|
|
||||||
@@ -9,14 +9,20 @@ in a very similar way to Sunlight.
|
|||||||
|
|
||||||
### Usage
|
### Usage
|
||||||
|
|
||||||
1. **Create YAML configuration file:**
|
1. **Build the tool:**
|
||||||
|
```bash
|
||||||
|
go build -o tesseract-genconf ./tesseract/genconf/
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Create YAML configuration file:**
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
listen:
|
listen:
|
||||||
- "[::]:16420"
|
- "[::]:8080"
|
||||||
roots: /etc/tesseract/roots.pem
|
roots: /etc/tesseract/roots.pem
|
||||||
logs:
|
logs:
|
||||||
- shortname: example2025h1
|
- shortname: example2025h1
|
||||||
|
listen: "[::]:16900"
|
||||||
inception: 2025-01-01
|
inception: 2025-01-01
|
||||||
submissionprefix: https://example2025h1.log.ct.example.com
|
submissionprefix: https://example2025h1.log.ct.example.com
|
||||||
monitoringprefix: https://example2025h1.mon.ct.example.com
|
monitoringprefix: https://example2025h1.mon.ct.example.com
|
||||||
@@ -27,20 +33,36 @@ logs:
|
|||||||
notafterlimit: 2025-07-01T00:00:00Z
|
notafterlimit: 2025-07-01T00:00:00Z
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Generate private keys:**
|
3. **Generate private keys:**
|
||||||
```bash
|
```bash
|
||||||
go run ./tesseract/genconf/main.go -c config.yaml gen-key
|
mkdir -p /etc/tesseract/keys
|
||||||
|
./tesseract-genconf -c config.yaml gen-key
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **Create directories and generate environment files:**
|
4. **Create directories and generate environment files:**
|
||||||
```bash
|
```bash
|
||||||
mkdir -p /var/lib/tesseract/example2025h1/data
|
mkdir -p /var/lib/tesseract/example2025h1/data
|
||||||
go run ./tesseract/genconf/main.go -c config.yaml gen-env
|
./tesseract-genconf -c config.yaml gen-env
|
||||||
```
|
```
|
||||||
|
|
||||||
4. **Generate HTML and JSON files:**
|
5. **Generate HTML and JSON files:**
|
||||||
```bash
|
```bash
|
||||||
go run ./tesseract/genconf/main.go -c config.yaml gen-html
|
./tesseract-genconf -c config.yaml gen-html
|
||||||
```
|
```
|
||||||
|
|
||||||
This generates `index.html`, `log.v3.json`, `.env`, and `roots.pem` files in each log's directory.
|
6. **Generate nginx configuration files:**
|
||||||
|
```bash
|
||||||
|
./tesseract-genconf -c config.yaml gen-nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
The port from the main `listen:` field will be used in the NGINX server blocks (in our case
|
||||||
|
`:8080`). You can symlink the generated $monitoringprefix.conf files from `/etc/nginx/sites-enabled/`.
|
||||||
|
|
||||||
|
7. **Generate root certificates (optional):**
|
||||||
|
```bash
|
||||||
|
# For testing/staging environment, take the ccadb 'testing' roots
|
||||||
|
./tesseract-genconf gen-roots --source https://rennet2027h2.log.ct.ipng.ch/ --output roots-staging.pem
|
||||||
|
|
||||||
|
# For production environment, take the ccadb 'production' roots
|
||||||
|
./tesseract-genconf gen-roots --source https://gouda2027h2.log.ct.ipng.ch/ --output roots-production.pem
|
||||||
|
```
|
||||||
|
6380
production-roots.pem
Normal file
6380
production-roots.pem
Normal file
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,6 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -25,16 +24,15 @@ func generateEnv(yamlFile string) {
|
|||||||
|
|
||||||
// Create combined roots.pem file
|
// Create combined roots.pem file
|
||||||
rootsPemPath := filepath.Join(logEntry.LocalDirectory, "roots.pem")
|
rootsPemPath := filepath.Join(logEntry.LocalDirectory, "roots.pem")
|
||||||
err := createCombinedRootsPem(config.Roots, logEntry.ExtraRoots, rootsPemPath)
|
err := createCombinedRootsPemWithStatus(config.Roots, logEntry.ExtraRoots, rootsPemPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to create %s: %v", rootsPemPath, err)
|
log.Fatalf("Failed to create %s: %v", rootsPemPath, err)
|
||||||
}
|
}
|
||||||
fmt.Printf("Generated %s\n", rootsPemPath)
|
|
||||||
|
|
||||||
// Build TESSERACT_ARGS string
|
// Build TESSERACT_ARGS string
|
||||||
args := []string{
|
args := []string{
|
||||||
fmt.Sprintf("--private_key=%s", logEntry.Secret),
|
fmt.Sprintf("--private_key=%s", logEntry.Secret),
|
||||||
fmt.Sprintf("--origin=%s.log.ct.ipng.ch", logEntry.ShortName),
|
fmt.Sprintf("--origin=%s.%s", logEntry.ShortName, logEntry.Domain),
|
||||||
fmt.Sprintf("--storage_dir=%s", logEntry.LocalDirectory),
|
fmt.Sprintf("--storage_dir=%s", logEntry.LocalDirectory),
|
||||||
fmt.Sprintf("--roots_pem_file=%s", rootsPemPath),
|
fmt.Sprintf("--roots_pem_file=%s", rootsPemPath),
|
||||||
}
|
}
|
||||||
@@ -44,53 +42,43 @@ func generateEnv(yamlFile string) {
|
|||||||
args = append(args, fmt.Sprintf("--http_endpoint=%s", logEntry.Listen))
|
args = append(args, fmt.Sprintf("--http_endpoint=%s", logEntry.Listen))
|
||||||
}
|
}
|
||||||
|
|
||||||
tesseractArgs := strings.Join(args, " ")
|
// Add not_after flags if specified
|
||||||
envContent := fmt.Sprintf("TESSERACT_ARGS=\"%s\"\n", tesseractArgs)
|
if !logEntry.NotAfterStart.IsZero() {
|
||||||
|
args = append(args, fmt.Sprintf("--not_after_start=%s", logEntry.NotAfterStart.Format("2006-01-02T15:04:05Z")))
|
||||||
|
}
|
||||||
|
if !logEntry.NotAfterLimit.IsZero() {
|
||||||
|
args = append(args, fmt.Sprintf("--not_after_limit=%s", logEntry.NotAfterLimit.Format("2006-01-02T15:04:05Z")))
|
||||||
|
}
|
||||||
|
|
||||||
err = os.WriteFile(envPath, []byte(envContent), 0644)
|
tesseractArgs := strings.Join(args, " ")
|
||||||
|
envContent := fmt.Sprintf("TESSERACT_ARGS=\"%s\"\nOTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318\n", tesseractArgs)
|
||||||
|
|
||||||
|
err = writeFileWithStatus(envPath, []byte(envContent))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to write %s: %v", envPath, err)
|
log.Fatalf("Failed to write %s: %v", envPath, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Generated %s\n", envPath)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func createCombinedRootsPem(rootsFile, extraRootsFile, outputPath string) error {
|
func createCombinedRootsPemWithStatus(rootsFile, extraRootsFile, outputPath string) error {
|
||||||
// Create output file
|
// Read main roots file
|
||||||
outputFile, err := os.Create(outputPath)
|
var combinedContent []byte
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to create output file: %v", err)
|
|
||||||
}
|
|
||||||
defer outputFile.Close()
|
|
||||||
|
|
||||||
// Copy main roots file
|
|
||||||
if rootsFile != "" {
|
if rootsFile != "" {
|
||||||
rootsData, err := os.Open(rootsFile)
|
rootsData, err := os.ReadFile(rootsFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to open roots file %s: %v", rootsFile, err)
|
return fmt.Errorf("failed to read roots file %s: %v", rootsFile, err)
|
||||||
}
|
|
||||||
defer rootsData.Close()
|
|
||||||
|
|
||||||
_, err = io.Copy(outputFile, rootsData)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to copy roots file: %v", err)
|
|
||||||
}
|
}
|
||||||
|
combinedContent = append(combinedContent, rootsData...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append extra roots file if it exists
|
// Append extra roots file if it exists
|
||||||
if extraRootsFile != "" {
|
if extraRootsFile != "" {
|
||||||
extraRootsData, err := os.Open(extraRootsFile)
|
extraRootsData, err := os.ReadFile(extraRootsFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to open extra roots file %s: %v", extraRootsFile, err)
|
return fmt.Errorf("failed to read extra roots file %s: %v", extraRootsFile, err)
|
||||||
}
|
|
||||||
defer extraRootsData.Close()
|
|
||||||
|
|
||||||
_, err = io.Copy(outputFile, extraRootsData)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to copy extra roots file: %v", err)
|
|
||||||
}
|
}
|
||||||
|
combinedContent = append(combinedContent, extraRootsData...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return writeFileWithStatus(outputPath, combinedContent)
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
@@ -58,25 +59,28 @@ const htmlTemplate = `<!DOCTYPE html>
|
|||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img alt="A TesseraCT logo, a stylized four-dimensional hypercube with on the left side penstrokes of orange with a padlock, and on the right penstrokes of dark blue with a magnifying glass." width="250" height="278" src="https://ipng.ch/assets/ctlog/tesseract-logo.png"> </p>
|
<img alt="A TesseraCT logo, a stylized four-dimensional hypercube with on the left side penstrokes of orange with a padlock, and on the right penstrokes of dark blue with a magnifying glass." width="260" height="250" src="https://ipng.ch/assets/ctlog/tesseract-logo.png"> </p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
This is a <a href="https://github.com/transparency-dev/tesseract">TesseraCT</a> Certificate Transparency log instance.
|
This is a <a href="https://github.com/transparency-dev/tesseract">TesseraCT</a> Certificate Transparency log instance.
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<a href="/metrics">Metrics</a> are available.
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
The following logs are active.
|
The following logs are active:
|
||||||
|
|
||||||
{{range .Logs}}
|
{{range .Logs}}
|
||||||
|
|
||||||
<h2>{{.ShortName}}.log.ct.ipng.ch</h2>
|
<h2>{{.ShortName}}.{{.Domain}}</h2>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Log ID: <code>{{.LogID}}</code><br>
|
Log ID: <code>{{.LogID}}</code><br>
|
||||||
Monitoring prefix: <code>{{.MonitoringPrefix}}/</code><br>
|
Monitoring prefix: <code>{{.MonitoringPrefix}}/</code><br>
|
||||||
Submission prefix: <code>{{.SubmissionPrefix}}/</code><br>
|
Submission prefix: <code>{{.SubmissionPrefix}}/</code><br>
|
||||||
Interval: {{.NotAfterStart.Format "2006-01-02T15:04:05Z"}} – {{.NotAfterLimit.Format "2006-01-02T15:04:05Z"}}<br>
|
Interval: {{.NotAfterStart.Format "2006-01-02T15:04:05Z"}} - {{.NotAfterLimit.Format "2006-01-02T15:04:05Z"}}<br>
|
||||||
Links: <a href="{{.MonitoringPrefix}}/checkpoint">checkpoint</a>
|
Links: <a href="{{.MonitoringPrefix}}/checkpoint">checkpoint</a>
|
||||||
<a href="data:application/octet-stream;base64,{{.PublicKeyDERB64}}"
|
<a href="data:application/octet-stream;base64,{{.PublicKeyDERB64}}"
|
||||||
download="{{.ShortName}}.der">key</a>
|
download="{{.ShortName}}.der">key</a>
|
||||||
@@ -133,27 +137,25 @@ func generateHTML(yamlFile string) {
|
|||||||
for _, logEntry := range config.Logs {
|
for _, logEntry := range config.Logs {
|
||||||
indexPath := fmt.Sprintf("%s/index.html", logEntry.LocalDirectory)
|
indexPath := fmt.Sprintf("%s/index.html", logEntry.LocalDirectory)
|
||||||
|
|
||||||
file, err := os.Create(indexPath)
|
// Execute template to buffer
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := tmpl.Execute(&buf, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to create %s: %v", indexPath, err)
|
log.Fatalf("Failed to execute HTML template for %s: %v", indexPath, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = tmpl.Execute(file, config)
|
// Write file with status
|
||||||
|
err = writeFileWithStatus(indexPath, buf.Bytes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
file.Close()
|
|
||||||
log.Fatalf("Failed to write HTML to %s: %v", indexPath, err)
|
log.Fatalf("Failed to write HTML to %s: %v", indexPath, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
file.Close()
|
|
||||||
fmt.Printf("Generated %s\n", indexPath)
|
|
||||||
|
|
||||||
// Generate log.v3.json for this log
|
// Generate log.v3.json for this log
|
||||||
jsonPath := filepath.Join(logEntry.LocalDirectory, "log.v3.json")
|
jsonPath := filepath.Join(logEntry.LocalDirectory, "log.v3.json")
|
||||||
err = generateLogJSON(logEntry, jsonPath)
|
err = generateLogJSONWithStatus(logEntry, jsonPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to generate %s: %v", jsonPath, err)
|
log.Fatalf("Failed to generate %s: %v", jsonPath, err)
|
||||||
}
|
}
|
||||||
fmt.Printf("Generated %s\n", jsonPath)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,9 +209,9 @@ func computeKeyInfo(logEntry *Log) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateLogJSON(logEntry Log, outputPath string) error {
|
func generateLogJSONWithStatus(logEntry Log, outputPath string) error {
|
||||||
logJSON := LogV3JSON{
|
logJSON := LogV3JSON{
|
||||||
Description: fmt.Sprintf("%s.log.ct.ipng.ch", logEntry.ShortName),
|
Description: fmt.Sprintf("%s.%s", logEntry.ShortName, logEntry.Domain),
|
||||||
SubmissionURL: fmt.Sprintf("%s/", logEntry.SubmissionPrefix),
|
SubmissionURL: fmt.Sprintf("%s/", logEntry.SubmissionPrefix),
|
||||||
MonitoringURL: fmt.Sprintf("%s/", logEntry.MonitoringPrefix),
|
MonitoringURL: fmt.Sprintf("%s/", logEntry.MonitoringPrefix),
|
||||||
TemporalInterval: TemporalInterval{
|
TemporalInterval: TemporalInterval{
|
||||||
@@ -226,10 +228,5 @@ func generateLogJSON(logEntry Log, outputPath string) error {
|
|||||||
return fmt.Errorf("failed to marshal JSON: %v", err)
|
return fmt.Errorf("failed to marshal JSON: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = os.WriteFile(outputPath, jsonData, 0644)
|
return writeFileWithStatus(outputPath, jsonData)
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to write JSON file: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
@@ -19,6 +19,7 @@ type Config struct {
|
|||||||
|
|
||||||
type Log struct {
|
type Log struct {
|
||||||
ShortName string `yaml:"shortname"`
|
ShortName string `yaml:"shortname"`
|
||||||
|
Domain string `yaml:"domain"`
|
||||||
Inception string `yaml:"inception"`
|
Inception string `yaml:"inception"`
|
||||||
Period int `yaml:"period"`
|
Period int `yaml:"period"`
|
||||||
PoolSize int `yaml:"poolsize"`
|
PoolSize int `yaml:"poolsize"`
|
||||||
@@ -79,19 +80,55 @@ func loadConfig(yamlFile string) Config {
|
|||||||
log.Fatalf("Failed to parse YAML: %v", err)
|
log.Fatalf("Failed to parse YAML: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set defaults for log entries
|
// Set default listen port if not configured
|
||||||
for i := range config.Logs {
|
if len(config.Listen) == 0 {
|
||||||
if config.Logs[i].PoolSize == 0 {
|
config.Listen = []string{":8080"}
|
||||||
config.Logs[i].PoolSize = 750
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set defaults for log entries and check for empty/missing values
|
||||||
|
for i := range config.Logs {
|
||||||
|
// Checks are in order of fields of the Log struct
|
||||||
|
|
||||||
|
if config.Logs[i].ShortName == "" {
|
||||||
|
log.Fatalf("Log %d is missing a ShortName", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Logs[i].Domain == "" {
|
||||||
|
log.Fatalf("Log %d (%s) is missing a value for Domain", i, config.Logs[i].ShortName)
|
||||||
|
}
|
||||||
|
|
||||||
if config.Logs[i].Period == 0 {
|
if config.Logs[i].Period == 0 {
|
||||||
config.Logs[i].Period = 200
|
config.Logs[i].Period = 200
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if config.Logs[i].PoolSize == 0 {
|
||||||
|
config.Logs[i].PoolSize = 750
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func writeFileWithStatus(filename string, content []byte) error {
|
||||||
|
existingContent, err := os.ReadFile(filename)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
fmt.Printf("Creating %s\n", filename)
|
||||||
|
} else if err != nil {
|
||||||
|
return fmt.Errorf("failed to read existing file %s: %v", filename, err)
|
||||||
|
} else if string(existingContent) == string(content) {
|
||||||
|
fmt.Printf("Unchanged %s\n", filename)
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Updating %s\n", filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.WriteFile(filename, content, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to write file %s: %v", filename, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func showHelp() {
|
func showHelp() {
|
||||||
fmt.Printf("Usage: %s [options] <command>\n\n", os.Args[0])
|
fmt.Printf("Usage: %s [options] <command>\n\n", os.Args[0])
|
||||||
fmt.Printf("Options:\n")
|
fmt.Printf("Options:\n")
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
@@ -10,8 +11,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const nginxTemplate = `server {
|
const nginxTemplate = `server {
|
||||||
listen 8080;
|
listen {{.ListenPort}};
|
||||||
listen [::]:8080;
|
listen [::]:{{.ListenPort}};
|
||||||
|
|
||||||
# Replace with your actual domain(s)
|
# Replace with your actual domain(s)
|
||||||
server_name {{.MonitoringHost}};
|
server_name {{.MonitoringHost}};
|
||||||
@@ -72,11 +73,21 @@ const nginxTemplate = `server {
|
|||||||
type NginxTemplateData struct {
|
type NginxTemplateData struct {
|
||||||
MonitoringHost string
|
MonitoringHost string
|
||||||
LocalDirectory string
|
LocalDirectory string
|
||||||
|
ListenPort string
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateNginx(yamlFile string) {
|
func generateNginx(yamlFile string) {
|
||||||
config := loadConfig(yamlFile)
|
config := loadConfig(yamlFile)
|
||||||
|
|
||||||
|
// Extract port from first listen address
|
||||||
|
listenPort := "8080" // fallback default
|
||||||
|
if len(config.Listen) > 0 {
|
||||||
|
port := extractPort(config.Listen[0])
|
||||||
|
if port != "" {
|
||||||
|
listenPort = port
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, log := range config.Logs {
|
for _, log := range config.Logs {
|
||||||
// Extract hostname from monitoring prefix
|
// Extract hostname from monitoring prefix
|
||||||
hostname, err := extractHostname(log.MonitoringPrefix)
|
hostname, err := extractHostname(log.MonitoringPrefix)
|
||||||
@@ -89,6 +100,7 @@ func generateNginx(yamlFile string) {
|
|||||||
data := NginxTemplateData{
|
data := NginxTemplateData{
|
||||||
MonitoringHost: hostname,
|
MonitoringHost: hostname,
|
||||||
LocalDirectory: log.LocalDirectory,
|
LocalDirectory: log.LocalDirectory,
|
||||||
|
ListenPort: listenPort,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse and execute template
|
// Parse and execute template
|
||||||
@@ -102,24 +114,41 @@ func generateNginx(yamlFile string) {
|
|||||||
outputFilename := fmt.Sprintf("%s.conf", hostname)
|
outputFilename := fmt.Sprintf("%s.conf", hostname)
|
||||||
outputPath := filepath.Join(log.LocalDirectory, outputFilename)
|
outputPath := filepath.Join(log.LocalDirectory, outputFilename)
|
||||||
|
|
||||||
// Create output file
|
// Execute template to buffer
|
||||||
file, err := os.Create(outputPath)
|
var buf bytes.Buffer
|
||||||
if err != nil {
|
err = tmpl.Execute(&buf, data)
|
||||||
fmt.Fprintf(os.Stderr, "Failed to create nginx config file %s: %v\n", outputPath, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
// Execute template
|
|
||||||
err = tmpl.Execute(file, data)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Failed to execute nginx template for %s: %v\n", outputPath, err)
|
fmt.Fprintf(os.Stderr, "Failed to execute nginx template for %s: %v\n", outputPath, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Generated nginx config: %s\n", outputPath)
|
// Write file with status
|
||||||
|
err = writeFileWithStatus(outputPath, buf.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Failed to write nginx config file %s: %v\n", outputPath, err)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractPort(listenAddr string) string {
|
||||||
|
// Handle common listen address formats:
|
||||||
|
// ":8080" -> "8080"
|
||||||
|
// "localhost:8080" -> "8080"
|
||||||
|
// "[::]:8080" -> "8080"
|
||||||
|
|
||||||
|
if strings.HasPrefix(listenAddr, ":") {
|
||||||
|
return listenAddr[1:] // Remove the leading ":"
|
||||||
|
}
|
||||||
|
|
||||||
|
// For addresses with host:port format
|
||||||
|
if strings.Contains(listenAddr, ":") {
|
||||||
|
parts := strings.Split(listenAddr, ":")
|
||||||
|
return parts[len(parts)-1] // Return the last part (port)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func extractHostname(urlStr string) (string, error) {
|
func extractHostname(urlStr string) (string, error) {
|
||||||
if !strings.HasPrefix(urlStr, "http://") && !strings.HasPrefix(urlStr, "https://") {
|
if !strings.HasPrefix(urlStr, "http://") && !strings.HasPrefix(urlStr, "https://") {
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -8,7 +9,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -67,15 +67,10 @@ func generateRoots(args []string) {
|
|||||||
log.Fatalf("Failed to parse JSON response: %v", err)
|
log.Fatalf("Failed to parse JSON response: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create output file
|
// Collect all valid certificates in a buffer
|
||||||
outFile, err := os.Create(outputFile)
|
var pemBuffer bytes.Buffer
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to create output file %s: %v", outputFile, err)
|
|
||||||
}
|
|
||||||
defer outFile.Close()
|
|
||||||
|
|
||||||
// Write each certificate as PEM
|
|
||||||
validCertCount := 0
|
validCertCount := 0
|
||||||
|
|
||||||
for _, certBase64 := range rootsResp.Certificates {
|
for _, certBase64 := range rootsResp.Certificates {
|
||||||
// Decode base64 certificate
|
// Decode base64 certificate
|
||||||
certBytes, err := base64.StdEncoding.DecodeString(certBase64)
|
certBytes, err := base64.StdEncoding.DecodeString(certBase64)
|
||||||
@@ -102,14 +97,20 @@ func generateRoots(args []string) {
|
|||||||
Bytes: certBytes,
|
Bytes: certBytes,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write PEM to file
|
// Write PEM to buffer
|
||||||
err = pem.Encode(outFile, pemBlock)
|
err = pem.Encode(&pemBuffer, pemBlock)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to write PEM certificate: %v", err)
|
log.Fatalf("Failed to encode PEM certificate: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
validCertCount++
|
validCertCount++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write all certificates to file with status
|
||||||
|
err = writeFileWithStatus(outputFile, pemBuffer.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to write output file %s: %v", outputFile, err)
|
||||||
|
}
|
||||||
|
|
||||||
fmt.Printf("Successfully wrote %d certificates to %s (out of %d total)\n", validCertCount, outputFile, len(rootsResp.Certificates))
|
fmt.Printf("Successfully wrote %d certificates to %s (out of %d total)\n", validCertCount, outputFile, len(rootsResp.Certificates))
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user