diff --git a/api/v1/softwarefactory_types.go b/api/v1/softwarefactory_types.go index 8e1d55a7..332ae966 100644 --- a/api/v1/softwarefactory_types.go +++ b/api/v1/softwarefactory_types.go @@ -546,9 +546,6 @@ type SoftwareFactorySpec struct { // at https://`service`.`FQDN` FQDN string `json:"fqdn"` - // LetsEncrypt settings for enabling using LetsEncrypt for Routes/TLS - LetsEncrypt *LetsEncryptSpec `json:"letsEncrypt,omitempty"` - // Enable log forwarding to a [Fluent Bit HTTP input](https://docs.fluentbit.io/manual/pipeline/inputs/http) FluentBitLogForwarding *FluentBitForwarderSpec `json:"FluentBitLogForwarding,omitempty"` diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go index 4acda3c9..2d3ec075 100644 --- a/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -430,11 +430,6 @@ func (in *SoftwareFactoryList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SoftwareFactorySpec) DeepCopyInto(out *SoftwareFactorySpec) { *out = *in - if in.LetsEncrypt != nil { - in, out := &in.LetsEncrypt, &out.LetsEncrypt - *out = new(LetsEncryptSpec) - **out = **in - } if in.FluentBitLogForwarding != nil { in, out := &in.FluentBitLogForwarding, &out.FluentBitLogForwarding *out = new(FluentBitForwarderSpec) diff --git a/cli/cmd/dev/dev.go b/cli/cmd/dev/dev.go index 8f65587d..56d52351 100644 --- a/cli/cmd/dev/dev.go +++ b/cli/cmd/dev/dev.go @@ -39,6 +39,7 @@ import ( "k8s.io/apimachinery/pkg/selection" "k8s.io/client-go/rest" + apiroutev1 "github.com/openshift/api/route/v1" "github.com/spf13/cobra" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -54,8 +55,16 @@ var defaultHost = "microshift.dev" var errMissingArg = errors.New("missing argument") -func createDemoEnv(env cliutils.ENV, restConfig *rest.Config, fqdn string, reposPath, sfOperatorRepoPath string, keepDemoTenantDefinition bool) { +func ensureGatewayRoute(env *cliutils.ENV, fqdn string) { + route := base.MkHTTPSRoute("sf-gateway", env.Ns, fqdn, "gateway", "/", 8080) + exists, _ := cliutils.GetM(env, "gateway", &apiroutev1.Route{}) + if !exists { + cliutils.CreateROrDie(env, &route) + } +} +func createDemoEnv(env cliutils.ENV, restConfig *rest.Config, fqdn string, reposPath, sfOperatorRepoPath string, keepDemoTenantDefinition bool) { + ensureGatewayRoute(&env, fqdn) gerrit.EnsureGerrit(&env, fqdn) ctrl.Log.Info("Making sure Gerrit is up and ready...") gerrit.EnsureGerritAccess(fqdn) diff --git a/cli/cmd/dev/gerrit/gerrit.go b/cli/cmd/dev/gerrit/gerrit.go index 28452707..7acf83a8 100644 --- a/cli/cmd/dev/gerrit/gerrit.go +++ b/cli/cmd/dev/gerrit/gerrit.go @@ -382,7 +382,7 @@ func (g *GerritCMDContext) ensureStatefulSetOrDie() { func (g *GerritCMDContext) ensureGerritIngressesOrDie() { name := "gerrit" route := base.MkHTTPSRoute(name, g.env.Ns, name+"."+g.fqdn, - gerritHTTPDPortName, "/", gerritHTTPDPort, map[string]string{}, nil) + gerritHTTPDPortName, "/", gerritHTTPDPort) g.ensureRouteOrDie(route) } diff --git a/cli/cmd/initialize.go b/cli/cmd/initialize.go index 6d4703f7..2c2427d9 100644 --- a/cli/cmd/initialize.go +++ b/cli/cmd/initialize.go @@ -118,11 +118,6 @@ func initializeSFManifest(withAuth bool, withBuilder bool, full bool, connection manifest.Spec.GitServer.Storage.Size = oneGi - leSpec := sfv1.LetsEncryptSpec{ - Server: sfv1.LEServerStaging, - } - manifest.Spec.LetsEncrypt = &leSpec - manifest.Spec.MariaDB.DBStorage.Size = oneGi manifest.Spec.MariaDB.LogStorage.Size = oneGi diff --git a/cli/cmd/sf.go b/cli/cmd/sf.go index bf23fd96..1cefa982 100644 --- a/cli/cmd/sf.go +++ b/cli/cmd/sf.go @@ -21,51 +21,20 @@ package cmd */ import ( - "errors" - "os" - bootstraptenantconfigrepo "github.com/softwarefactory-project/sf-operator/cli/cmd/bootstrap-tenant-config-repo" "github.com/spf13/cobra" - ctrl "sigs.k8s.io/controller-runtime" ) -func sfConfigureCmd(kmd *cobra.Command, args []string) { - if args[0] == "TLS" { - TLSConfigureCmd(kmd, args) - } else { - ctrl.Log.Error(errors.New("unknown argument"), args[0]+" is not a supported target") - os.Exit(1) - } -} - func MkSFCmd() *cobra.Command { - var ( - CAPath string - CertificatePath string - KeyPath string - - sfCmd = &cobra.Command{ - Use: "SF", - Short: "subcommands related to managing a Software Factory resource", - Long: `Use these subcommands to perform management tasks at the resource level.`, - } - - configureCmd = &cobra.Command{ - Use: "configure {TLS}", - Short: "configure {TLS}", - Long: "Configure a SF resource. The resource can be the TLS certificates", - ValidArgs: []string{"TLS"}, - Run: sfConfigureCmd, - } - ) - configureCmd.Flags().StringVar(&CAPath, "CA", "", "path to the PEM-encoded Certificate Authority file") - configureCmd.Flags().StringVar(&CertificatePath, "cert", "", "path to the domain certificate file") - configureCmd.Flags().StringVar(&KeyPath, "key", "", "path to the private key file") + var sfCmd = &cobra.Command{ + Use: "SF", + Short: "subcommands related to managing a Software Factory resource", + Long: `Use these subcommands to perform management tasks at the resource level.`, + } sfCmd.AddCommand(MkBackupCmd()) sfCmd.AddCommand(MkRestoreCmd()) - sfCmd.AddCommand(configureCmd) sfCmd.AddCommand(bootstraptenantconfigrepo.MkBootstrapCmd()) return sfCmd diff --git a/cli/cmd/tls.go b/cli/cmd/tls.go deleted file mode 100644 index 74f2d5fb..00000000 --- a/cli/cmd/tls.go +++ /dev/null @@ -1,144 +0,0 @@ -/* -Copyright © 2023 Red Hat - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package cmd - -/* -"tls" subcommand configures TLS certificates for a SF instance. -*/ - -import ( - "context" - "crypto/x509" - "encoding/pem" - "errors" - "os" - - cliutils "github.com/softwarefactory-project/sf-operator/cli/cmd/utils" - sf "github.com/softwarefactory-project/sf-operator/controllers" - - "github.com/spf13/cobra" - apiv1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - ctrl "sigs.k8s.io/controller-runtime" -) - -func ensureTLSSecret(env *cliutils.ENV, CAContents []byte, - CertificateContents []byte, KeyContents []byte) { - var secret apiv1.Secret - secretName := sf.CustomSSLSecretName - data := map[string][]byte{ - "CA": CAContents, - "crt": CertificateContents, - "key": KeyContents, - } - if !cliutils.GetMOrDie(env, secretName, &secret) { - // Create the secret as it does not exists - secret := apiv1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: secretName, - Namespace: env.Ns, - }, - Data: data, - } - cliutils.CreateROrDie(env, &secret) - } else { - // Update the secret data - secret.Data = data - cliutils.UpdateROrDie(env, &secret) - } -} - -func verifyCertificates(CAContents []byte, CertificateContents []byte, - KeyContents []byte, serverName string) error { - // Verify if provided cert is correct - decodedCACert, _ := pem.Decode(CAContents) - if decodedCACert == nil { - ctrl.Log.Error(errors.New("no PEM data found"), - "Error while PEM-decoding the Certificate Authority's certificate") - os.Exit(1) - } - caCert, err := x509.ParseCertificate(decodedCACert.Bytes) - if err != nil { - ctrl.Log.Error(err, "Error parsing the Certificate Authority's certificate") - os.Exit(1) - } - - decodedClientCert, _ := pem.Decode(CertificateContents) - clientCert, err := x509.ParseCertificate(decodedClientCert.Bytes) - if err != nil { - ctrl.Log.Error(err, "Error parsing the certificate") - os.Exit(1) - } - - roots := x509.NewCertPool() - roots.AddCert(caCert) - - opts := x509.VerifyOptions{ - Roots: roots, - Intermediates: roots, - DNSName: serverName, - } - - _, err = clientCert.Verify(opts) - return err -} - -func configureTLS(ns string, kubeContext string, CAPath string, CertificatePath string, KeyPath string, fqdn string) { - var err error - var CAContents, CertificateContents, KeyContents []byte - if CAContents, err = cliutils.GetFileContent(CAPath); err != nil { - ctrl.Log.Error(err, "Error opening "+CAPath) - os.Exit(1) - } - if CertificateContents, err = cliutils.GetFileContent(CertificatePath); err != nil { - ctrl.Log.Error(err, "Error opening "+CertificatePath) - os.Exit(1) - } - if KeyContents, err = cliutils.GetFileContent(KeyPath); err != nil { - ctrl.Log.Error(err, "Error opening "+KeyPath) - os.Exit(1) - } - if CAContents == nil || CertificateContents == nil || KeyContents == nil { - ctrl.Log.Error(errors.New("empty file"), "At least one of the provided files has no contents") - os.Exit(1) - } - if err = verifyCertificates(CAContents, CertificateContents, KeyContents, fqdn); err != nil { - ctrl.Log.Error(err, "Certificates verification failed") - os.Exit(1) - } - env := cliutils.ENV{ - Cli: cliutils.CreateKubernetesClientOrDie(kubeContext), - Ctx: context.TODO(), - Ns: ns, - } - ensureTLSSecret(&env, CAContents, CertificateContents, KeyContents) -} - -func TLSConfigureCmd(kmd *cobra.Command, args []string) { - cliCtx, err := cliutils.GetCLIContext(kmd) - if err != nil { - ctrl.Log.Error(err, "Error initializing") - os.Exit(1) - } - ns := cliCtx.Namespace - kubeContext := cliCtx.KubeContext - fqdn := cliCtx.FQDN - CAPath, _ := kmd.Flags().GetString("CA") - CertificatePath, _ := kmd.Flags().GetString("cert") - KeyPath, _ := kmd.Flags().GetString("key") - configureTLS(ns, kubeContext, CAPath, CertificatePath, KeyPath, fqdn) -} diff --git a/config/crd/bases/sf.softwarefactory-project.io_softwarefactories.yaml b/config/crd/bases/sf.softwarefactory-project.io_softwarefactories.yaml index 00e80b0e..975f3228 100644 --- a/config/crd/bases/sf.softwarefactory-project.io_softwarefactories.yaml +++ b/config/crd/bases/sf.softwarefactory-project.io_softwarefactories.yaml @@ -137,23 +137,6 @@ spec: - size type: object type: object - letsEncrypt: - description: LetsEncrypt settings for enabling using LetsEncrypt for - Routes/TLS - properties: - server: - description: |- - Specify the Lets encrypt server. - Valid values are: - "staging", - "prod" - enum: - - prod - - staging - type: string - required: - - server - type: object logserver: default: loopDelay: 3600 diff --git a/controllers/gateway.go b/controllers/gateway.go index 80a0515a..6ce9b8d5 100644 --- a/controllers/gateway.go +++ b/controllers/gateway.go @@ -64,13 +64,5 @@ func (r *SFController) DeployHTTPDGateway() bool { r.CreateR(¤t) } - isDeploymentReady := r.IsDeploymentReady(¤t) - - routeReady := r.ensureHTTPSRoute( - ident, r.cr.Spec.FQDN, - ident, "/", port, map[string]string{}, r.cr.Spec.LetsEncrypt) - - isReady := isDeploymentReady && routeReady - - return isReady + return r.IsDeploymentReady(¤t) } diff --git a/controllers/libs/base/base.go b/controllers/libs/base/base.go index b0ed5223..b19fef9e 100644 --- a/controllers/libs/base/base.go +++ b/controllers/libs/base/base.go @@ -329,20 +329,15 @@ func MkHeadlessServicePod(name string, ns string, podName string, ports []int32, // MkHTTPSRoute produces a Route on top of a Service func MkHTTPSRoute( - name string, ns string, host string, serviceName string, path string, - port int, annotations map[string]string, customTLS *apiroutev1.TLSConfig) apiroutev1.Route { + name string, ns string, host string, serviceName string, path string, port int) apiroutev1.Route { tls := apiroutev1.TLSConfig{ InsecureEdgeTerminationPolicy: apiroutev1.InsecureEdgeTerminationPolicyRedirect, Termination: apiroutev1.TLSTerminationEdge, } - if customTLS != nil { - tls = *customTLS - } return apiroutev1.Route{ ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: ns, - Annotations: annotations, + Name: name, + Namespace: ns, }, Spec: apiroutev1.RouteSpec{ TLS: &tls, diff --git a/controllers/softwarefactory_controller.go b/controllers/softwarefactory_controller.go index 58e59aad..5cfdade6 100644 --- a/controllers/softwarefactory_controller.go +++ b/controllers/softwarefactory_controller.go @@ -102,79 +102,31 @@ func isOperatorReady(services map[string]bool) bool { // cleanup ensures removal of legacy resources func (r *SFController) cleanup() { - r.DeleteR(&corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: r.ns, - Name: BuildLogsHttpdPortName, - }, - }) - - // clean up old podmonitors if they exist. Remove after next release - currentZKpm := monitoringv1.PodMonitor{} - if r.GetM(ZookeeperIdent+"-monitor", ¤tZKpm) { - r.DeleteR(¤tZKpm) - } - currentDBpm := monitoringv1.PodMonitor{} - if r.GetM(MariaDBIdent+"-monitor", ¤tDBpm) { - r.DeleteR(¤tDBpm) - } - currentGSpm := monitoringv1.PodMonitor{} - if r.GetM(GitServerIdent+"-monitor", ¤tGSpm) { - r.DeleteR(¤tGSpm) - } - currentZPM := monitoringv1.PodMonitor{} - if r.GetM("zuul-monitor", ¤tZPM) { - r.DeleteR(¤tZPM) - } - currentNPM := monitoringv1.PodMonitor{} - if r.GetM("nodepool-monitor", ¤tNPM) { - r.DeleteR(¤tNPM) - } - // remove a legacy Route definition for Zuul - r.DeleteR(&apiroutev1.Route{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: r.ns, - Name: r.cr.Spec.FQDN + "-zuul-red", - }, - }) - // Remove unneeded extra Service resource for Zookeeper - currentZKHeadlessSVC := corev1.Service{} - if r.GetM("zookeeper-headless", ¤tZKHeadlessSVC) { - r.DeleteR(¤tZKHeadlessSVC) - } - // remove a legacy Route definition for logserver + // remove a legacy Route definition for gateway r.DeleteR(&apiroutev1.Route{ ObjectMeta: metav1.ObjectMeta{ Namespace: r.ns, - Name: r.cr.Name + "-logserver", + Name: "gateway", }, }) - // remove a legacy Route definition for icons path - r.DeleteR(&apiroutev1.Route{ + // remove managed certificate resource + r.DeleteR(&certv1.Certificate{ ObjectMeta: metav1.ObjectMeta{ + Name: "sf-le-certificate", Namespace: r.ns, - Name: r.cr.Name + "-icons", }, }) - // remove a legacy Route definition for nodepool-builder - r.DeleteR(&apiroutev1.Route{ + // remove managed cert-manager issuers + r.DeleteR(&certv1.Issuer{ ObjectMeta: metav1.ObjectMeta{ + Name: "cm-le-issuer-production", Namespace: r.ns, - Name: r.cr.Name + "-nodepool-builder", }, }) - // remove a legacy Route definition for nodepool-launcher - r.DeleteR(&apiroutev1.Route{ + r.DeleteR(&certv1.Issuer{ ObjectMeta: metav1.ObjectMeta{ + Name: "cm-le-issuer-staging", Namespace: r.ns, - Name: r.cr.Name + "-nodepool-launcher", - }, - }) - // remove a legacy Route definition for zuul - r.DeleteR(&apiroutev1.Route{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: r.ns, - Name: r.cr.Name + "-zuul", }, }) } @@ -248,11 +200,6 @@ func (r *SFController) deploySFStep(services map[string]bool) map[string]bool { // Setup a Self-Signed certificate issuer r.EnsureLocalCA() - // Setup LetsEncrypt Issuer if needed - if r.cr.Spec.LetsEncrypt != nil { - r.ensureLetsEncryptIssuer(*r.cr.Spec.LetsEncrypt) - } - // Ensure SF Admin ssh key pair r.DeployZuulSecrets() diff --git a/controllers/utils.go b/controllers/utils.go index 935ef124..e4e8dc3c 100644 --- a/controllers/utils.go +++ b/controllers/utils.go @@ -26,7 +26,6 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" - certv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" appsv1 "k8s.io/api/apps/v1" apiv1 "k8s.io/api/core/v1" @@ -39,7 +38,6 @@ import ( sfmonitoring "github.com/softwarefactory-project/sf-operator/controllers/libs/monitoring" "github.com/softwarefactory-project/sf-operator/controllers/libs/utils" - apiroutev1 "github.com/openshift/api/route/v1" sfv1 "github.com/softwarefactory-project/sf-operator/api/v1" ) @@ -279,93 +277,6 @@ func (r *SFUtilContext) EnsureService(service *apiv1.Service) { } } -// ensureRoute ensures the Route exist -// The Route is updated if needed -// The function returns false when the resource is just created/updated -func (r *SFUtilContext) ensureRoute(route apiroutev1.Route, name string) bool { - current := apiroutev1.Route{} - found := r.GetM(name, ¤t) - if !found { - utils.LogI("Creating route, name: " + name) - r.CreateR(&route) - return false - } else { - // Route already exist - check if we need to update the Route - needUpdate := false - - // First check the route annotations - if (len(route.Annotations) == 0 && len(current.Annotations) != 0) || (len(route.Annotations) != 0 && len(current.Annotations) == 0) { - current.Annotations = route.Annotations - needUpdate = true - } - if len(route.Annotations) != 0 && len(current.Annotations) != 0 { - if !utils.MapEquals(&route.Annotations, ¤t.Annotations) { - current.Annotations = route.Annotations - needUpdate = true - } - } - - // Use the String repr of the RouteSpec to compare for Spec changes - // This comparaison mechanics may fail in case of some Route Spec default values - // not specified in the wanted version. - if route.Spec.String() != current.Spec.String() { - current.Spec = route.Spec - needUpdate = true - } - - if needUpdate { - utils.LogI("Updating route, name: " + name) - r.UpdateR(¤t) - return false - } - } - return true -} - -// ensureHTTPSRoute ensures a HTTPS enabled Route exist -// The Route is updated if needed -// The function returns false when the controller reconcile loop must be re-triggered because -// the route setting changed. -func (r *SFUtilContext) ensureHTTPSRoute( - name string, host string, serviceName string, path string, - port int, annotations map[string]string, le *sfv1.LetsEncryptSpec) bool { - - tlsDataReady := true - var sslCA, sslCrt, sslKey []byte - - if le == nil { - // Letsencrypt config has not been set so we check the `customSSLSecretName` Secret - // for any custom TLS data to setup the Route - sslCA, sslCrt, sslKey = r.extractStaticTLSFromSecret() - } else { - // Letsencrypt config has been set so we ensure we set a Certificate via the - // cert-manager Issuer and then we'll setup the Route based on the Certificate's Secret - tlsDataReady, sslCA, sslCrt, sslKey = r.extractTLSFromLECertificateSecret(host, *le) - } - - if !tlsDataReady { - return false - } - - var route apiroutev1.Route - - // Checking if there is any content and setting the Route with TLS data from the Secret - if len(sslCrt) > 0 && len(sslKey) > 0 { - utils.LogI(fmt.Sprintf("SSL certificate for Route detected, host: %s, route name: %s", host, name)) - tls := apiroutev1.TLSConfig{ - InsecureEdgeTerminationPolicy: apiroutev1.InsecureEdgeTerminationPolicyRedirect, - Termination: apiroutev1.TLSTerminationEdge, - Certificate: string(sslCrt), - Key: string(sslKey), - CACertificate: string(sslCA), - } - route = base.MkHTTPSRoute(name, r.ns, host, serviceName, path, port, annotations, &tls) - } else { - route = base.MkHTTPSRoute(name, r.ns, host, serviceName, path, port, annotations, nil) - } - return r.ensureRoute(route, name) -} - // EnsureLocalCA ensures cert-manager resources exists to enable of local CA Issuer // This function does not support update func (r *SFUtilContext) EnsureLocalCA() { @@ -381,32 +292,6 @@ func (r *SFUtilContext) EnsureLocalCA() { r.GetOrCreate(&rootCACertificate) } -// --- Functions and Structs below are helper for handle cert-manager / Let's Encrypt --- - -// getLetsEncryptServer returns a tuple with the production or statging URL based on sfv1.LetsEncryptSpec -// and the a proposed name for the Issuer. -func getLetsEncryptServer(le sfv1.LetsEncryptSpec) (string, string) { - var serverURL string - name := "" - switch server := le.Server; server { - case sfv1.LEServerProd: - serverURL = "https://acme-v02.api.letsencrypt.org/directory" - name = "cm-le-issuer-production" - case sfv1.LEServerStaging: - serverURL = "https://acme-staging-v02.api.letsencrypt.org/directory" - name = "cm-le-issuer-staging" - } - return serverURL, name -} - -// This function ensures the cert-manager / Let's Encrypt issuer is created -// Thus function does not support update -func (r *SFUtilContext) ensureLetsEncryptIssuer(le sfv1.LetsEncryptSpec) bool { - server, name := getLetsEncryptServer(le) - issuer := cert.MkLetsEncryptIssuer(name, r.ns, server) - return r.GetOrCreate(&issuer) -} - //---------------------------------------------------------------------------- // --- TODO clean functions below / remove useless code --- //---------------------------------------------------------------------------- @@ -490,67 +375,6 @@ func (r *SFUtilContext) DebugStatefulSet(name string) { utils.LogI("Debugging service, name: " + name) } -// extractStaticTLSFromSecret gets secret keys from sf-ssl-cert secret -// Returns CA, key and crt keys. -func (r *SFUtilContext) extractStaticTLSFromSecret() ([]byte, []byte, []byte) { - var customSSLSecret apiv1.Secret - - if !r.GetM(CustomSSLSecretName, &customSSLSecret) { - return nil, nil, nil - } else { - // Fetching secret expected TLS Keys content - return customSSLSecret.Data["CA"], customSSLSecret.Data["crt"], customSSLSecret.Data["key"] - } -} - -// extractTLSFromLECertificateSecret gets LetsEncrypt Certificate -func (r *SFUtilContext) extractTLSFromLECertificateSecret(host string, le sfv1.LetsEncryptSpec) (bool, []byte, []byte, []byte) { - _, issuerName := getLetsEncryptServer(le) - const sfLECertName = "sf-le-certificate" - dnsNames := []string{host} - certificate := cert.MkCertificate(sfLECertName, r.ns, issuerName, dnsNames, sfLECertName+"-tls", nil) - - current := certv1.Certificate{} - - found := r.GetM(sfLECertName, ¤t) - if !found { - utils.LogI("Creating Cert-Manager LetsEncrypt Certificate, name: " + sfLECertName) - r.CreateR(&certificate) - return false, nil, nil, nil - } else { - if current.Spec.IssuerRef.Name != certificate.Spec.IssuerRef.Name || - !reflect.DeepEqual(current.Spec.DNSNames, certificate.Spec.DNSNames) { - // We need to update the Certficate - utils.LogI("Updating Cert-Manager LetsEncrypt Certificate, name: " + sfLECertName) - current.Spec = *certificate.Spec.DeepCopy() - r.UpdateR(¤t) - return false, nil, nil, nil - } - // The certificate is found and have the required Spec, so let's check - // the Ready status - ready := cert.IsCertificateReady(¤t) - - if ready { - utils.LogI("Cert-Manager LetsEncrypt Certificate is Ready: name: " + sfLECertName) - var leSSLSecret apiv1.Secret - if r.GetM(current.Spec.SecretName, &leSSLSecret) { - // Extract the TLS material - return true, nil, leSSLSecret.Data["tls.crt"], leSSLSecret.Data["tls.key"] - // Nothing more to do the rest of the function will setup the Route's TLS - } else { - // We are not able to find the Certificate's secret - utils.LogI(fmt.Sprintf("Cert-Manager LetsEncrypt Certificate is Ready but waiting for the Secret, name: %s, secret: %s", - sfLECertName, current.Spec.SecretName)) - return false, nil, nil, nil - } - } else { - // Return false to force a new Reconcile as the certificate is not Ready yet - utils.LogI("Cert-Manager LetsEncrypt Certificate is not Ready yet, name: " + sfLECertName) - return false, nil, nil, nil - } - } -} - // GetConfigMap Get ConfigMap by name func (r *SFUtilContext) GetConfigMap(name string) (apiv1.ConfigMap, error) { var dep apiv1.ConfigMap diff --git a/doc/deployment/certificates.md b/doc/deployment/certificates.md deleted file mode 100644 index 77ed37a2..00000000 --- a/doc/deployment/certificates.md +++ /dev/null @@ -1,90 +0,0 @@ -# Set up TLS on service routes - - -1. [Using a trusted Certificate Authority](#using-a-trusted-certificate-authority) -1. [Using Let's Encrypt](#using-lets-encrypt) - -By default, a SF deployment comes with a self-signed Certificate Authority delivered by the Ingress manager, and HTTPS services in the deployment get certificates from this CA to enable secure ingress with TLS. - -Currently, the list of concerned HTTPS services is: - -- logserver HTTP endpoint -- nodepool web API and nodepool build's logs -- zuul-web - -While this is good enough for testing, the sf-operator allows you to integrate your deployment with an existing, trusted Certificate Authority, or even use [Let's Encrypt](https://letsencrypt.org/). - -## Using a trusted Certificate Authority - -The operator watches a `Secret` named `sf-ssl-cert` in the `SoftwareFactory` Custom Resources namespace. -When this `Secret`'s data hold a Certificate, Key and CA Certificate (following a specific scheme) then -the sf-operator is able to reconfigure all managed `Route`'s TLS to use the TLS material stored in the secret. - -!!! note - Make sure that the certificate `CN` matches the `fqdn` setting of the `SoftwareFactory` Custom Resource. - -The [sf-operator](./../reference/cli/index.md) CLI can be used to configure these secrets. - -!!! note - The [`SF configure TLS`](./../reference/cli/index.md#configure-tls) subcommand will validate the SSL certificate/key before updating the `Secret`. - -```sh -sf-operator SF configure TLS --CA /tmp/ssl/localCA.pem \ - --key /tmp/ssl/ssl.key \ - --cert /tmp/ssl/ssl.crt -``` - -Alternatively, the `sf-ssl-cert` `Secret` resource can be managed without the CLI by -following this Secret's layout: - -```yaml -apiVersion: v1 -kind: Secret -metadata: - name: sf-ssl-cert -data: - CA: "" - crt: "" - key: "" -``` - -The `SoftwareFactory` Custom Resource will pass into a "non Ready" state until reconfiguration is completed. -Once `Ready`, all managed `Route` will present the new certificate. - - -## Using Let's Encrypt - -The SF Operator offers an option to request a certificate from `Let's Encrypt` using the `ACME http01` -challenge. The deployment `FQDN` must be publicly resolvable. - -!!! note - This overrides the custom X509 certificates that might have been set following the [steps above](#using-a-trusted-certificate-authority). - -1. test your deployment with Let's Encrypt's staging server: - -```sh -kubectl edit sf my-sf -[...] -spec: - letsEncrypt: - server: "staging" -[...] -``` - -The `SoftwareFactory` CR will pass into a "non Ready" state until all `Challenges` are resolved -and all the `Routes` are reconfigured. - -2. Set the server to `prod` to use the Let's Encrypt production server: - -```sh -kubectl edit sf my-sf -[...] -spec: - letsEncrypt: - server: "prod" -[...] -``` - -Once the `SoftwareFactory` Custom Resource is ready, all managed `Route` will present the new certificate -issued by Let's Encrypt. - diff --git a/doc/deployment/getting_started.md b/doc/deployment/getting_started.md index 061df8dc..ea150e21 100644 --- a/doc/deployment/getting_started.md +++ b/doc/deployment/getting_started.md @@ -62,8 +62,31 @@ NAME READY my-sf true ``` -The `sf-operator` handles the `Route`s installation. Here is the lists of available -endpoints: +The `sf-operator` does not handle the `Route/Ingress`s installation. + +The following resource can be applied in the namespace to setup a `Route` and redirects +traffic to the gateway service. + +```yaml +apiVersion: route.openshift.io/v1 +kind: Route +metadata: + name: gateway + namespace: sf +spec: + host: sfop.me + path: / + port: + targetPort: 8080 + to: + Kind: Service + name: gateway + tls: + insecureEdgeTerminationPolicy: Redirect + termination: edge +``` + +On the "Route/Ingress" resource is up, here is the lists of available endpoints: - https://sfop.me/zuul - https://sfop.me/logs diff --git a/doc/developer/getting_started.md b/doc/developer/getting_started.md index 3dff07db..fa460bc6 100644 --- a/doc/developer/getting_started.md +++ b/doc/developer/getting_started.md @@ -72,6 +72,7 @@ go run ./main.go --config /path/to/sfcli.yaml dev create demo-env - ensure the deployment of a test Gerrit instance - ensure the checkout of the `config`, `demo-tenant-config` and `demo-project` git repositories in the directory of your choosing (defaults to `deploy`) - ensure the configuration of a test openshiftpods provider for nodepool +- ensure a Route called "sf-gateway" to target the "gateway" Service The context is now ready to run the sf-operator using the `manager` or the `standalone` modes. diff --git a/doc/reference/CHANGELOG.md b/doc/reference/CHANGELOG.md index 2f2c4fff..ef1906a5 100644 --- a/doc/reference/CHANGELOG.md +++ b/doc/reference/CHANGELOG.md @@ -5,12 +5,16 @@ All notable changes to this project will be documented in this file. ### Added - - CLI - add the 'debug' flag to set the LogLevel to DEBUG - - Log Forwarding - add preset label "labels_app" by default, its value is "sf" +- CLI - add the 'debug' flag to set the LogLevel to DEBUG +- Log Forwarding - add preset label "labels_app" by default, its value is "sf" +- doc - how to set the Route for a deployment of Sofware Factory services ### Changed ### Deprecated ### Removed + +- Route resource and TLS related handling and CLI facilities has been removed + ### Fixed - zuul-merger: CRD logLevel parameter not handled correctly diff --git a/doc/reference/adr/0015-route-handling.md b/doc/reference/adr/0015-route-handling.md new file mode 100644 index 00000000..ae06912d --- /dev/null +++ b/doc/reference/adr/0015-route-handling.md @@ -0,0 +1,40 @@ +--- +status: accepted +date: 2024-06-07 +revise: 0007-edge-cert.md +--- + +# Route management + +## Context and Problem Statement + +It has been decided earlier in this ADR: [Edge cert ADR](./0007-edge-cert.md) to handle **Route** resources +and TLS (Let'sEncrypt, static certs) integration for the user. It appears that this choice might not be the right one as: + +- A **Route** is only specific to OpenShift and prevent usage on Kubernetes +- A **Route** controller is custom to the Cluster and underlying infrastructure thus **Route** resources + might need extra settings (labels, ...) to be functional. +- A **Route** might need to be amended by extra controller to handle the TLS section. +- A **Route** might not be need when a Service Type LoadBalancer and an external DNS is used. + +## Considered Options + +* 1. Make the **Route** optional and keep the current code: + +Pros and Cons of this option: + +* Good because the sf-operator provide more optionalities and functionalities +* Bad because we need to maintain more code and more doc + +* 2. Remove the **Route** handling and clean extra code + +Pros and Cons of this option: + +* Good because, sf-operator become more agnostic regarding OpenShift or Kubernetes +* Good because we simplify the code base and documentation + +## Decision Outcome + +Chosen option: + +* 2. Remove the **Route** handling and clean extra code \ No newline at end of file diff --git a/doc/reference/api/index.md b/doc/reference/api/index.md index d35257dc..1f2727e5 100644 --- a/doc/reference/api/index.md +++ b/doc/reference/api/index.md @@ -189,18 +189,6 @@ _Appears in:_ -#### LetsEncryptSpec - - - - - -_Appears in:_ -- [SoftwareFactorySpec](#softwarefactoryspec) - -| Field | Description | Default Value | -| --- | --- | --- | -| `server` _[LEServer](#leserver)_ | Specify the Lets encrypt server. Valid values are: "staging", "prod" | -| #### LimitsSpec @@ -403,7 +391,6 @@ _Appears in:_ | Field | Description | Default Value | | --- | --- | --- | | `fqdn` _string_ | The fully qualified domain name to use with the deployment. Relevant services will be served at https://`service`.`FQDN` | -| -| `letsEncrypt` _[LetsEncryptSpec](#letsencryptspec)_ | LetsEncrypt settings for enabling using LetsEncrypt for Routes/TLS | -| | `FluentBitLogForwarding` _[FluentBitForwarderSpec](#fluentbitforwarderspec)_ | Enable log forwarding to a [Fluent Bit HTTP input](https://docs.fluentbit.io/manual/pipeline/inputs/http) | -| | `storageClassName` _string_ | Default storage class to use by Persistent Volume Claims | {topolvm-provisioner}| | `config-location` _[ConfigRepositoryLocationSpec](#configrepositorylocationspec)_ | Config repository spec | -| diff --git a/mkdocs.yml b/mkdocs.yml index 24a38a58..cba75027 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -77,7 +77,6 @@ nav: - Zuul: deployment/zuul.md - Zuul External Executor: deployment/external-executor.md - TLS: - - Setting up certificates: deployment/certificates.md - Adding third-party certificates into the CA trust chain: deployment/corporate-certificates.md - Monitoring: deployment/monitoring.md - Logging: deployment/logging.md diff --git a/roles/health-check/test-cert-manager-letsencrypt/defaults/main.yaml b/roles/health-check/test-cert-manager-letsencrypt/defaults/main.yaml deleted file mode 100644 index 31eb8460..00000000 --- a/roles/health-check/test-cert-manager-letsencrypt/defaults/main.yaml +++ /dev/null @@ -1,2 +0,0 @@ ---- -fqdn: sfop.me diff --git a/roles/health-check/test-cert-manager-letsencrypt/tasks/main.yaml b/roles/health-check/test-cert-manager-letsencrypt/tasks/main.yaml deleted file mode 100644 index ad0be3a6..00000000 --- a/roles/health-check/test-cert-manager-letsencrypt/tasks/main.yaml +++ /dev/null @@ -1,32 +0,0 @@ ---- -- name: Update SF CR to set Let's Encrypt - ansible.builtin.include_role: - name: "update-custom-resource" - vars: - check_sf_resource_ready: false - cr_spec: - letsEncrypt: - server: "staging" - -# We do not wait for the CR status to be ready because it will never happen as the -# CI deployment Route URL/Host cannot be resolved and thus the http01 challenge will -# fails. -# However here we are able to check that a 'Certificate resources''s challenge is created but -# does not become ready due to http01 challenge failure. -# This partialy verifies the flow with Let's Encrypt. - -- name: Ensure nodepool service Certificate not Ready for expected reason - ansible.builtin.shell: "kubectl -n sf get challenge -o json | grep {{ fqdn }}" - register: result - until: - - result is success - - "'DNS problem: NXDOMAIN' in result.stdout or 'Waiting for HTTP-01 challenge propagation' in result.stdout or 'no such host' in result.stdout" - retries: 6 - delay: 10 - -- name: Update SF CR to unset Let's Encrypt - ansible.builtin.include_role: - name: "update-custom-resource" - vars: - cr_spec: - letsEncrypt: diff --git a/roles/health-check/test-custom-certs/defaults/main.yaml b/roles/health-check/test-custom-certs/defaults/main.yaml deleted file mode 100644 index fa3dc635..00000000 --- a/roles/health-check/test-custom-certs/defaults/main.yaml +++ /dev/null @@ -1,9 +0,0 @@ ---- -org_name: SoftwareFactory -organizational_unit_name: Test -country_name: PL -state_or_province_name: Dolnoslaskie -locality_name: Wroclaw -fqdn: sfop.me -ca_common_name: "{{ fqdn }}" -common_name: "{{ ca_common_name }}" diff --git a/roles/health-check/test-custom-certs/tasks/ca.yaml b/roles/health-check/test-custom-certs/tasks/ca.yaml deleted file mode 100644 index 2643b4ad..00000000 --- a/roles/health-check/test-custom-certs/tasks/ca.yaml +++ /dev/null @@ -1,34 +0,0 @@ ---- -- name: Gen CA privkey - community.crypto.openssl_privatekey: - path: "{{ ssl_path }}/localCA.key" - -- name: Generate CA CSR - community.crypto.openssl_csr: - path: "{{ ssl_path }}/localCA.csr" - privatekey_path: "{{ ssl_path }}/localCA.key" - country_name: "{{ country_name }}" - organization_name: "{{ org_name }}" - common_name: "{{ ca_common_name }}" - state_or_province_name: "{{ random_state_or_province_name }}" - locality_name: "{{ locality_name }}" - organizational_unit_name: "{{ organizational_unit_name }}" - basic_constraints: - - CA:TRUE - key_usage: - - cRLSign - - keyCertSign - - keyEncipherment - - digitalSignature - key_usage_critical: true - extended_key_usage: - - clientAuth - - serverAuth - -- name: Generate selfsigned CA certificate - openssl_certificate: - path: "{{ ssl_path }}/localCA.pem" - csr_path: "{{ ssl_path }}/localCA.csr" - privatekey_path: "{{ ssl_path }}/localCA.key" - provider: selfsigned - selfsigned_digest: sha256 diff --git a/roles/health-check/test-custom-certs/tasks/check_ca_trust_chain.yaml b/roles/health-check/test-custom-certs/tasks/check_ca_trust_chain.yaml deleted file mode 100644 index 150d3c4e..00000000 --- a/roles/health-check/test-custom-certs/tasks/check_ca_trust_chain.yaml +++ /dev/null @@ -1,14 +0,0 @@ ---- -- name: Check that ca-bundle contains the localCA certificate - ansible.builtin.shell: | - kubectl exec {{ item }} -- bash -c "grep '{{ ca_common_name }}' /etc/pki/tls/certs/ca-bundle.crt" - register: witness - until: witness is success - loop: - - sts/zuul-scheduler - - sts/zuul-merger - - sts/zuul-executor - - sts/nodepool-builder - - deploy/nodepool-launcher - delay: 5 - retries: 6 diff --git a/roles/health-check/test-custom-certs/tasks/check_route.yaml b/roles/health-check/test-custom-certs/tasks/check_route.yaml deleted file mode 100644 index b8944415..00000000 --- a/roles/health-check/test-custom-certs/tasks/check_route.yaml +++ /dev/null @@ -1,32 +0,0 @@ ---- -- name: Get new certificate content - ansible.builtin.shell: cat {{ ssl_path }}/ssl.crt - register: _ssl_content - -- name: Verify that the SSL is in the secret - ansible.builtin.shell: > - kubectl get secret sf-ssl-cert -o json | - jq -r ".data.crt" | base64 -d - register: _new_cert_secret - -- name: Ensure that the cert and secret cert are same - ansible.builtin.assert: - that: - - _ssl_content.stdout == _new_cert_secret.stdout - -# From here we validate all Route handled by the sf-operator to ensure that curl -# is exposed to the right certificate - -- name: Make a query to validate that the Routes expose the expected certificate - ansible.builtin.shell: curl -kv https://{{ item }} - register: _new_cert_subj - loop: - - "{{ zuul_endpoint }}" - - "{{ nodepool_endpoint }}/builds" - - "{{ nodepool_endpoint }}/api" - - "{{ logserver_endpoint }}" - until: - - ca_common_name in _new_cert_subj.stderr or ca_common_name in _new_cert_subj.stdout - - random_state_or_province_name in _new_cert_subj.stderr or random_state_or_province_name in _new_cert_subj.stdout - retries: 5 - delay: 3 diff --git a/roles/health-check/test-custom-certs/tasks/client-cert.yaml b/roles/health-check/test-custom-certs/tasks/client-cert.yaml deleted file mode 100644 index 1d2914a6..00000000 --- a/roles/health-check/test-custom-certs/tasks/client-cert.yaml +++ /dev/null @@ -1,23 +0,0 @@ ---- -- name: Gen privkey - client - community.crypto.openssl_privatekey: - path: "{{ ssl_path }}/ssl.key" - -- name: Generate Openssl CSR - client - community.crypto.openssl_csr: - path: "{{ ssl_path }}/ssl.csr" - privatekey_path: "{{ ssl_path }}/ssl.key" - common_name: "{{ common_name }}" - country_name: "{{ country_name }}" - state_or_province_name: "{{ state_or_province_name }}" - locality_name: "{{ locality_name }}" - organization_name: "{{ org_name }}" - organizational_unit_name: "{{ organizational_unit_name }}" - -- name: Generate a client cert - signed by the localCA.pem - openssl_certificate: - path: "{{ ssl_path }}/ssl.crt" - csr_path: "{{ ssl_path }}/ssl.csr" - ownca_path: "{{ ssl_path }}/localCA.pem" - ownca_privatekey_path: "{{ ssl_path }}/localCA.key" - provider: ownca diff --git a/roles/health-check/test-custom-certs/tasks/install_corporate_ca_certs_cm.yaml b/roles/health-check/test-custom-certs/tasks/install_corporate_ca_certs_cm.yaml deleted file mode 100644 index bca93451..00000000 --- a/roles/health-check/test-custom-certs/tasks/install_corporate_ca_certs_cm.yaml +++ /dev/null @@ -1,17 +0,0 @@ ---- -- name: Retrieve localCA.pem content - ansible.builtin.slurp: - src: "{{ ssl_path }}/localCA.pem" - register: localca - -- name: Install corporate-ca-certs ConfigMap - kubernetes.core.k8s: - state: present - name: corporate-ca-certs - namespace: sf - definition: | - kind: ConfigMap - apiVersion: v1 - type: Opaque - data: - local-ca.crt: "{{ localca['content'] | b64decode }}" diff --git a/roles/health-check/test-custom-certs/tasks/install_sf_ssl_cert.yaml b/roles/health-check/test-custom-certs/tasks/install_sf_ssl_cert.yaml deleted file mode 100644 index 5392b8d3..00000000 --- a/roles/health-check/test-custom-certs/tasks/install_sf_ssl_cert.yaml +++ /dev/null @@ -1,6 +0,0 @@ ---- -- name: Add custom self signed cert - ansible.builtin.shell: > - go run ./main.go {{ cli_global_flags }} SF configure TLS --CA {{ ssl_path }}/localCA.pem --cert {{ ssl_path }}/ssl.crt --key {{ ssl_path }}/ssl.key - args: - chdir: "{{ zuul.project.src_dir | default(src_dir) }}" diff --git a/roles/health-check/test-custom-certs/tasks/main.yaml b/roles/health-check/test-custom-certs/tasks/main.yaml deleted file mode 100644 index dce61c1a..00000000 --- a/roles/health-check/test-custom-certs/tasks/main.yaml +++ /dev/null @@ -1,38 +0,0 @@ ---- -- name: Create temporary directory for the custom CA and Route certificate - ansible.builtin.tempfile: - state: directory - suffix: ssl - register: tempdir - -- set_fact: - ssl_path: "{{ tempdir.path }}" - random_state_or_province_name: "{{ lookup('community.general.random_string', special=false, length=8) }}" - -- name: Generate CA certificate - ansible.builtin.include_tasks: ca.yaml - -- name: Validate Corporate CA certificates installation - block: - - name: Add the coporate ca certs Config Map - ansible.builtin.include_tasks: install_corporate_ca_certs_cm.yaml - - - ansible.builtin.include_role: - name: "health-check/check-sf-resource-ready" - - - name: Check that generated CA trust chain contains the localCA cert - ansible.builtin.include_tasks: check_ca_trust_chain.yaml - -- name: Validate Route custom TLS setting - block: - - name: Generate Client certificate - ansible.builtin.include_tasks: client-cert.yaml - - - name: Add SSL cert for route as secret - ansible.builtin.include_tasks: install_sf_ssl_cert.yaml - - - ansible.builtin.include_role: - name: "health-check/check-sf-resource-ready" - - - name: Check SSL for the route - ansible.builtin.include_tasks: check_route.yaml diff --git a/roles/run-tests/tasks/main.yaml b/roles/run-tests/tasks/main.yaml index 4ee1dff9..3e65c4d3 100644 --- a/roles/run-tests/tasks/main.yaml +++ b/roles/run-tests/tasks/main.yaml @@ -36,8 +36,6 @@ - name: test-volumestats-sidecar - name: expand-volume - name: validate-purgelogs - - name: test-custom-certs - - name: test-cert-manager-letsencrypt when: "{{ mode == 'olm' }}" - name: zuul-client-api - name: zuul-components