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
4 changes: 2 additions & 2 deletions NOTICE
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ License URL: https://github.com/googleapis/google-cloud-go/blob/resourcemanager/

----------
Module: cloud.google.com/go/serviceusage
Version: v1.10.0
Version: v1.11.0
License: Apache-2.0
License URL: https://github.com/googleapis/google-cloud-go/blob/serviceusage/v1.10.0/serviceusage/LICENSE
License URL: https://github.com/googleapis/google-cloud-go/blob/serviceusage/v1.11.0/serviceusage/LICENSE

----------
Module: code.gitea.io/sdk/gitea
Expand Down
65 changes: 47 additions & 18 deletions internal/bootstrap/gcp/install_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,11 @@ func (b *GCPBootstrapper) UpdateInstallConfig() error {
Hostname: b.Env.PostgreSQLNode.GetName(),
}
}

previousPrimaryIP := b.Env.InstallConfig.Postgres.Primary.IP
previousPrimaryHostname := b.Env.InstallConfig.Postgres.Primary.Hostname
b.Env.InstallConfig.Postgres.Primary.IP = b.Env.PostgreSQLNode.GetInternalIP()
b.Env.InstallConfig.Postgres.Primary.Hostname = b.Env.PostgreSQLNode.GetName()

b.Env.InstallConfig.Ceph.CsiKubeletDir = "/var/lib/k0s/kubelet"
b.Env.InstallConfig.Ceph.NodesSubnet = "10.10.0.0/20"
Expand Down Expand Up @@ -247,24 +251,8 @@ func (b *GCPBootstrapper) UpdateInstallConfig() error {
return fmt.Errorf("failed to generate secrets: %w", err)
}
} else {
var err error
b.Env.InstallConfig.Postgres.Primary.PrivateKey, b.Env.InstallConfig.Postgres.Primary.SSLConfig.ServerCertPem, err = installer.GenerateServerCertificate(
b.Env.InstallConfig.Postgres.CaCertPrivateKey,
b.Env.InstallConfig.Postgres.CACertPem,
b.Env.InstallConfig.Postgres.Primary.Hostname,
[]string{b.Env.InstallConfig.Postgres.Primary.IP})
if err != nil {
return fmt.Errorf("failed to generate primary server certificate: %w", err)
}
if b.Env.InstallConfig.Postgres.Replica != nil {
b.Env.InstallConfig.Postgres.ReplicaPrivateKey, b.Env.InstallConfig.Postgres.Replica.SSLConfig.ServerCertPem, err = installer.GenerateServerCertificate(
b.Env.InstallConfig.Postgres.CaCertPrivateKey,
b.Env.InstallConfig.Postgres.CACertPem,
b.Env.InstallConfig.Postgres.Replica.Name,
[]string{b.Env.InstallConfig.Postgres.Replica.IP})
if err != nil {
return fmt.Errorf("failed to generate replica server certificate: %w", err)
}
if err := b.regeneratePostgresCerts(previousPrimaryIP, previousPrimaryHostname); err != nil {
return err
}
}

Expand Down Expand Up @@ -298,6 +286,47 @@ func (b *GCPBootstrapper) UpdateInstallConfig() error {
return nil
}

// regeneratePostgresCerts regenerates PostgreSQL TLS certificates when the IP/hostname
// changed or no private key was loaded from the vault.
func (b *GCPBootstrapper) regeneratePostgresCerts(previousPrimaryIP, previousPrimaryHostname string) error {
// Only regenerate postgres certificates if the IP or hostname changed,
// or if the private key was not loaded from the vault.
primaryNeedsRegen := b.Env.InstallConfig.Postgres.Primary.PrivateKey == "" ||
previousPrimaryIP != b.Env.InstallConfig.Postgres.Primary.IP ||
previousPrimaryHostname != b.Env.InstallConfig.Postgres.Primary.Hostname

if primaryNeedsRegen {
var err error
b.Env.InstallConfig.Postgres.Primary.PrivateKey, b.Env.InstallConfig.Postgres.Primary.SSLConfig.ServerCertPem, err = installer.GenerateServerCertificate(
b.Env.InstallConfig.Postgres.CaCertPrivateKey,
b.Env.InstallConfig.Postgres.CACertPem,
b.Env.InstallConfig.Postgres.Primary.Hostname,
[]string{b.Env.InstallConfig.Postgres.Primary.IP})
if err != nil {
return fmt.Errorf("failed to generate primary server certificate: %w", err)
}
if err := installer.ValidateCertKeyPair(b.Env.InstallConfig.Postgres.Primary.SSLConfig.ServerCertPem, b.Env.InstallConfig.Postgres.Primary.PrivateKey); err != nil {
return fmt.Errorf("primary PostgreSQL cert/key validation failed: %w", err)
}
}
// Replica certificates only regenerate if the key is missing from the vault.
if b.Env.InstallConfig.Postgres.Replica != nil && (b.Env.InstallConfig.Postgres.ReplicaPrivateKey == "") {
var err error
b.Env.InstallConfig.Postgres.ReplicaPrivateKey, b.Env.InstallConfig.Postgres.Replica.SSLConfig.ServerCertPem, err = installer.GenerateServerCertificate(
b.Env.InstallConfig.Postgres.CaCertPrivateKey,
b.Env.InstallConfig.Postgres.CACertPem,
b.Env.InstallConfig.Postgres.Replica.Name,
[]string{b.Env.InstallConfig.Postgres.Replica.IP})
if err != nil {
return fmt.Errorf("failed to generate replica server certificate: %w", err)
}
if err := installer.ValidateCertKeyPair(b.Env.InstallConfig.Postgres.Replica.SSLConfig.ServerCertPem, b.Env.InstallConfig.Postgres.ReplicaPrivateKey); err != nil {
return fmt.Errorf("replica PostgreSQL cert/key validation failed: %w", err)
}
}
return nil
}

func (b *GCPBootstrapper) EnsureAgeKey() error {
hasKey := b.Env.Jumpbox.NodeClient.HasFile(b.Env.Jumpbox, b.Env.SecretsDir+"/age_key.txt")
if hasKey {
Expand Down
125 changes: 125 additions & 0 deletions internal/bootstrap/gcp/install_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,131 @@ var _ = Describe("Installconfig & Secrets", func() {
Expect(err.Error()).To(ContainSubstring("failed to copy secrets file to jumpbox"))
})
})

Describe("ExistingConfigUsed", func() {
BeforeEach(func() {
csEnv.ExistingConfigUsed = true
})

Context("with unchanged IP and existing key", func() {
BeforeEach(func() {
caKey, caCert, err := installer.GenerateCA("Test CA", "DE", "Berlin", "TestOrg")
Expect(err).NotTo(HaveOccurred())

key, cert, err := installer.GenerateServerCertificate(caKey, caCert, "postgres", []string{"10.0.0.1"})
Expect(err).NotTo(HaveOccurred())

csEnv.InstallConfig.Postgres.CaCertPrivateKey = caKey
csEnv.InstallConfig.Postgres.CACertPem = caCert
csEnv.InstallConfig.Postgres.Primary.IP = "10.0.0.1"
csEnv.InstallConfig.Postgres.Primary.Hostname = "postgres"
csEnv.InstallConfig.Postgres.Primary.PrivateKey = key
csEnv.InstallConfig.Postgres.Primary.SSLConfig.ServerCertPem = cert
})

It("preserves existing cert/key without regeneration", func() {
origKey := csEnv.InstallConfig.Postgres.Primary.PrivateKey
origCert := csEnv.InstallConfig.Postgres.Primary.SSLConfig.ServerCertPem

icg.EXPECT().GenerateSecrets().Return(nil).Times(0)
icg.EXPECT().WriteInstallConfig("fake-config-file", true).Return(nil)
icg.EXPECT().WriteVault("fake-secret", true).Return(nil)
nodeClient.EXPECT().CopyFile(mock.Anything, mock.Anything, mock.Anything).Return(nil).Twice()

err := bs.UpdateInstallConfig()
Expect(err).NotTo(HaveOccurred())

Expect(bs.Env.InstallConfig.Postgres.Primary.PrivateKey).To(Equal(origKey))
Expect(bs.Env.InstallConfig.Postgres.Primary.SSLConfig.ServerCertPem).To(Equal(origCert))
})
})

Context("with changed IP", func() {
BeforeEach(func() {
caKey, caCert, err := installer.GenerateCA("Test CA", "DE", "Berlin", "TestOrg")
Expect(err).NotTo(HaveOccurred())

key, cert, err := installer.GenerateServerCertificate(caKey, caCert, "postgres", []string{"10.0.0.99"})
Expect(err).NotTo(HaveOccurred())

csEnv.InstallConfig.Postgres.CaCertPrivateKey = caKey
csEnv.InstallConfig.Postgres.CACertPem = caCert
csEnv.InstallConfig.Postgres.Primary.IP = "10.0.0.99"
csEnv.InstallConfig.Postgres.Primary.Hostname = "postgres"
csEnv.InstallConfig.Postgres.Primary.PrivateKey = key
csEnv.InstallConfig.Postgres.Primary.SSLConfig.ServerCertPem = cert
})

It("regenerates cert/key for the new IP", func() {
origKey := csEnv.InstallConfig.Postgres.Primary.PrivateKey

icg.EXPECT().WriteInstallConfig("fake-config-file", true).Return(nil)
icg.EXPECT().WriteVault("fake-secret", true).Return(nil)
nodeClient.EXPECT().CopyFile(mock.Anything, mock.Anything, mock.Anything).Return(nil).Twice()

err := bs.UpdateInstallConfig()
Expect(err).NotTo(HaveOccurred())

// IP should be updated to the PostgreSQLNode's InternalIP ("10.0.0.1" from fakeNode)
Expect(bs.Env.InstallConfig.Postgres.Primary.IP).To(Equal("10.0.0.1"))
// Key should be regenerated
Expect(bs.Env.InstallConfig.Postgres.Primary.PrivateKey).NotTo(Equal(origKey))
Expect(bs.Env.InstallConfig.Postgres.Primary.PrivateKey).NotTo(BeEmpty())
// New cert/key should match
err = installer.ValidateCertKeyPair(
bs.Env.InstallConfig.Postgres.Primary.SSLConfig.ServerCertPem,
bs.Env.InstallConfig.Postgres.Primary.PrivateKey,
)
Expect(err).NotTo(HaveOccurred())
})
})

Context("with empty PrivateKey (not loaded from vault)", func() {
BeforeEach(func() {
caKey, caCert, err := installer.GenerateCA("Test CA", "DE", "Berlin", "TestOrg")
Expect(err).NotTo(HaveOccurred())

csEnv.InstallConfig.Postgres.CaCertPrivateKey = caKey
csEnv.InstallConfig.Postgres.CACertPem = caCert
csEnv.InstallConfig.Postgres.Primary.IP = "10.0.0.1"
csEnv.InstallConfig.Postgres.Primary.Hostname = "postgres"
csEnv.InstallConfig.Postgres.Primary.PrivateKey = ""
})

It("generates new cert/key pair", func() {
icg.EXPECT().WriteInstallConfig("fake-config-file", true).Return(nil)
icg.EXPECT().WriteVault("fake-secret", true).Return(nil)
nodeClient.EXPECT().CopyFile(mock.Anything, mock.Anything, mock.Anything).Return(nil).Twice()

err := bs.UpdateInstallConfig()
Expect(err).NotTo(HaveOccurred())

Expect(bs.Env.InstallConfig.Postgres.Primary.PrivateKey).NotTo(BeEmpty())
Expect(bs.Env.InstallConfig.Postgres.Primary.SSLConfig.ServerCertPem).NotTo(BeEmpty())
err = installer.ValidateCertKeyPair(
bs.Env.InstallConfig.Postgres.Primary.SSLConfig.ServerCertPem,
bs.Env.InstallConfig.Postgres.Primary.PrivateKey,
)
Expect(err).NotTo(HaveOccurred())
})
})

Context("with missing CA cert (cert generation fails)", func() {
BeforeEach(func() {
csEnv.InstallConfig.Postgres.CaCertPrivateKey = ""
csEnv.InstallConfig.Postgres.CACertPem = ""
csEnv.InstallConfig.Postgres.Primary.IP = "10.0.0.1"
csEnv.InstallConfig.Postgres.Primary.Hostname = "postgres"
csEnv.InstallConfig.Postgres.Primary.PrivateKey = ""
})

It("returns an error", func() {
err := bs.UpdateInstallConfig()
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("failed to generate primary server certificate"))
})
})
})
})

