Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions cli/cmd/init_install_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,14 @@ type InitInstallConfigOpts struct {
MetalLBEnabled bool
MetalLBPools []files.MetalLBPool

ACMEEnabled bool
ACMEIssuerName string
ACMEEmail string
ACMEServer string
ACMEEABKeyID string
ACMEEABMacKey string
ACMEDNS01Provider string

CodesphereDomain string
CodespherePublicIP string
CodesphereWorkspaceHostingBaseDomain string
Expand Down Expand Up @@ -135,6 +143,14 @@ func AddInitInstallConfigCmd(init *cobra.Command, opts *GlobalOptions) {
c.cmd.Flags().BoolVar(&c.Opts.KubernetesManagedByCodesphere, "k8s-managed", true, "Use Codesphere-managed Kubernetes")
c.cmd.Flags().StringSliceVar(&c.Opts.KubernetesControlPlanes, "k8s-control-plane", []string{}, "K8s control plane IPs (comma-separated)")

c.cmd.Flags().BoolVar(&c.Opts.ACMEEnabled, "acme-enabled", false, "Enable ACME certificate issuer")
c.cmd.Flags().StringVar(&c.Opts.ACMEIssuerName, "acme-issuer-name", "acme-issuer", "Name for the ACME ClusterIssuer")
c.cmd.Flags().StringVar(&c.Opts.ACMEEmail, "acme-email", "", "Email address for ACME account registration")
c.cmd.Flags().StringVar(&c.Opts.ACMEServer, "acme-server", "https://acme-v02.api.letsencrypt.org/directory", "ACME server URL")
c.cmd.Flags().StringVar(&c.Opts.ACMEEABKeyID, "acme-eab-key-id", "", "External Account Binding key ID (required by some ACME providers)")
c.cmd.Flags().StringVar(&c.Opts.ACMEEABMacKey, "acme-eab-mac-key", "", "External Account Binding MAC key (required by some ACME providers)")
c.cmd.Flags().StringVar(&c.Opts.ACMEDNS01Provider, "acme-dns01-provider", "", "DNS provider for DNS-01 solver (e.g., cloudflare)")

c.cmd.Flags().StringVar(&c.Opts.CodesphereDomain, "domain", "", "Main Codesphere domain")

util.MarkFlagRequired(c.cmd, "config")
Expand Down Expand Up @@ -373,6 +389,38 @@ func (c *InitInstallConfigCmd) updateConfigFromOpts(config *files.RootConfig) *f
}
}

// ACME configuration
if c.Opts.ACMEEnabled {
if config.Cluster.Certificates.ACME == nil {
config.Cluster.Certificates.ACME = &files.ACMEConfig{}
}
config.Cluster.Certificates.ACME.Enabled = true

if c.Opts.ACMEIssuerName != "" {
config.Cluster.Certificates.ACME.Name = c.Opts.ACMEIssuerName
}
if c.Opts.ACMEEmail != "" {
config.Cluster.Certificates.ACME.Email = c.Opts.ACMEEmail
}
if c.Opts.ACMEServer != "" {
config.Cluster.Certificates.ACME.Server = c.Opts.ACMEServer
}

if c.Opts.ACMEEABKeyID != "" {
config.Cluster.Certificates.ACME.EABKeyID = c.Opts.ACMEEABKeyID
}
if c.Opts.ACMEEABMacKey != "" {
config.Cluster.Certificates.ACME.EABMacKey = c.Opts.ACMEEABMacKey
}

// Configure DNS-01 solver
if c.Opts.ACMEDNS01Provider != "" {
config.Cluster.Certificates.ACME.Solver.DNS01 = &files.ACMEDNS01Solver{
Provider: c.Opts.ACMEDNS01Provider,
}
}
}

