From f544442f946251c21f52cc17e210e0528fdef99e Mon Sep 17 00:00:00 2001 From: Dan Molik Date: Fri, 9 Aug 2024 20:01:10 -0400 Subject: [PATCH] internal tls support --- api/v1/gitea_types.go | 14 + cmd/main.go | 2 + config/crd/bases/hyperspike.io_gitea.yaml | 16 ++ config/rbac/role.yaml | 21 ++ go.mod | 2 + go.sum | 4 + internal/client/client.go | 108 +++++++- internal/controller/gitea_controller.go | 311 ++++++++++++++++++++-- internal/controller/runner_controller.go | 55 +--- 9 files changed, 462 insertions(+), 71 deletions(-) diff --git a/api/v1/gitea_types.go b/api/v1/gitea_types.go index 81947c1b..d95f560d 100644 --- a/api/v1/gitea_types.go +++ b/api/v1/gitea_types.go @@ -54,6 +54,20 @@ type GiteaSpec struct { // Use Valkey // +kubebuilder:default:=false Valkey bool `json:"valkey,omitempty"` + + // Use TLS + // +kubebuilder:default:=false + TLS bool `json:"tls,omitempty"` + + // TLS Cert-manager Issuer + CertIssuer string `json:"certIssuer,omitempty"` + + // Cert-Manger Cluster Issuer Kind + // +kubebuilder:default:="ClusterIssuer" + // +kubebuilder:validation:Enum=ClusterIssuer;Issuer + CertIssuerType string `json:"certIssuerType,omitempty"` + + ClusterDomain string `json:"clusterDomain,omitempty"` } type IngressSpec struct { diff --git a/cmd/main.go b/cmd/main.go index 48806051..c936abe9 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -35,6 +35,7 @@ import ( metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" "sigs.k8s.io/controller-runtime/pkg/webhook" + certv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" zalandov1 "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1" hyperspikeiov1 "hyperspike.io/gitea-operator/api/v1" @@ -51,6 +52,7 @@ var ( func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) + utilruntime.Must(certv1.AddToScheme(scheme)) utilruntime.Must(zalandov1.AddToScheme(scheme)) utilruntime.Must(monitoringv1.AddToScheme(scheme)) utilruntime.Must(hyperspikeiov1.AddToScheme(scheme)) diff --git a/config/crd/bases/hyperspike.io_gitea.yaml b/config/crd/bases/hyperspike.io_gitea.yaml index 7a00ea04..63f4b13c 100644 --- a/config/crd/bases/hyperspike.io_gitea.yaml +++ b/config/crd/bases/hyperspike.io_gitea.yaml @@ -58,6 +58,18 @@ spec: spec: description: GiteaSpec defines the desired state of Gitea properties: + certIssuer: + description: TLS Cert-manager Issuer + type: string + certIssuerType: + default: ClusterIssuer + description: Cert-Manger Cluster Issuer Kind + enum: + - ClusterIssuer + - Issuer + type: string + clusterDomain: + type: string externalSSH: description: Create a loadbalancer for ssh access type: boolean @@ -94,6 +106,10 @@ spec: sshHostname: description: if different from Hostname type: string + tls: + default: false + description: Use TLS + type: boolean valkey: default: false description: Use Valkey diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 98cd7930..9fd15363 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -69,6 +69,27 @@ rules: - get - list - watch +- apiGroups: + - cert-manager.io + resources: + - certificates + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - cert-manager.io + resources: + - clusterissuers + - issuers + verbs: + - get + - list + - watch - apiGroups: - "" resources: diff --git a/go.mod b/go.mod index e15dcf92..58edfa48 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ toolchain go1.22.3 require ( code.gitea.io/sdk/gitea v0.19.0 + github.com/cert-manager/cert-manager v1.15.2 github.com/onsi/ginkgo/v2 v2.20.0 github.com/onsi/gomega v1.34.1 github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.75.2 @@ -82,6 +83,7 @@ require ( k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20240620174524-b456828f718b // indirect k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 // indirect + sigs.k8s.io/gateway-api v1.1.0 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect sigs.k8s.io/yaml v1.4.0 // indirect diff --git a/go.sum b/go.sum index c3dcace5..d04bb79e 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ code.gitea.io/sdk/gitea v0.19.0 h1:8I6s1s4RHgzxiPHhOQdgim1RWIRcr0LVMbHBjBFXq4Y= code.gitea.io/sdk/gitea v0.19.0/go.mod h1:IG9xZJoltDNeDSW0qiF2Vqx5orMWa7OhVWrjvrd5NpI= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cert-manager/cert-manager v1.15.2 h1:Mjbvc+FjYeg2928xy7bcS+c+ARxyqBcXM9QypOg1/Uo= +github.com/cert-manager/cert-manager v1.15.2/go.mod h1:stBge/DTvrhfQMB/93+Y62s+gQgZBsfL1o0C/4AL/mI= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -268,6 +270,8 @@ k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 h1:jgGTlFYnhF1PM1Ax/lAlxUPE+KfCI k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/controller-runtime v0.18.4 h1:87+guW1zhvuPLh1PHybKdYFLU0YJp4FhJRmiHvm5BZw= sigs.k8s.io/controller-runtime v0.18.4/go.mod h1:TVoGrfdpbA9VRFaRnKgk9P5/atA0pMwq+f+msb9M8Sg= +sigs.k8s.io/gateway-api v1.1.0 h1:DsLDXCi6jR+Xz8/xd0Z1PYl2Pn0TyaFMOPPZIj4inDM= +sigs.k8s.io/gateway-api v1.1.0/go.mod h1:ZH4lHrL2sDi0FHZ9jjneb8kKnGzFWyrTya35sWUTrRs= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= diff --git a/internal/client/client.go b/internal/client/client.go index 7a817da1..3b662e7e 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -2,12 +2,19 @@ package client import ( "context" + "crypto/tls" + "crypto/x509" + "io" + "net/http" + "time" "code.gitea.io/sdk/gitea" "k8s.io/apimachinery/pkg/types" rclient "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" + certv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + cmetav1 "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" hyperv1 "hyperspike.io/gitea-operator/api/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -15,7 +22,9 @@ import ( type Client struct { *gitea.Client - Instance *hyperv1.Gitea + Instance *hyperv1.Gitea + httpClient *http.Client + CA []byte } func BuildFromOrg(ctx context.Context, r rclient.Client, instance *hyperv1.OrgRef, ns string) (*Client, *hyperv1.Gitea, error) { @@ -52,6 +61,14 @@ func Build(ctx context.Context, r rclient.Client, instance *hyperv1.InstanceType if !git.Status.Ready { return nil, nil, nil } + var err error + c := Client{} + c.CA, err = getCACertificate(ctx, r, git) + if err != nil { + logger.Error(err, "failed to get ca certificate") + return nil, nil, err + } + c.httpClient = httpClient(ctx, c.CA) secret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: git.Name + "-admin", @@ -63,7 +80,7 @@ func Build(ctx context.Context, r rclient.Client, instance *hyperv1.InstanceType return nil, nil, err } url := "http://" + git.Name + "." + git.Namespace + ".svc" - g, err := gitea.NewClient(url, gitea.SetContext(ctx), gitea.SetToken(string(secret.Data["token"]))) + g, err := gitea.NewClient(url, gitea.SetContext(ctx), gitea.SetToken(string(secret.Data["token"])), gitea.SetHTTPClient(c.httpClient)) if err != nil { logger.Error(err, "failed to create client for "+url) return nil, nil, err @@ -74,8 +91,93 @@ func Build(ctx context.Context, r rclient.Client, instance *hyperv1.InstanceType return nil, nil, err } - c := Client{} c.Client = g c.Instance = git return &c, git, nil } + +func httpClient(ctx context.Context, CA []byte) *http.Client { + logger := log.FromContext(ctx) + httpClient := http.Client{ + Timeout: time.Second * 10, + } + if CA != nil { + certpool, _ := x509.SystemCertPool() + if certpool == nil { + logger.Info("system cert pool is nil, creating new") + certpool = x509.NewCertPool() + } + certpool.AppendCertsFromPEM(CA) + httpClient.Transport = &http.Transport{ + TLSClientConfig: &tls.Config{ + RootCAs: certpool, + }, + } + } + return &httpClient +} + +func getCACertificate(ctx context.Context, r rclient.Client, gitea *hyperv1.Gitea) ([]byte, error) { + logger := log.FromContext(ctx) + + cert := &certv1.Certificate{} + if err := r.Get(ctx, types.NamespacedName{Namespace: gitea.Namespace, Name: gitea.Name}, cert); err != nil { + logger.Error(err, "failed to get ca certificate") + return []byte{}, err + } + if cert.Status.Conditions == nil { + return []byte{}, nil + } + good := false + for _, cond := range cert.Status.Conditions { + if cond.Type == certv1.CertificateConditionReady { + if cond.Status == cmetav1.ConditionTrue { + good = true + break + } + } + } + if !good { + return []byte{}, nil + } + tls := &corev1.Secret{} + err := r.Get(ctx, types.NamespacedName{Namespace: gitea.Namespace, Name: cert.Spec.SecretName}, tls) + if err != nil { + logger.Error(err, "failed to get tls secret") + return []byte{}, err + } + return tls.Data["ca.crt"], nil +} + +func (c *Client) Get(url string) (resp *http.Response, err error) { + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, err + + } + + req.Header.Set("User-Agent", "gitea-operator/0.1.0") + + return c.httpClient.Do(req) + +} + +func (c *Client) Post(url, contentType string, body io.Reader) (resp *http.Response, err error) { + req, err := http.NewRequest("POST", url, body) + if err != nil { + return nil, err + + } + + req.Header.Set("Content-Type", contentType) + req.Header.Set("User-Agent", "gitea-operator/0.1.0") + + return c.httpClient.Do(req) + +} + +func (c *Client) Do(req *http.Request) (*http.Response, error) { + req.Header.Set("User-Agent", "gitea-operator/0.1.0") + return c.httpClient.Do(req) + +} diff --git a/internal/controller/gitea_controller.go b/internal/controller/gitea_controller.go index 84263afd..1e84ddd3 100644 --- a/internal/controller/gitea_controller.go +++ b/internal/controller/gitea_controller.go @@ -20,12 +20,17 @@ import ( "bytes" "context" "crypto/rand" + "crypto/tls" + "crypto/x509" "encoding/base64" "encoding/json" "fmt" "io" "math/big" + "net" "net/http" + "os" + "strings" "time" "k8s.io/apimachinery/pkg/api/errors" @@ -40,6 +45,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/log" + certv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + cmetav1 "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" zalandov1 "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1" hyperv1 "hyperspike.io/gitea-operator/api/v1" @@ -85,6 +92,8 @@ type GiteaReconciler struct { // +kubebuilder:rbac:groups=hyperspike.io,resources=valkeys,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=hyperspike.io,resources=gitea/status,verbs=get;update;patch // +kubebuilder:rbac:groups=hyperspike.io,resources=gitea/finalizers,verbs=update +// +kubebuilder:rbac:groups=cert-manager.io,resources=clusterissuers;issuers,verbs=get;list;watch +// +kubebuilder:rbac:groups=cert-manager.io,resources=certificates,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups="",resources=serviceaccounts;secrets;services,verbs=create;delete;get;list;watch;update // +kubebuilder:rbac:groups="",resources=pods,verbs=get;list;patch;update;watch;delete // +kubebuilder:rbac:groups="",resources=endpoints,verbs=create;delete;deletecollection;get;list;patch;update;watch @@ -175,6 +184,11 @@ func (r *GiteaReconciler) reconcileGitea(ctx context.Context, gitea *hyperv1.Git return ctrl.Result{Requeue: true, RequeueAfter: time.Second * 5}, nil } } + if gitea.Spec.TLS { + if err := r.upsertCertificate(ctx, gitea); err != nil { + return ctrl.Result{}, err + } + } if !gitea.Status.Ready { if err := r.setCondition(ctx, gitea, "DatabaseReady", "True", "DatabaseReady", "database ready"); err != nil { return ctrl.Result{}, err @@ -314,10 +328,6 @@ echo '==== END GITEA CONFIGURATION ===='`, }, false); err != nil { return ctrl.Result{}, err } - hostname := gitea.Spec.Ingress.Host - if hostname == "" { - hostname = "git.example.com" - } password, err := r.getValkeyPassword(ctx, gitea) if err != nil { return ctrl.Result{}, err @@ -335,17 +345,8 @@ echo '==== END GITEA CONFIGURATION ===='`, "queue": queueSvc(gitea, password), "repository": "ROOT=/data/git/gitea-repositories", "security": "INSTALL_LOCK=true", - "server": `APP_DATA_PATH=/data -DOMAIN=` + hostname + ` -ENABLE_PPROF=false -HTTP_PORT=3000 -PROTOCOL=http -ROOT_URL=https://` + hostname + ` -SSH_DOMAIN=` + hostname + ` -SSH_LISTEN_PORT=2222 -SSH_PORT=22 -START_SSH_SERVER=true`, - "session": sessionSvc(gitea, password), + "server": serverSvc(gitea), + "session": sessionSvc(gitea, password), }, false); err != nil { return ctrl.Result{}, err } @@ -726,6 +727,11 @@ func (r *GiteaReconciler) upsertValkey(ctx context.Context, gitea *hyperv1.Gitea PrometheusLabels: gitea.Spec.PrometheusLabels, }, } + if gitea.Spec.TLS { + vk.Spec.TLS = true + vk.Spec.CertIssuer = gitea.Spec.CertIssuer + vk.Spec.CertIssuerType = gitea.Spec.CertIssuerType + } if err := controllerutil.SetControllerReference(gitea, vk, r.Scheme); err != nil { return err } @@ -806,11 +812,47 @@ func (r *GiteaReconciler) upsertGiteaSvc(ctx context.Context, gitea *hyperv1.Git return nil } +func serverSvc(gitea *hyperv1.Gitea) string { + hostname := gitea.Spec.Ingress.Host + if hostname == "" { + hostname = "git.example.com" + } + service := `APP_DATA_PATH=/data +DOMAIN=` + hostname + ` +ENABLE_PPROF=false +HTTP_PORT=3000 +PROTOCOL=http +ROOT_URL=https://` + hostname + ` +SSH_DOMAIN=` + hostname + ` +SSH_LISTEN_PORT=2222 +SSH_PORT=22 +START_SSH_SERVER=true` + if gitea.Spec.TLS { + service = `APP_DATA_PATH=/data +DOMAIN=` + hostname + ` +ENABLE_PPROF=false +HTTP_PORT=3000 +PROTOCOL=https +ROOT_URL=https://` + hostname + ` +SSH_DOMAIN=` + hostname + ` +SSH_LISTEN_PORT=2222 +SSH_PORT=22 +START_SSH_SERVER=true +CERT_FILE=/certs/tls.crt +KEY_FILE=/certs/tls.key` + } + return service +} + func cacheSvc(gitea *hyperv1.Gitea, password string) string { cache := "ADAPTER=memory" if gitea.Spec.Valkey { cache = `ADAPTER=redis HOST=redis+cluster://:` + password + "@" + gitea.Name + "-valkey." + gitea.Namespace + ".svc:6379/0?pool_size=100&idle_timeout=180s&" + if gitea.Spec.TLS { + cache = `ADAPTER=redis +HOST=rediss+cluster://:` + password + "@" + gitea.Name + "-valkey." + gitea.Namespace + ".svc:6379/0?pool_size=100&idle_timeout=180s&skipverify=true" + } } return cache } @@ -820,6 +862,10 @@ func sessionSvc(gitea *hyperv1.Gitea, password string) string { if gitea.Spec.Valkey { session = `PROVIDER=redis PROVIDER_CONFIG=redis+cluster://:` + password + "@" + gitea.Name + "-valkey." + gitea.Namespace + ".svc:6379/0?pool_size=100&idle_timeout=180s&" + if gitea.Spec.TLS { + session = `PROVIDER=redis +PROVIDER_CONFIG=rediss+cluster://:` + password + "@" + gitea.Name + "-valkey." + gitea.Namespace + ".svc:6379/0?pool_size=100&idle_timeout=180s&skipverify=true" + } } return session } @@ -829,6 +875,10 @@ func queueSvc(gitea *hyperv1.Gitea, password string) string { if gitea.Spec.Valkey { queue = `TYPE=redis CONN_STR=redis+cluster://:` + password + "@" + gitea.Name + "-valkey." + gitea.Namespace + ".svc:6379/0?pool_size=100&idle_timeout=180s&" + if gitea.Spec.TLS { + queue = `TYPE=redis +CONN_STR=rediss+cluster://:` + password + "@" + gitea.Name + "-valkey." + gitea.Namespace + ".svc:6379/0?pool_size=100&idle_timeout=180s&skipverify=true" + } } return queue } @@ -837,8 +887,8 @@ func metricsSvc(gitea *hyperv1.Gitea) string { metrics := "ENABLED=false" if gitea.Spec.Prometheus { metrics = `ENABLED=true -ENABLE_ISSUE_BY_REPOSITORY=true -ENABLE_ISSUE_BY_LABEL=true` +ENABLED_ISSUE_BY_REPOSITORY=true +ENABLED_ISSUE_BY_LABEL=false` } return metrics } @@ -863,6 +913,147 @@ func (r *GiteaReconciler) getValkeyPassword(ctx context.Context, gitea *hyperv1. return string(vk.Data["password"]), nil } +func (r *GiteaReconciler) getCertManagerIp(ctx context.Context) (string, error) { + logger := log.FromContext(ctx) + pods := &corev1.PodList{} + l := map[string]string{ + "app.kubernetes.io/component": "controller", + } + if err := r.List(ctx, pods, client.InNamespace("cert-manager"), client.MatchingLabels(l)); err != nil { + logger.Error(err, "failed to list coredns pods") + return "", err + } + for _, pod := range pods.Items { + return pod.Status.PodIP, nil + } + return "", nil +} + +func (r *GiteaReconciler) detectClusterDomain(ctx context.Context, gitea *hyperv1.Gitea) (string, error) { + logger := log.FromContext(ctx) + + logger.Info("detecting cluster domain") + clusterDomain := os.Getenv("CLUSTER_DOMAIN") + if clusterDomain == "" { + clusterDomain = "cluster.local" + } + ip, err := r.getCertManagerIp(ctx) + if err != nil { + return "", err + } + + if ip != "" { + addrs, err := net.LookupAddr(ip) + if err != nil { + logger.Error(err, "failed to lookup addr", "ip", ip) + } else { + logger.Info("detected addrs", "addrs", addrs) + clusterDomain = addrs[0] + clusterDomain = clusterDomain[strings.Index(clusterDomain, ".svc.")+5:] + clusterDomain = strings.TrimSuffix(clusterDomain, ".") + logger.Info("detected cluster domain", "clusterDomain", clusterDomain) + } + } + gitea.Spec.ClusterDomain = clusterDomain + if err := r.Update(ctx, gitea); err != nil { + logger.Error(err, "failed to update gitea") + return "", err + } + return clusterDomain, nil +} + +func (r *GiteaReconciler) getCACertificate(ctx context.Context, gitea *hyperv1.Gitea) ([]byte, error) { + logger := log.FromContext(ctx) + + cert := &certv1.Certificate{} + if err := r.Get(ctx, types.NamespacedName{Namespace: gitea.Namespace, Name: gitea.Name}, cert); err != nil { + logger.Error(err, "failed to get ca certificate") + return []byte{}, err + } + if cert.Status.Conditions == nil { + return []byte{}, nil + } + good := false + for _, cond := range cert.Status.Conditions { + if cond.Type == certv1.CertificateConditionReady { + if cond.Status == cmetav1.ConditionTrue { + good = true + break + } + } + } + if !good { + return []byte{}, nil + } + tls := &corev1.Secret{} + err := r.Get(ctx, types.NamespacedName{Namespace: gitea.Namespace, Name: cert.Spec.SecretName}, tls) + if err != nil { + logger.Error(err, "failed to get tls secret") + return []byte{}, err + } + return tls.Data["ca.crt"], nil +} + +func (r *GiteaReconciler) upsertCertificate(ctx context.Context, gitea *hyperv1.Gitea) error { + logger := log.FromContext(ctx) + + logger.Info("upserting certificate") + + clusterDomain, err := r.detectClusterDomain(ctx, gitea) + if err != nil { + logger.Error(err, "failed to detect cluster domain") + return err + } + logger.Info("using cluster domain " + clusterDomain) + cert := &certv1.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: gitea.Name, + Namespace: gitea.Namespace, + Labels: labels(gitea.Name), + }, + Spec: certv1.CertificateSpec{ + CommonName: gitea.Name + "." + gitea.Namespace + ".svc", + SecretName: gitea.Name + "-tls", + IssuerRef: cmetav1.ObjectReference{ + Name: gitea.Spec.CertIssuer, + Kind: gitea.Spec.CertIssuerType, + }, + DNSNames: []string{ + "localhost", + gitea.Name, + gitea.Name + "." + gitea.Namespace + ".svc", + gitea.Name + "." + gitea.Namespace + ".svc." + clusterDomain, + }, + IPAddresses: []string{ + "127.0.0.1", + }, + }, + } + + if err := controllerutil.SetControllerReference(gitea, cert, r.Scheme); err != nil { + return err + } + err = r.Get(ctx, types.NamespacedName{Namespace: gitea.Namespace, Name: gitea.Name}, cert) + if err != nil && errors.IsNotFound(err) { + if err := r.Create(ctx, cert); err != nil { + logger.Error(err, "failed to create certificate") + return err + } + r.Recorder.Event(gitea, "Normal", "Created", + fmt.Sprintf("Certificate %s/%s is created", gitea.Namespace, gitea.Name)) + } else if err != nil { + logger.Error(err, "failed to fetch certificate") + return err + } else if err == nil && false { // detect changes + if err := r.Update(ctx, cert); err != nil { + logger.Error(err, "failed to update certificate") + return err + } + } + + return nil +} + func (r *GiteaReconciler) upsertGiteaSa(ctx context.Context, gitea *hyperv1.Gitea) error { logger := log.FromContext(ctx) sa := &corev1.ServiceAccount{ @@ -923,6 +1114,14 @@ func (r *GiteaReconciler) upsertServiceMonitor(ctx context.Context, gitea *hyper }, }, } + if gitea.Spec.TLS { + sm.Spec.Endpoints[0].Scheme = "https" + sm.Spec.Endpoints[0].TLSConfig = &monitoringv1.TLSConfig{ + SafeTLSConfig: monitoringv1.SafeTLSConfig{ + InsecureSkipVerify: func(b bool) *bool { return &b }(true), + }, + } + } if err := controllerutil.SetControllerReference(gitea, sm, r.Scheme); err != nil { return err } @@ -1280,11 +1479,46 @@ func (r *GiteaReconciler) podUP(ctx context.Context, gitea *hyperv1.Gitea) (bool return false, nil } +func (r *GiteaReconciler) httpClient(ctx context.Context, gitea *hyperv1.Gitea) (*http.Client, error) { + logger := log.FromContext(ctx) + httpClient := http.Client{ + Timeout: time.Second * 10, + } + if gitea.Spec.TLS { + certpool, _ := x509.SystemCertPool() + if certpool == nil { + logger.Info("system cert pool is nil, creating new") + certpool = x509.NewCertPool() + } + cert, err := r.getCACertificate(ctx, gitea) + if err != nil { + logger.Error(err, "failed to get ca certificate") + return nil, err + } + certpool.AppendCertsFromPEM(cert) + httpClient.Transport = &http.Transport{ + TLSClientConfig: &tls.Config{ + RootCAs: certpool, + }, + } + } + return &httpClient, nil +} + func (r *GiteaReconciler) apiUP(ctx context.Context, gitea *hyperv1.Gitea) bool { logger := log.FromContext(ctx) - _, err := http.Get("http://" + gitea.Name + "." + gitea.Namespace + ".svc") + httpClient, err := r.httpClient(ctx, gitea) + if err != nil { + logger.Error(err, "failed to get http client") + return false + } + url := "http://" + gitea.Name + "." + gitea.Namespace + ".svc" + if gitea.Spec.TLS { + url = "https://" + gitea.Name + "." + gitea.Namespace + ".svc" + } + _, err = httpClient.Get(url) if err != nil { - logger.Error(err, "failed to get http://"+gitea.Name+"."+gitea.Namespace+".svc") + logger.Error(err, "failed to get url "+url) if err := r.setCondition(ctx, gitea, "Ready", "False", "Ready", "Api Down"); err != nil { logger.Error(err, "Gitea status update failed.") return false @@ -1334,6 +1568,11 @@ func (r *GiteaReconciler) adminToken(ctx context.Context, gitea *hyperv1.Gitea) logger.Info("creating admin token", "SecretName", gitea.Name+"-admin") body := []byte(`{"name":"admin","scopes": ["write:admin","write:organization","write:repository","write:user"]}`) url := "http://" + gitea.Name + "." + gitea.Namespace + ".svc/api/v1/users/gitea/tokens" + httpClient, err := r.httpClient(ctx, gitea) + if err != nil { + logger.Error(err, "failed to get http client") + return err + } req, err := http.NewRequest("POST", url, bytes.NewBuffer(body)) if err != nil { logger.Error(err, "failed creating new http request") @@ -1342,7 +1581,7 @@ func (r *GiteaReconciler) adminToken(ctx context.Context, gitea *hyperv1.Gitea) req.Header.Set("Content-Type", "application/json") auth := base64.StdEncoding.EncodeToString([]byte(string(secret.Data["username"]) + ":" + string(secret.Data["password"]))) req.Header.Set("Authorization", "Basic "+auth) - resp, err := http.DefaultClient.Do(req) + resp, err := httpClient.Do(req) if err != nil { logger.Error(err, "failed posting to url "+url) return err @@ -1591,6 +1830,32 @@ func (r *GiteaReconciler) upsertGiteaSts(ctx context.Context, gitea *hyperv1.Git }, } sts.Spec.Template.Spec.InitContainers[2].Env = append(sts.Spec.Template.Spec.InitContainers[2].Env, admins...) + if gitea.Spec.TLS { + sts.Spec.Template.Spec.Volumes = append(sts.Spec.Template.Spec.Volumes, corev1.Volume{ + Name: "tls", + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: gitea.Name + "-tls", + }, + }, + }) + sts.Spec.Template.Spec.Containers[0].VolumeMounts = append(sts.Spec.Template.Spec.Containers[0].VolumeMounts, corev1.VolumeMount{ + Name: "tls", + MountPath: "/certs", + }) + sts.Spec.Template.Spec.InitContainers[0].VolumeMounts = append(sts.Spec.Template.Spec.InitContainers[0].VolumeMounts, corev1.VolumeMount{ + Name: "tls", + MountPath: "/certs", + }) + sts.Spec.Template.Spec.InitContainers[1].VolumeMounts = append(sts.Spec.Template.Spec.InitContainers[1].VolumeMounts, corev1.VolumeMount{ + Name: "tls", + MountPath: "/certs", + }) + sts.Spec.Template.Spec.InitContainers[2].VolumeMounts = append(sts.Spec.Template.Spec.InitContainers[2].VolumeMounts, corev1.VolumeMount{ + Name: "tls", + MountPath: "/certs", + }) + } dbs := []corev1.EnvVar{ { @@ -1673,13 +1938,17 @@ func (r *GiteaReconciler) upsertGiteaIngress(ctx context.Context, gitea *hyperv1 if hostname == "" { hostname = "git.example.com" } + annotations := gitea.Spec.Ingress.Annotations + if gitea.Spec.TLS { + annotations["nginx.ingress.kubernetes.io/backend-protocol"] = "HTTPS" + } prefix := netv1.PathTypePrefix ing := &netv1.Ingress{ ObjectMeta: metav1.ObjectMeta{ Name: gitea.Name, Namespace: gitea.Namespace, Labels: labels(gitea.Name), - Annotations: gitea.Spec.Ingress.Annotations, + Annotations: annotations, }, Spec: netv1.IngressSpec{ IngressClassName: ptrString("nginx"), diff --git a/internal/controller/runner_controller.go b/internal/controller/runner_controller.go index dfc722cf..0c6afd9d 100644 --- a/internal/controller/runner_controller.go +++ b/internal/controller/runner_controller.go @@ -24,7 +24,7 @@ import ( "net/http" "time" - g "code.gitea.io/sdk/gitea" + hyperspikeClient "hyperspike.io/gitea-operator/internal/client" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/runtime" @@ -105,60 +105,22 @@ func (r *RunnerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr func (r *RunnerReconciler) registrationToken(ctx context.Context, instance *hyperv1.OrgRef, ns string) (string, string, error) { logger := log.FromContext(ctx) - orgName := instance.Name - orgNamespace := instance.Namespace - if orgNamespace == "" { - orgNamespace = ns - } - org := &hyperv1.Org{} - if err := r.Get(ctx, types.NamespacedName{Name: orgName, Namespace: orgNamespace}, org); err != nil { - logger.Error(err, "failed to get gitea") - return "", "", err - } - - git := &hyperv1.Gitea{} - gitName := org.Spec.Instance.Name - gitNamespace := org.Spec.Instance.Namespace - if gitNamespace == "" { - gitNamespace = ns - } - if err := r.Get(ctx, types.NamespacedName{Name: gitName, Namespace: gitNamespace}, git); err != nil { - logger.Error(err, "failed to get gitea") - return "", "", err - } - if !git.Status.Ready { - return "", "", nil - } - - secret := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: git.Name + "-admin", - Namespace: git.Namespace, - }, - } - if err := r.Get(ctx, types.NamespacedName{Name: git.Name + "-admin", Namespace: git.Namespace}, secret); err != nil { - logger.Error(err, "failed getting admin secret "+git.Name+"-admin ") - return "", "", err - } - instanceUrl := "http://" + git.Name + "." + git.Namespace + ".svc" - gClient, err := g.NewClient(instanceUrl, g.SetContext(ctx), g.SetToken(string(secret.Data["token"]))) + hclient, git, err := hyperspikeClient.BuildFromOrg(ctx, r.Client, instance, ns) if err != nil { - logger.Error(err, "failed to create client for "+instanceUrl) + logger.Error(err, "failed to build client") return "", "", err } - _, _, err = gClient.ServerVersion() - if err != nil { - logger.Error(err, "failed to get server version "+instanceUrl) - return "", "", err + instanceUrl := "http://" + git.Name + "." + git.Namespace + ".svc" + if git.Spec.TLS { + instanceUrl = "https://" + git.Name + "." + git.Namespace + ".svc" } - url := "http://" + git.Name + "." + git.Namespace + ".svc/api/v1/orgs/" + orgName + "/actions/runners/registration-token" + url := instanceUrl + "/actions/runners/registration-token" req, err := http.NewRequest("GET", url, nil) if err != nil { return "", "", nil } req.Header.Set("Accept", "application/json") - req.Header.Set("Authorization", "token "+string(secret.Data["token"])) - resp, err := http.DefaultClient.Do(req) + resp, err := hclient.Do(req) if err != nil { logger.Error(err, "failed posting to url "+url) return "", "", err @@ -182,7 +144,6 @@ func (r *RunnerReconciler) registrationToken(ctx context.Context, instance *hype if tresp.Token == "" { return "", "", nil } - return tresp.Token, instanceUrl, nil }