Describe("EnsureAgeKey", func() {
Expand Down
16 changes: 15 additions & 1 deletion internal/installer/config_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,21 @@ func (g *InstallConfig) WriteVault(vaultPath string, withComments bool) error {
return fmt.Errorf("no configuration provided - config is nil")
}

vault := g.Config.AddSecretsToVault(g.GetVault())
vault := g.Config.ExtractVault()

// Preserve vault-only secrets that were added directly via SetSecret and aren't on the config struct.
if g.Vault != nil {
configSecretNames := make(map[string]bool)
for _, s := range vault.Secrets {
configSecretNames[s.Name] = true
}
for _, s := range g.Vault.Secrets {
if !configSecretNames[s.Name] {
vault.Secrets = append(vault.Secrets, s)
}
}
}

vaultYAML, err := vault.Marshal()
if err != nil {
return fmt.Errorf("failed to marshal vault.yaml: %w", err)
Expand Down
8 changes: 8 additions & 0 deletions internal/installer/config_manager_secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ func (g *InstallConfig) generatePostgresSecrets(config *files.RootConfig) error
return fmt.Errorf("failed to generate primary PostgreSQL certificate: %w", err)
}

if err := ValidateCertKeyPair(config.Postgres.Primary.SSLConfig.ServerCertPem, config.Postgres.Primary.PrivateKey); err != nil {
return fmt.Errorf("primary PostgreSQL cert/key validation failed: %w", err)
}

config.Postgres.AdminPassword = GeneratePassword(32)
config.Postgres.ReplicaPassword = GeneratePassword(32)

Expand All @@ -72,6 +76,10 @@ func (g *InstallConfig) generatePostgresSecrets(config *files.RootConfig) error
if err != nil {
return fmt.Errorf("failed to generate replica PostgreSQL certificate: %w", err)
}

if err := ValidateCertKeyPair(config.Postgres.Replica.SSLConfig.ServerCertPem, config.Postgres.ReplicaPrivateKey); err != nil {
return fmt.Errorf("replica PostgreSQL cert/key validation failed: %w", err)
}
}

services := []string{"auth", "deployment", "ide", "marketplace", "payment", "public_api", "team", "workspace"}
Expand Down
Loading
Loading