// Codesphere settings
if c.Opts.CodesphereDomain != "" {
config.Codesphere.Domain = c.Opts.CodesphereDomain
Expand Down
92 changes: 91 additions & 1 deletion cli/cmd/update_install_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@ type UpdateInstallConfigOpts struct {
ClusterPublicGatewayServiceType string
ClusterPublicGatewayIPAddresses []string

ACMEEnabled bool
ACMEIssuerName string
ACMEEmail string
ACMEServer string
ACMEEABKeyID string
ACMEEABMacKey string
ACMEDNS01Provider string

CodesphereDomain string
CodespherePublicIP string
CodesphereWorkspaceHostingBaseDomain string
Expand Down Expand Up @@ -110,6 +118,15 @@ func AddUpdateInstallConfigCmd(update *cobra.Command, opts *GlobalOptions) {
c.cmd.Flags().StringVar(&c.Opts.ClusterPublicGatewayServiceType, "cluster-public-gateway-service-type", "", "Cluster public gateway service type")
c.cmd.Flags().StringSliceVar(&c.Opts.ClusterPublicGatewayIPAddresses, "cluster-public-gateway-ips", []string{}, "Cluster public gateway IP addresses (comma-separated)")

// ACME update flags
c.cmd.Flags().BoolVar(&c.Opts.ACMEEnabled, "acme-enabled", false, "Enable ACME certificate issuer")
c.cmd.Flags().StringVar(&c.Opts.ACMEIssuerName, "acme-issuer-name", "", "Name for the ACME ClusterIssuer")
c.cmd.Flags().StringVar(&c.Opts.ACMEEmail, "acme-email", "", "Email address for ACME account registration")
c.cmd.Flags().StringVar(&c.Opts.ACMEServer, "acme-server", "", "ACME server URL")
c.cmd.Flags().StringVar(&c.Opts.ACMEEABKeyID, "acme-eab-key-id", "", "External Account Binding key ID (required by some ACME providers)")
c.cmd.Flags().StringVar(&c.Opts.ACMEEABMacKey, "acme-eab-mac-key", "", "External Account Binding MAC key (required by some ACME providers)")
c.cmd.Flags().StringVar(&c.Opts.ACMEDNS01Provider, "acme-dns01-provider", "", "DNS provider for DNS-01 solver")

// Codesphere update flags
c.cmd.Flags().StringVar(&c.Opts.CodesphereDomain, "domain", "", "Main Codesphere domain")
c.cmd.Flags().StringVar(&c.Opts.CodespherePublicIP, "public-ip", "", "Codesphere public IP address")
Expand Down Expand Up @@ -255,6 +272,67 @@ func (c *UpdateInstallConfigCmd) applyUpdates(config *files.RootConfig, tracker
config.Cluster.PublicGateway.IPAddresses = c.Opts.ClusterPublicGatewayIPAddresses
}

// ACME updates
acmeChanged := false
if c.Opts.ACMEEnabled {
if config.Cluster.Certificates.ACME == nil {
config.Cluster.Certificates.ACME = &files.ACMEConfig{}
}

if !config.Cluster.Certificates.ACME.Enabled {
log.Printf("Enabling ACME certificate issuer\n")
config.Cluster.Certificates.ACME.Enabled = true
acmeChanged = true
}

if c.Opts.ACMEIssuerName != "" && config.Cluster.Certificates.ACME.Name != c.Opts.ACMEIssuerName {
log.Printf("Updating ACME issuer name: %s -> %s\n", config.Cluster.Certificates.ACME.Name, c.Opts.ACMEIssuerName)
config.Cluster.Certificates.ACME.Name = c.Opts.ACMEIssuerName
acmeChanged = true
}

if c.Opts.ACMEEmail != "" && config.Cluster.Certificates.ACME.Email != c.Opts.ACMEEmail {
log.Printf("Updating ACME email: %s -> %s\n", config.Cluster.Certificates.ACME.Email, c.Opts.ACMEEmail)
config.Cluster.Certificates.ACME.Email = c.Opts.ACMEEmail
acmeChanged = true
}

if c.Opts.ACMEServer != "" && config.Cluster.Certificates.ACME.Server != c.Opts.ACMEServer {
log.Printf("Updating ACME server: %s -> %s\n", config.Cluster.Certificates.ACME.Server, c.Opts.ACMEServer)
config.Cluster.Certificates.ACME.Server = c.Opts.ACMEServer
acmeChanged = true
}

if c.Opts.ACMEEABKeyID != "" && config.Cluster.Certificates.ACME.EABKeyID != c.Opts.ACMEEABKeyID {
log.Printf("Updating ACME EAB key ID: %s -> %s\n", config.Cluster.Certificates.ACME.EABKeyID, c.Opts.ACMEEABKeyID)
config.Cluster.Certificates.ACME.EABKeyID = c.Opts.ACMEEABKeyID
acmeChanged = true
}

if c.Opts.ACMEEABMacKey != "" && config.Cluster.Certificates.ACME.EABMacKey != c.Opts.ACMEEABMacKey {
log.Printf("Updating ACME EAB MAC key\n")
config.Cluster.Certificates.ACME.EABMacKey = c.Opts.ACMEEABMacKey
acmeChanged = true
}

// Update DNS-01 solver configuration
if c.Opts.ACMEDNS01Provider != "" {
if config.Cluster.Certificates.ACME.Solver.DNS01 == nil {
config.Cluster.Certificates.ACME.Solver.DNS01 = &files.ACMEDNS01Solver{}
}
if config.Cluster.Certificates.ACME.Solver.DNS01.Provider != c.Opts.ACMEDNS01Provider {
log.Printf("Updating ACME DNS-01 provider: %s -> %s\n",
config.Cluster.Certificates.ACME.Solver.DNS01.Provider, c.Opts.ACMEDNS01Provider)
config.Cluster.Certificates.ACME.Solver.DNS01.Provider = c.Opts.ACMEDNS01Provider
acmeChanged = true
}
}

if acmeChanged {
tracker.MarkACMEConfigChanged()
}
}

// Codesphere updates
if c.Opts.CodesphereDomain != "" && config.Codesphere.Domain != c.Opts.CodesphereDomain {
fmt.Printf("Updating Codesphere domain: %s -> %s\n", config.Codesphere.Domain, c.Opts.CodesphereDomain)
Expand Down Expand Up @@ -329,6 +407,9 @@ func (c *UpdateInstallConfigCmd) printSuccessMessage(tracker *SecretDependencyTr
if tracker.NeedsPostgresReplicaCertRegen() {
fmt.Println(" ✓ PostgreSQL replica server certificate")
}
if tracker.ACMEConfigChanged() {
log.Println(" ✓ ACME configuration updated")
}
}

fmt.Println("\nIMPORTANT: The vault file has been updated with new secrets.")
Expand All @@ -339,6 +420,7 @@ func (c *UpdateInstallConfigCmd) printSuccessMessage(tracker *SecretDependencyTr
type SecretDependencyTracker struct {
postgresPrimaryCertNeedsRegen bool
postgresReplicaCertNeedsRegen bool
acmeConfigChanged bool
}

func NewSecretDependencyTracker() *SecretDependencyTracker {
Expand All @@ -353,6 +435,10 @@ func (t *SecretDependencyTracker) MarkPostgresReplicaCertNeedsRegen() {
t.postgresReplicaCertNeedsRegen = true
}

func (t *SecretDependencyTracker) MarkACMEConfigChanged() {
t.acmeConfigChanged = true
}

func (t *SecretDependencyTracker) NeedsPostgresPrimaryCertRegen() bool {
return t.postgresPrimaryCertNeedsRegen
}
Expand All @@ -361,6 +447,10 @@ func (t *SecretDependencyTracker) NeedsPostgresReplicaCertRegen() bool {
return t.postgresReplicaCertNeedsRegen
}

func (t *SecretDependencyTracker) ACMEConfigChanged() bool {
return t.acmeConfigChanged
}

func (t *SecretDependencyTracker) HasChanges() bool {
return t.postgresPrimaryCertNeedsRegen || t.postgresReplicaCertNeedsRegen
return t.postgresPrimaryCertNeedsRegen || t.postgresReplicaCertNeedsRegen || t.acmeConfigChanged
}
7 changes: 7 additions & 0 deletions docs/oms-cli_init_install-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@ $ oms-cli init install-config --validate -c config.yaml --vault prod.vault.yaml
### Options

```
--acme-dns01-provider string DNS provider for DNS-01 solver (e.g., cloudflare)
--acme-eab-key-id string External Account Binding key ID (required by some ACME providers)
--acme-eab-mac-key string External Account Binding MAC key (required by some ACME providers)
--acme-email string Email address for ACME account registration
--acme-enabled Enable ACME certificate issuer
--acme-issuer-name string Name for the ACME ClusterIssuer (default "acme-issuer")
--acme-server string ACME server URL (default "https://acme-v02.api.letsencrypt.org/directory")
-c, --config string Output file path for config.yaml (default "config.yaml")
--dc-id int Datacenter ID
--dc-name string Datacenter name
Expand Down
7 changes: 7 additions & 0 deletions docs/oms-cli_update_install-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ $ oms-cli update install-config --k8s-api-server 10.0.0.10 --config config.yaml
### Options

```
--acme-dns01-provider string DNS provider for DNS-01 solver
--acme-eab-key-id string External Account Binding key ID (required by some ACME providers)
--acme-eab-mac-key string External Account Binding MAC key (required by some ACME providers)
--acme-email string Email address for ACME account registration
--acme-enabled Enable ACME certificate issuer
--acme-issuer-name string Name for the ACME ClusterIssuer
--acme-server string ACME server URL
--ceph-nodes-subnet string Ceph nodes subnet
--cluster-gateway-ips strings Cluster gateway IP addresses (comma-separated)
--cluster-gateway-service-type string Cluster gateway service type
Expand Down
68 changes: 68 additions & 0 deletions internal/installer/config_generator_collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package installer

import (
"fmt"
"log"

"github.com/codesphere-cloud/oms/internal/installer/files"
)
Expand All @@ -19,6 +20,7 @@ func (g *InstallConfig) CollectInteractively() error {
g.collectK8sConfig(prompter)
g.collectGatewayConfig(prompter)
g.collectMetalLBConfig(prompter)
g.collectACMEConfig(prompter)
g.collectCodesphereConfig(prompter)

return nil
Expand Down Expand Up @@ -210,6 +212,72 @@ func (g *InstallConfig) collectMetalLBConfig(prompter *Prompter) {
}
}

func (g *InstallConfig) collectACMEConfig(prompter *Prompter) {
log.Println("\n=== ACME Certificate Configuration (Optional) ===")

// Initialize ACME config if it doesn't exist
if g.Config.Cluster.Certificates.ACME == nil {
g.Config.Cluster.Certificates.ACME = &files.ACMEConfig{}
}

g.Config.Cluster.Certificates.ACME.Enabled = prompter.Bool("Enable ACME certificate issuer (e.g., Let's Encrypt)", g.Config.Cluster.Certificates.ACME.Enabled)

if g.Config.Cluster.Certificates.ACME.Enabled {
defaultIssuerName := g.Config.Cluster.Certificates.ACME.Name
if defaultIssuerName == "" {
defaultIssuerName = "acme-issuer"
}
g.Config.Cluster.Certificates.ACME.Name = g.collectString(prompter, "ACME issuer name", defaultIssuerName)

defaultEmail := g.Config.Cluster.Certificates.ACME.Email
if defaultEmail == "" {
defaultEmail = "admin@example.com"
}
g.Config.Cluster.Certificates.ACME.Email = g.collectString(prompter, "Email address for ACME account registration", defaultEmail)

defaultServer := g.Config.Cluster.Certificates.ACME.Server
if defaultServer == "" {
defaultServer = "https://acme-v02.api.letsencrypt.org/directory"
}
g.Config.Cluster.Certificates.ACME.Server = g.collectString(prompter, "ACME server URL", defaultServer)

// External Account Binding (EAB)
log.Println("\n--- External Account Binding (Optional) ---")
hasEAB := prompter.Bool("Configure External Account Binding (required by some ACME CAs)", g.Config.Cluster.Certificates.ACME.EABKeyID != "")
if hasEAB {
g.Config.Cluster.Certificates.ACME.EABKeyID = g.collectString(prompter, "EAB Key ID", g.Config.Cluster.Certificates.ACME.EABKeyID)
g.Config.Cluster.Certificates.ACME.EABMacKey = g.collectString(prompter, "EAB MAC Key", g.Config.Cluster.Certificates.ACME.EABMacKey)
} else {
g.Config.Cluster.Certificates.ACME.EABKeyID = ""
g.Config.Cluster.Certificates.ACME.EABMacKey = ""
}

// DNS-01 Challenge Configuration
log.Println("\n--- DNS-01 Challenge Configuration (Optional) ---")
if g.Config.Cluster.Certificates.ACME.Solver.DNS01 == nil {
g.Config.Cluster.Certificates.ACME.Solver.DNS01 = &files.ACMEDNS01Solver{}
}

useDNS01 := prompter.Bool("Configure DNS-01 challenge solver", g.Config.Cluster.Certificates.ACME.Solver.DNS01.Provider != "")
if useDNS01 {
providerOptions := []string{"route53", "cloudflare", "azure", "gcp", "other"}
defaultProvider := g.Config.Cluster.Certificates.ACME.Solver.DNS01.Provider
if defaultProvider == "" {
defaultProvider = "cloudflare"
}
g.Config.Cluster.Certificates.ACME.Solver.DNS01.Provider = g.collectChoice(prompter, "DNS provider", providerOptions, defaultProvider)

log.Println("Note: Additional DNS provider configuration will need to be added to the vault file.")
log.Println("Provider config and secrets should be added manually after generation.")
} else {
g.Config.Cluster.Certificates.ACME.Solver.DNS01 = nil
}
} else {
// If ACME is disabled, clear the config
g.Config.Cluster.Certificates.ACME = nil
}
}

func (g *InstallConfig) collectCodesphereConfig(prompter *Prompter) {
fmt.Println("\n=== Codesphere Application Configuration ===")
defaultDomain := g.Config.Codesphere.Domain
Expand Down
10 changes: 10 additions & 0 deletions internal/installer/config_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -348,5 +348,15 @@ func (g *InstallConfig) MergeVaultIntoConfig() error {
g.Config.Codesphere.GitProviders.GitHub.OAuth.ClientSecret = secret.Fields.Password
}

// ACME secrets
if g.Config.Cluster.Certificates.ACME != nil {
if secret, ok := secretsMap["acmeEabKeyId"]; ok && secret.Fields != nil {
g.Config.Cluster.Certificates.ACME.EABKeyID = secret.Fields.Password
}
if secret, ok := secretsMap["acmeEabMacKey"]; ok && secret.Fields != nil {
g.Config.Cluster.Certificates.ACME.EABMacKey = secret.Fields.Password
}
}

return nil
}
Loading