Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| affa895cfe | |||
| 714a38d612 | |||
| 5d2675450d | |||
| 8ee0b326d0 | |||
| e3c812592b |
19
Makefile
Normal file
19
Makefile
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Get version from git tags
|
||||||
|
VERSION := $(shell git describe --tags --always 2>/dev/null)
|
||||||
|
|
||||||
|
.PHONY: clean
|
||||||
|
|
||||||
|
build/lab-ca: main.go ca.go certdb.go
|
||||||
|
@mkdir -p build
|
||||||
|
ifneq ($(VERSION),)
|
||||||
|
@echo "Building version: $(VERSION)"
|
||||||
|
go build -o build/lab-ca -ldflags "-X main.Version=$(VERSION)"
|
||||||
|
else
|
||||||
|
@echo "Building without version information"
|
||||||
|
go build -o build/lab-ca .
|
||||||
|
endif
|
||||||
|
|
||||||
|
lab-ca: build/lab-ca
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f build/lab-ca
|
||||||
13
README.md
13
README.md
@@ -86,7 +86,7 @@ lab-ca list --revoked
|
|||||||
Issue a new certificate from the command line:
|
Issue a new certificate from the command line:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
lab-ca issue --name <name> [--subject <subject>] [--type <type>] [--validity <period>] [--san <SAN> ...] [--overwrite] [--dry-run] [--verbose]
|
lab-ca issue --name <name> [--subject <subject>] [--type <type>] [--validity <period>] [--san <SAN> ...] [--dry-run] [--verbose]
|
||||||
```
|
```
|
||||||
|
|
||||||
- `--name` (required): Name for the certificate and key files (used as subject if `--subject` is omitted)
|
- `--name` (required): Name for the certificate and key files (used as subject if `--subject` is omitted)
|
||||||
@@ -94,7 +94,6 @@ lab-ca issue --name <name> [--subject <subject>] [--type <type>] [--validity <pe
|
|||||||
- `--type`: Certificate type: `client`, `server`, `code-signing`, `email` (comma-separated for multiple usages; default: `server`)
|
- `--type`: Certificate type: `client`, `server`, `code-signing`, `email` (comma-separated for multiple usages; default: `server`)
|
||||||
- `--validity`: Validity period (e.g. `2y`, `6m`, `30d`; default: `1y`)
|
- `--validity`: Validity period (e.g. `2y`, `6m`, `30d`; default: `1y`)
|
||||||
- `--san`: Subject Alternative Name (repeatable; e.g. `dns:example.com`, `ip:1.2.3.4`, `email:user@example.com`)
|
- `--san`: Subject Alternative Name (repeatable; e.g. `dns:example.com`, `ip:1.2.3.4`, `email:user@example.com`)
|
||||||
- `--overwrite`: Allow overwriting existing files
|
|
||||||
- `--dry-run`: Validate and show what would be created, but do not write files
|
- `--dry-run`: Validate and show what would be created, but do not write files
|
||||||
- `--verbose`: Print detailed information
|
- `--verbose`: Print detailed information
|
||||||
|
|
||||||
@@ -105,7 +104,7 @@ lab-ca issue --name <name> [--subject <subject>] [--type <type>] [--validity <pe
|
|||||||
Provision multiple certificates from a batch file (HCL):
|
Provision multiple certificates from a batch file (HCL):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
lab-ca provision --file <certificates.hcl> [--overwrite] [--verbose]
|
lab-ca provision --file <certificates.hcl> [--verbose]
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Example HCL Provisioning File
|
#### Example HCL Provisioning File
|
||||||
@@ -223,13 +222,9 @@ See `examples/example-certificates.hcl` for a more advanced provisioning file wi
|
|||||||
|
|
||||||
## Building the Tool
|
## Building the Tool
|
||||||
|
|
||||||
The repository includes a `build.sh` script to build the CLI tool. It updates the version in `version.go` and builds the binary.
|
The repository includes a `Makefile` to build the CLI tool. It automatically determines the version from Git tags and builds the binary.
|
||||||
|
|
||||||
To ignore changes made to `version.go` in Git, you can run:
|
To build the tool, run the `make` command. The binary will be created as `build/lab-ca`.
|
||||||
|
|
||||||
```bash
|
|
||||||
git update-index --assume-unchanged version.go
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
26
build.sh
26
build.sh
@@ -1,26 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
# Build script for lab-ca with version injection from git tag
|
|
||||||
git describe --tags --always --dirty > /dev/null 2>&1
|
|
||||||
if [ $? -eq 0 ]; then
|
|
||||||
VERSION=$(git describe --tags --always --dirty)
|
|
||||||
else
|
|
||||||
VERSION="dev"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Hardcode the version into main.go
|
|
||||||
sed -i '' "s/^var Version = .*/var Version = \"$VERSION\"/" version.go
|
|
||||||
|
|
||||||
if echo $VERSION | grep -q 'dirty$'; then
|
|
||||||
echo "Building in development mode, output directory is set to 'build'."
|
|
||||||
OUTPUT_DIR=build
|
|
||||||
|
|
||||||
# Make sure the output directory exists, create it if it is not
|
|
||||||
mkdir -p $OUTPUT_DIR
|
|
||||||
else
|
|
||||||
echo "Building with version: $VERSION"
|
|
||||||
OUTPUT_DIR=$GOHOME/bin
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Build the Lab CA binary with version information
|
|
||||||
# go build -ldflags "-X main.Version=$VERSION" -o $OUTPUT_DIR/lab-ca
|
|
||||||
go build -o $OUTPUT_DIR/lab-ca
|
|
||||||
20
ca.go
20
ca.go
@@ -460,9 +460,19 @@ func issueSingleCertificate(def CertificateDefinition, i int, n int) (bool, erro
|
|||||||
return false, fmt.Errorf("certificate name must be specified and contain only letters, numbers, dash, or underscore")
|
return false, fmt.Errorf("certificate name must be specified and contain only letters, numbers, dash, or underscore")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the certificate is in database, fail if it is.
|
// Check if the certificate is in database, skip if it already exists and is valid.
|
||||||
if caState.FindByName(def.Name, false) != nil {
|
if caState.FindByName(def.Name, false) != nil {
|
||||||
return false, fmt.Errorf("certificate %s already exists and is valid.", def.Name)
|
if !dryRun {
|
||||||
|
fmt.Printf("skipped (already exists).\n")
|
||||||
|
} else {
|
||||||
|
msg := fmt.Sprintf("Certificate '%s' already exists and is valid (would skip).", def.Name)
|
||||||
|
if n > 1 {
|
||||||
|
fmt.Printf("[%d/%d] %s\n", i+1, n, msg)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("%s\n", msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize Subject if not specified
|
// Initialize Subject if not specified
|
||||||
@@ -634,7 +644,7 @@ Certificate generated:
|
|||||||
// If saving CA state fails, we still return success for the certificate issuance
|
// If saving CA state fails, we still return success for the certificate issuance
|
||||||
fmt.Printf("WARNING: %v\n", err)
|
fmt.Printf("WARNING: %v\n", err)
|
||||||
fmt.Println("CA state not saved, but certificate issued and saved successfully.")
|
fmt.Println("CA state not saved, but certificate issued and saved successfully.")
|
||||||
return true, fmt.Errorf("Error saving CA state: %v", err)
|
return true, fmt.Errorf("error saving CA state: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return true, nil
|
return true, nil
|
||||||
@@ -655,12 +665,12 @@ func ProvisionCertificates(filePath string) error {
|
|||||||
// Load certificates provisioning configuration from the file (HCL syntax)
|
// Load certificates provisioning configuration from the file (HCL syntax)
|
||||||
err = certDefs.LoadFromFile(filePath)
|
err = certDefs.LoadFromFile(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Error loading certificates file: %v", err)
|
return fmt.Errorf("error loading certificates file: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// The certificate provisioning file must contain at least one certificate definition
|
// The certificate provisioning file must contain at least one certificate definition
|
||||||
if len(certDefs.Certificates) < 1 {
|
if len(certDefs.Certificates) < 1 {
|
||||||
return fmt.Errorf("No certificates defined in %s", filePath)
|
return fmt.Errorf("no certificates defined in %s", filePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
// We will be counting successes and errors
|
// We will be counting successes and errors
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ func SaveCAState() error {
|
|||||||
// UpdateCAStateAfterIssue updates the CA state JSON after issuing a certificate
|
// UpdateCAStateAfterIssue updates the CA state JSON after issuing a certificate
|
||||||
func (s *CAState) UpdateCAStateAfterIssue(serialType, name string, subject string, certType string, serialNumber any, validity time.Duration) error {
|
func (s *CAState) UpdateCAStateAfterIssue(serialType, name string, subject string, certType string, serialNumber any, validity time.Duration) error {
|
||||||
if s == nil {
|
if s == nil {
|
||||||
return fmt.Errorf("CAState is nil in UpdateCAStateAfterIssue. This indicates a programming error.")
|
return fmt.Errorf("CAState is nil in UpdateCAStateAfterIssue. This indicates a programming error")
|
||||||
}
|
}
|
||||||
issued := time.Now().UTC().Format(time.RFC3339)
|
issued := time.Now().UTC().Format(time.RFC3339)
|
||||||
expires := time.Now().Add(validity).UTC().Format(time.RFC3339)
|
expires := time.Now().Add(validity).UTC().Format(time.RFC3339)
|
||||||
@@ -135,7 +135,7 @@ func (s *CAState) AddCertificate(name, subject, certType, issued, expires, seria
|
|||||||
// RevokeCertificate revokes a certificate by serial number and reason code, updates state, and saves to disk
|
// RevokeCertificate revokes a certificate by serial number and reason code, updates state, and saves to disk
|
||||||
func (s *CAState) RevokeCertificate(serial string, reason int) error {
|
func (s *CAState) RevokeCertificate(serial string, reason int) error {
|
||||||
if s == nil {
|
if s == nil {
|
||||||
return fmt.Errorf("CAState is nil in RevokeCertificate. This indicates a programming error.")
|
return fmt.Errorf("CAState is nil in RevokeCertificate. This indicates a programming error")
|
||||||
}
|
}
|
||||||
revoked := false
|
revoked := false
|
||||||
revokedAt := time.Now().UTC().Format(time.RFC3339)
|
revokedAt := time.Now().UTC().Format(time.RFC3339)
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
git update-index --assume-unchanged version.go
|
|
||||||
6
main.go
6
main.go
@@ -10,6 +10,7 @@ import (
|
|||||||
// Global flags available to all commands
|
// Global flags available to all commands
|
||||||
var dryRun bool
|
var dryRun bool
|
||||||
var verbose bool
|
var verbose bool
|
||||||
|
var Version = "v0.4.0"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
@@ -70,10 +71,13 @@ func main() {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
for _, certDef := range caState.Certificates {
|
for _, certDef := range caState.Certificates {
|
||||||
if certDef.RevokedAt != "" {
|
if certDef.RevokedAt != "" && !listRevoked {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
fmt.Printf("Certificate %s\n", certDef.Name)
|
fmt.Printf("Certificate %s\n", certDef.Name)
|
||||||
|
if certDef.RevokedAt != "" {
|
||||||
|
fmt.Printf("\tStatus: REVOKED (at %s)\n", certDef.RevokedAt)
|
||||||
|
}
|
||||||
fmt.Printf("\tSubject: %s\n\tType: %s\n\tIssued at: %s\n",
|
fmt.Printf("\tSubject: %s\n\tType: %s\n\tIssued at: %s\n",
|
||||||
certDef.Subject, certDef.Type, certDef.Issued)
|
certDef.Subject, certDef.Type, certDef.Issued)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
VERSION=${1:-$(git describe --tags --always --dirty 2>/dev/null || echo "dev")}
|
|
||||||
# Allow git to track changes to version.go
|
|
||||||
git update-index --no-assume-unchanged version.go
|
|
||||||
# Hardcode the version into main.go
|
|
||||||
sed -i '' "s/^var Version = .*/var Version = \"$VERSION\"/" version.go
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
var Version = "v0.4.0"
|
|
||||||
Reference in New Issue
Block a user