From cf384e406542ba309a85a89474ec11ebc4e5b715 Mon Sep 17 00:00:00 2001 From: Tayler Geiger Date: Tue, 14 May 2024 11:54:08 -0500 Subject: [PATCH] =?UTF-8?q?=E2=9A=A0=EF=B8=8F=20Serve=20catalog=20over=20H?= =?UTF-8?q?TTPS=20(#263)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * make catalog server serve catalog contents over HTTPS adds cert-manager as a dependency again to create self-signed certs for the catalog server Signed-off-by: everettraven * fix e2e Signed-off-by: everettraven * Reorganize manifests for cert-manager overlay This allows the use of alternate certificate managers. Signed-off-by: Tayler Geiger * Reconfigure TLS functionality to use Listener Fix a few manifest issues as well. Signed-off-by: Tayler Geiger * Add certwatcher for TLS cert and key from controller-runtime - Add error for missing either tls-key or tls-cert arguments. - Move server creation and configuration to serverutil Signed-off-by: Tayler Geiger * Update README and docs for HTTPS --------- Signed-off-by: everettraven Signed-off-by: Tayler Geiger Co-authored-by: everettraven --- .goreleaser.yml | 2 + Makefile | 16 +++-- README.md | 12 ++-- Tiltfile | 2 +- api/core/v1alpha1/catalog_types_test.go | 2 +- cmd/manager/main.go | 51 ++++++++------ ...atalogd.operatorframework.io_catalogs.yaml | 0 config/{ => base}/crd/kustomization.yaml | 0 .../crd/patches/catalog_validation.yaml | 0 config/{ => base}/default/kustomization.yaml | 1 - .../manager/catalogserver_service.yaml | 0 config/{ => base}/manager/kustomization.yaml | 0 config/{ => base}/manager/manager.yaml | 2 +- .../nginx-ingress/kustomization.yaml | 0 .../resources/nginx_ingress.yaml | 0 .../rbac/auth_proxy_client_clusterrole.yaml | 0 config/{ => base}/rbac/auth_proxy_role.yaml | 0 .../rbac/auth_proxy_role_binding.yaml | 0 .../{ => base}/rbac/auth_proxy_service.yaml | 0 config/{ => base}/rbac/kustomization.yaml | 0 .../{ => base}/rbac/leader_election_role.yaml | 0 .../rbac/leader_election_role_binding.yaml | 0 config/base/rbac/role.yaml | 65 +++++++++++++++++ config/{ => base}/rbac/role_binding.yaml | 0 config/{ => base}/rbac/service_account.yaml | 0 .../overlays/cert-manager/kustomization.yaml | 28 ++++++++ .../patches/catalogserver_service_port.yaml | 6 ++ .../patches/manager_deployment_certs.yaml | 12 ++++ .../cert-manager/resources/certificate.yaml | 14 ++++ .../cert-manager/resources/issuer.yaml | 37 ++++++++++ .../cert-manager/resources/kustomization.yaml | 3 + docs/fetching-catalog-contents.md | 19 ++--- internal/serverutil/serverutil.go | 70 +++++++++++++++++++ internal/third_party/server/server.go | 3 +- test/e2e/unpack_test.go | 4 +- 35 files changed, 303 insertions(+), 46 deletions(-) rename config/{ => base}/crd/bases/catalogd.operatorframework.io_catalogs.yaml (100%) rename config/{ => base}/crd/kustomization.yaml (100%) rename config/{ => base}/crd/patches/catalog_validation.yaml (100%) rename config/{ => base}/default/kustomization.yaml (99%) rename config/{ => base}/manager/catalogserver_service.yaml (100%) rename config/{ => base}/manager/kustomization.yaml (100%) rename config/{ => base}/manager/manager.yaml (97%) rename config/{ => base}/nginx-ingress/kustomization.yaml (100%) rename config/{ => base}/nginx-ingress/resources/nginx_ingress.yaml (100%) rename config/{ => base}/rbac/auth_proxy_client_clusterrole.yaml (100%) rename config/{ => base}/rbac/auth_proxy_role.yaml (100%) rename config/{ => base}/rbac/auth_proxy_role_binding.yaml (100%) rename config/{ => base}/rbac/auth_proxy_service.yaml (100%) rename config/{ => base}/rbac/kustomization.yaml (100%) rename config/{ => base}/rbac/leader_election_role.yaml (100%) rename config/{ => base}/rbac/leader_election_role_binding.yaml (100%) create mode 100644 config/base/rbac/role.yaml rename config/{ => base}/rbac/role_binding.yaml (100%) rename config/{ => base}/rbac/service_account.yaml (100%) create mode 100644 config/overlays/cert-manager/kustomization.yaml create mode 100644 config/overlays/cert-manager/patches/catalogserver_service_port.yaml create mode 100644 config/overlays/cert-manager/patches/manager_deployment_certs.yaml create mode 100644 config/overlays/cert-manager/resources/certificate.yaml create mode 100644 config/overlays/cert-manager/resources/issuer.yaml create mode 100644 config/overlays/cert-manager/resources/kustomization.yaml create mode 100644 internal/serverutil/serverutil.go diff --git a/.goreleaser.yml b/.goreleaser.yml index ab4f87de..9b74e2e8 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -66,6 +66,8 @@ release: header: | ## Installation ```bash + kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.11.0/cert-manager.yaml + kubectl wait --for=condition=Available --namespace=cert-manager deployment/cert-manager-webhook --timeout=60s kubectl apply -f https://github.com/operator-framework/catalogd/releases/download/{{ .Tag }}/catalogd.yaml kubectl wait --for=condition=Available --namespace=catalogd-system deployment/catalogd-controller-manager --timeout=60s ``` diff --git a/Makefile b/Makefile index 40286991..b6f061fa 100644 --- a/Makefile +++ b/Makefile @@ -48,7 +48,7 @@ clean: ## Remove binaries and test artifacts .PHONY: generate generate: $(CONTROLLER_GEN) ## Generate code and manifests. $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." - $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases + $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/base/crd/bases .PHONY: fmt fmt: ## Run go fmt against code. @@ -152,20 +152,24 @@ kind-load: $(KIND) ## Load the built images onto the local cluster $(KIND) load docker-image $(IMAGE) --name $(KIND_CLUSTER_NAME) .PHONY: install -install: build-container kind-load deploy wait ## Install local catalogd +install: build-container kind-load cert-manager deploy wait ## Install local catalogd .PHONY: deploy deploy: $(KUSTOMIZE) ## Deploy Catalogd to the K8s cluster specified in ~/.kube/config. - cd config/manager && $(KUSTOMIZE) edit set image controller=$(IMAGE) - $(KUSTOMIZE) build config/default | kubectl apply -f - + cd config/base/manager && $(KUSTOMIZE) edit set image controller=$(IMAGE) + $(KUSTOMIZE) build config/overlays/cert-manager | kubectl apply -f - .PHONY: undeploy undeploy: $(KUSTOMIZE) ## Undeploy Catalogd from the K8s cluster specified in ~/.kube/config. - $(KUSTOMIZE) build config/default | kubectl delete --ignore-not-found=true -f - + $(KUSTOMIZE) build config/overlays/cert-manager | kubectl delete --ignore-not-found=true -f - wait: kubectl wait --for=condition=Available --namespace=$(CATALOGD_NAMESPACE) deployment/catalogd-controller-manager --timeout=60s +.PHONY: cert-manager +cert-manager: + kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/${CERT_MGR_VERSION}/cert-manager.yaml + kubectl wait --for=condition=Available --namespace=cert-manager deployment/cert-manager-webhook --timeout=60s ##@ Release export ENABLE_RELEASE_PIPELINE ?= false @@ -175,7 +179,7 @@ release: $(GORELEASER) ## Runs goreleaser for catalogd. By default, this will ru $(GORELEASER) $(GORELEASER_ARGS) quickstart: $(KUSTOMIZE) generate ## Generate the installation release manifests and scripts - $(KUSTOMIZE) build config/default | sed "s/:devel/:$(GIT_VERSION)/g" > catalogd.yaml + $(KUSTOMIZE) build config/overlays/cert-manager | sed "s/:devel/:$(GIT_VERSION)/g" > catalogd.yaml .PHONY: demo-update demo-update: diff --git a/README.md b/README.md index abd3a2e3..c6ae5b02 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,9 @@ Catalogd helps customers discover installable content by hosting catalog metadat [![asciicast](https://asciinema.org/a/624043.svg)](https://asciinema.org/a/624043) ## Quickstart Steps -**NOTE:** Procedure steps marked with an asterisk (`*`) are likely to change with future API updates. +Procedure steps marked with an asterisk (`*`) are likely to change with future API updates. + +**NOTE:** The examples below use the `-k` flag in curl to skip validating the TLS certificates. This is for demonstration purposes only. 1. To install catalogd, navigate to the [releases](https://github.com/operator-framework/catalogd/releases/) page, and follow the install instructions included in the release you want to install. @@ -95,13 +97,13 @@ Catalogd helps customers discover installable content by hosting catalog metadat 1. Port forward the `catalogd-catalogserver` service in the `catalogd-system` namespace: ```sh - $ kubectl -n catalogd-system port-forward svc/catalogd-catalogserver 8080:80 + $ kubectl -n catalogd-system port-forward svc/catalogd-catalogserver 8080:443 ``` 1. Run the following command to get a list of packages: ```sh - $ curl http://localhost:8080/catalogs/operatorhubio/all.json | jq -s '.[] | select(.schema == "olm.package") | .name' + $ curl -k https://localhost:8080/catalogs/operatorhubio/all.json | jq -s '.[] | select(.schema == "olm.package") | .name' ``` *Example output* @@ -128,7 +130,7 @@ Catalogd helps customers discover installable content by hosting catalog metadat 1. Run the following command to get a list of channels for the `ack-acm-controller` package: ```sh - $ curl http://localhost:8080/catalogs/operatorhubio/all.json | jq -s '.[] | select(.schema == "olm.channel") | select(.package == "ack-acm-controller") | .name' + $ curl -k https://localhost:8080/catalogs/operatorhubio/all.json | jq -s '.[] | select(.schema == "olm.channel") | select(.package == "ack-acm-controller") | .name' ``` *Example output* @@ -142,7 +144,7 @@ Catalogd helps customers discover installable content by hosting catalog metadat 1. Run the following command to get a list of bundles belonging to the `ack-acm-controller` package: ```sh - $ curl http://localhost:8080/catalogs/operatorhubio/all.json | jq -s '.[] | select(.schema == "olm.bundle") | select(.package == "ack-acm-controller") | .name' + $ curl -k https://localhost:8080/catalogs/operatorhubio/all.json | jq -s '.[] | select(.schema == "olm.bundle") | select(.package == "ack-acm-controller") | .name' ``` *Example output* diff --git a/Tiltfile b/Tiltfile index 351e0cb5..b34713f2 100644 --- a/Tiltfile +++ b/Tiltfile @@ -5,7 +5,7 @@ load('../tilt-support/Tiltfile', 'deploy_repo') repo = { 'image': 'quay.io/operator-framework/catalogd', - 'yaml': 'config/default', + 'yaml': 'config/overlays/cert-manager', 'binaries': { 'manager': 'catalogd-controller-manager', }, diff --git a/api/core/v1alpha1/catalog_types_test.go b/api/core/v1alpha1/catalog_types_test.go index b4c4e67d..352445ab 100644 --- a/api/core/v1alpha1/catalog_types_test.go +++ b/api/core/v1alpha1/catalog_types_test.go @@ -20,7 +20,7 @@ import ( ) func TestPollIntervalCELValidationRules(t *testing.T) { - validators := fieldValidatorsFromFile(t, "../../../config/crd/bases/catalogd.operatorframework.io_catalogs.yaml") + validators := fieldValidatorsFromFile(t, "../../../config/base/crd/bases/catalogd.operatorframework.io_catalogs.yaml") pth := "openAPIV3Schema.properties.spec" validator, found := validators["v1alpha1"][pth] assert.True(t, found) diff --git a/cmd/manager/main.go b/cmd/manager/main.go index 9103e352..49890a47 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -19,7 +19,6 @@ package main import ( "flag" "fmt" - "net/http" "net/url" "os" "path/filepath" @@ -39,8 +38,8 @@ import ( "github.com/operator-framework/catalogd/api/core/v1alpha1" "github.com/operator-framework/catalogd/internal/garbagecollection" + "github.com/operator-framework/catalogd/internal/serverutil" "github.com/operator-framework/catalogd/internal/source" - "github.com/operator-framework/catalogd/internal/third_party/server" "github.com/operator-framework/catalogd/internal/version" corecontrollers "github.com/operator-framework/catalogd/pkg/controllers/core" "github.com/operator-framework/catalogd/pkg/features" @@ -71,9 +70,11 @@ func main() { catalogdVersion bool systemNamespace string catalogServerAddr string - httpExternalAddr string + externalAddr string cacheDir string gcInterval time.Duration + certFile string + keyFile string ) flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") @@ -83,10 +84,12 @@ func main() { "Enabling this will ensure there is only one active controller manager.") flag.StringVar(&systemNamespace, "system-namespace", "", "The namespace catalogd uses for internal state, configuration, and workloads") flag.StringVar(&catalogServerAddr, "catalogs-server-addr", ":8083", "The address where the unpacked catalogs' content will be accessible") - flag.StringVar(&httpExternalAddr, "http-external-address", "http://catalogd-catalogserver.catalogd-system.svc", "The external address at which the http server is reachable.") + flag.StringVar(&externalAddr, "external-address", "catalogd-catalogserver.catalogd-system.svc", "The external address at which the http(s) server is reachable.") flag.StringVar(&cacheDir, "cache-dir", "/var/cache/", "The directory in the filesystem that catalogd will use for file based caching") flag.BoolVar(&catalogdVersion, "version", false, "print the catalogd version and exit") flag.DurationVar(&gcInterval, "gc-interval", 12*time.Hour, "interval in which garbage collection should be run against the catalog content cache") + flag.StringVar(&certFile, "tls-cert", "", "The certificate file used for serving catalog contents over HTTPS. Requires tls-key.") + flag.StringVar(&keyFile, "tls-key", "", "The key file used for serving catalog contents over HTTPS. Requires tls-cert.") opts := zap.Options{ Development: true, } @@ -103,6 +106,18 @@ func main() { } ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) + + if (certFile != "" && keyFile == "") || (certFile == "" && keyFile != "") { + setupLog.Error(nil, "unable to configure TLS certificates: tls-cert and tls-key flags must be used together") + os.Exit(1) + } + + protocol := "http://" + if certFile != "" && keyFile != "" { + protocol = "https://" + } + externalAddr = protocol + externalAddr + cfg := ctrl.GetConfigOrDie() mgr, err := ctrl.NewManager(cfg, ctrl.Options{ Scheme: scheme, @@ -143,29 +158,25 @@ func main() { os.Exit(1) } - baseStorageURL, err := url.Parse(fmt.Sprintf("%s/catalogs/", httpExternalAddr)) + baseStorageURL, err := url.Parse(fmt.Sprintf("%s/catalogs/", externalAddr)) if err != nil { setupLog.Error(err, "unable to create base storage URL") os.Exit(1) } localStorage = storage.LocalDir{RootDir: storeDir, BaseURL: baseStorageURL} - shutdownTimeout := 30 * time.Second - catalogServer := server.Server{ - Kind: "catalogs", - Server: &http.Server{ - Addr: catalogServerAddr, - Handler: catalogdmetrics.AddMetricsToHandler(localStorage.StorageServerHandler()), - ReadTimeout: 5 * time.Second, - // TODO: Revert this to 10 seconds if/when the API - // evolves to have significantly smaller responses - WriteTimeout: 5 * time.Minute, - }, - ShutdownTimeout: &shutdownTimeout, + + catalogServerConfig := serverutil.CatalogServerConfig{ + ExternalAddr: externalAddr, + CatalogAddr: catalogServerAddr, + CertFile: certFile, + KeyFile: keyFile, + LocalStorage: localStorage, } - if err := mgr.Add(&catalogServer); err != nil { - setupLog.Error(err, "unable to start catalog server") + err = serverutil.AddCatalogServerToManager(mgr, catalogServerConfig) + if err != nil { + setupLog.Error(err, "unable to configure catalog server") os.Exit(1) } @@ -202,7 +213,7 @@ func main() { Interval: gcInterval, } if err := mgr.Add(gc); err != nil { - setupLog.Error(err, "problem adding garbage collector to manager") + setupLog.Error(err, "unable to add garbage collector to manager") os.Exit(1) } diff --git a/config/crd/bases/catalogd.operatorframework.io_catalogs.yaml b/config/base/crd/bases/catalogd.operatorframework.io_catalogs.yaml similarity index 100% rename from config/crd/bases/catalogd.operatorframework.io_catalogs.yaml rename to config/base/crd/bases/catalogd.operatorframework.io_catalogs.yaml diff --git a/config/crd/kustomization.yaml b/config/base/crd/kustomization.yaml similarity index 100% rename from config/crd/kustomization.yaml rename to config/base/crd/kustomization.yaml diff --git a/config/crd/patches/catalog_validation.yaml b/config/base/crd/patches/catalog_validation.yaml similarity index 100% rename from config/crd/patches/catalog_validation.yaml rename to config/base/crd/patches/catalog_validation.yaml diff --git a/config/default/kustomization.yaml b/config/base/default/kustomization.yaml similarity index 99% rename from config/default/kustomization.yaml rename to config/base/default/kustomization.yaml index f1c837dd..1754a847 100644 --- a/config/default/kustomization.yaml +++ b/config/base/default/kustomization.yaml @@ -15,4 +15,3 @@ resources: - ../crd - ../rbac - ../manager - diff --git a/config/manager/catalogserver_service.yaml b/config/base/manager/catalogserver_service.yaml similarity index 100% rename from config/manager/catalogserver_service.yaml rename to config/base/manager/catalogserver_service.yaml diff --git a/config/manager/kustomization.yaml b/config/base/manager/kustomization.yaml similarity index 100% rename from config/manager/kustomization.yaml rename to config/base/manager/kustomization.yaml diff --git a/config/manager/manager.yaml b/config/base/manager/manager.yaml similarity index 97% rename from config/manager/manager.yaml rename to config/base/manager/manager.yaml index fc5c87e1..f524f250 100644 --- a/config/manager/manager.yaml +++ b/config/base/manager/manager.yaml @@ -76,7 +76,7 @@ spec: args: - --leader-elect - --metrics-bind-address=127.0.0.1:8080 - - --http-external-address=http://catalogd-catalogserver.catalogd-system.svc + - --external-address=catalogd-catalogserver.catalogd-system.svc image: controller:latest name: manager volumeMounts: diff --git a/config/nginx-ingress/kustomization.yaml b/config/base/nginx-ingress/kustomization.yaml similarity index 100% rename from config/nginx-ingress/kustomization.yaml rename to config/base/nginx-ingress/kustomization.yaml diff --git a/config/nginx-ingress/resources/nginx_ingress.yaml b/config/base/nginx-ingress/resources/nginx_ingress.yaml similarity index 100% rename from config/nginx-ingress/resources/nginx_ingress.yaml rename to config/base/nginx-ingress/resources/nginx_ingress.yaml diff --git a/config/rbac/auth_proxy_client_clusterrole.yaml b/config/base/rbac/auth_proxy_client_clusterrole.yaml similarity index 100% rename from config/rbac/auth_proxy_client_clusterrole.yaml rename to config/base/rbac/auth_proxy_client_clusterrole.yaml diff --git a/config/rbac/auth_proxy_role.yaml b/config/base/rbac/auth_proxy_role.yaml similarity index 100% rename from config/rbac/auth_proxy_role.yaml rename to config/base/rbac/auth_proxy_role.yaml diff --git a/config/rbac/auth_proxy_role_binding.yaml b/config/base/rbac/auth_proxy_role_binding.yaml similarity index 100% rename from config/rbac/auth_proxy_role_binding.yaml rename to config/base/rbac/auth_proxy_role_binding.yaml diff --git a/config/rbac/auth_proxy_service.yaml b/config/base/rbac/auth_proxy_service.yaml similarity index 100% rename from config/rbac/auth_proxy_service.yaml rename to config/base/rbac/auth_proxy_service.yaml diff --git a/config/rbac/kustomization.yaml b/config/base/rbac/kustomization.yaml similarity index 100% rename from config/rbac/kustomization.yaml rename to config/base/rbac/kustomization.yaml diff --git a/config/rbac/leader_election_role.yaml b/config/base/rbac/leader_election_role.yaml similarity index 100% rename from config/rbac/leader_election_role.yaml rename to config/base/rbac/leader_election_role.yaml diff --git a/config/rbac/leader_election_role_binding.yaml b/config/base/rbac/leader_election_role_binding.yaml similarity index 100% rename from config/rbac/leader_election_role_binding.yaml rename to config/base/rbac/leader_election_role_binding.yaml diff --git a/config/base/rbac/role.yaml b/config/base/rbac/role.yaml new file mode 100644 index 00000000..66f6922a --- /dev/null +++ b/config/base/rbac/role.yaml @@ -0,0 +1,65 @@ +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: manager-role +rules: +- apiGroups: + - catalogd.operatorframework.io + resources: + - catalogs + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - catalogd.operatorframework.io + resources: + - catalogs/finalizers + verbs: + - update +- apiGroups: + - catalogd.operatorframework.io + resources: + - catalogs/status + verbs: + - get + - patch + - update +- apiGroups: + - "" + resources: + - pods + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - "" + resources: + - pods/log + verbs: + - get + - list + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: manager-role + namespace: system +rules: +- apiGroups: + - "" + resources: + - secrets + verbs: + - get diff --git a/config/rbac/role_binding.yaml b/config/base/rbac/role_binding.yaml similarity index 100% rename from config/rbac/role_binding.yaml rename to config/base/rbac/role_binding.yaml diff --git a/config/rbac/service_account.yaml b/config/base/rbac/service_account.yaml similarity index 100% rename from config/rbac/service_account.yaml rename to config/base/rbac/service_account.yaml diff --git a/config/overlays/cert-manager/kustomization.yaml b/config/overlays/cert-manager/kustomization.yaml new file mode 100644 index 00000000..5c2fd183 --- /dev/null +++ b/config/overlays/cert-manager/kustomization.yaml @@ -0,0 +1,28 @@ +# Adds namespace to all resources. +namespace: catalogd-system + +# Value of this field is prepended to the +# names of all resources, e.g. a deployment named +# "wordpress" becomes "alices-wordpress". +# Note that it should also match with the prefix (text before '-') of the namespace +# field above. +namePrefix: catalogd- + +# the following config is for teaching kustomize how to do var substitution +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- ../../base/crd +- ../../base/rbac +- ../../base/manager +- resources + +patches: +- target: + kind: Service + name: catalogserver + path: patches/catalogserver_service_port.yaml +- target: + kind: Deployment + name: controller-manager + path: patches/manager_deployment_certs.yaml \ No newline at end of file diff --git a/config/overlays/cert-manager/patches/catalogserver_service_port.yaml b/config/overlays/cert-manager/patches/catalogserver_service_port.yaml new file mode 100644 index 00000000..b5b88bb4 --- /dev/null +++ b/config/overlays/cert-manager/patches/catalogserver_service_port.yaml @@ -0,0 +1,6 @@ +- op: replace + path: /spec/ports/0/port + value: 443 +- op: replace + path: /spec/ports/0/name + value: https \ No newline at end of file diff --git a/config/overlays/cert-manager/patches/manager_deployment_certs.yaml b/config/overlays/cert-manager/patches/manager_deployment_certs.yaml new file mode 100644 index 00000000..37ef4173 --- /dev/null +++ b/config/overlays/cert-manager/patches/manager_deployment_certs.yaml @@ -0,0 +1,12 @@ +- op: add + path: /spec/template/spec/volumes/- + value: {"name":"catalogserver-certs", "secret":{"secretName":"catalogd-catalogserver-cert"}} +- op: add + path: /spec/template/spec/containers/1/volumeMounts/- + value: {"name":"catalogserver-certs", "mountPath":"/var/certs"} +- op: add + path: /spec/template/spec/containers/1/args/- + value: "--tls-cert=/var/certs/tls.crt" +- op: add + path: /spec/template/spec/containers/1/args/- + value: "--tls-key=/var/certs/tls.key" \ No newline at end of file diff --git a/config/overlays/cert-manager/resources/certificate.yaml b/config/overlays/cert-manager/resources/certificate.yaml new file mode 100644 index 00000000..0d0e865d --- /dev/null +++ b/config/overlays/cert-manager/resources/certificate.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: catalogserver-cert + namespace: system +spec: + secretName: catalogd-catalogserver-cert + dnsNames: + - localhost + - catalogd-catalogserver.catalogd-system.svc + issuerRef: + kind: Issuer + name: catalogd-catalogserver-ca-issuer diff --git a/config/overlays/cert-manager/resources/issuer.yaml b/config/overlays/cert-manager/resources/issuer.yaml new file mode 100644 index 00000000..b06f9dd5 --- /dev/null +++ b/config/overlays/cert-manager/resources/issuer.yaml @@ -0,0 +1,37 @@ +--- +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: catalogserver-selfsigned-issuer +spec: + selfSigned: {} +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: catalogserver-ca + namespace: system +spec: + isCA: true + secretName: catalogd-catalogserver-ca + dnsNames: + - catalogd.io + duration: 2160h # 90d + renewBefore: 360h # 15d + privateKey: + rotationPolicy: Always + algorithm: ECDSA + size: 256 + issuerRef: + name: catalogd-catalogserver-selfsigned-issuer + kind: Issuer + group: cert-manager.io +--- +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: catalogserver-ca-issuer + namespace: system +spec: + ca: + secretName: catalogd-catalogserver-ca \ No newline at end of file diff --git a/config/overlays/cert-manager/resources/kustomization.yaml b/config/overlays/cert-manager/resources/kustomization.yaml new file mode 100644 index 00000000..be2bcf4c --- /dev/null +++ b/config/overlays/cert-manager/resources/kustomization.yaml @@ -0,0 +1,3 @@ +resources: +- certificate.yaml +- issuer.yaml \ No newline at end of file diff --git a/docs/fetching-catalog-contents.md b/docs/fetching-catalog-contents.md index b6e96952..b948388c 100644 --- a/docs/fetching-catalog-contents.md +++ b/docs/fetching-catalog-contents.md @@ -1,12 +1,15 @@ # Fetching `Catalog` contents from the Catalogd HTTP Server This document covers how to fetch the contents for a `Catalog` from the -Catalogd HTTP Server that runs when the `HTTPServer` feature-gate is enabled -(enabled by default). +Catalogd HTTP(S) Server. For example purposes we make the following assumption: - A `Catalog` named `operatorhubio` has been created and successfully unpacked (denoted in the `Catalog.Status`) +**NOTE:** By default, Catalogd is configured to use TLS with self-signed certificates. +For local development, consider skipping TLS verification, such as `curl -k`, or reference external material +on self-signed certificate verification. + `Catalog` CRs have a status.contentURL field whose value is the location where the content of a catalog can be read from: @@ -18,7 +21,7 @@ of a catalog can be read from: reason: UnpackSuccessful status: "True" type: Unpacked - contentURL: http://catalogd-catalogserver.catalogd-system.svc/catalogs/operatorhubio/all.json + contentURL: https://catalogd-catalogserver.catalogd-system.svc/catalogs/operatorhubio/all.json phase: Unpacked resolvedSource: image: @@ -34,11 +37,11 @@ object. When making a request for the contents of the `operatorhubio` `Catalog` from within the cluster issue a HTTP `GET` request to -`http://catalogd-catalogserver.catalogd-system.svc/catalogs/operatorhubio/all.json` +`https://catalogd-catalogserver.catalogd-system.svc/catalogs/operatorhubio/all.json` An example command to run a `Pod` to `curl` the catalog contents: ```sh -kubectl run fetcher --image=curlimages/curl:latest -- curl http://catalogd-catalogserver.catalogd-system.svc/catalogs/operatorhubio/all.json +kubectl run fetcher --image=curlimages/curl:latest -- curl https://catalogd-catalogserver.catalogd-system.svc/catalogs/operatorhubio/all.json ``` ## Off cluster @@ -47,11 +50,11 @@ When making a request for the contents of the `operatorhubio` `Catalog` from out the cluster, we have to perform an extra step: 1. Port forward the `catalogd-catalogserver` service in the `catalogd-system` namespace: ```sh -kubectl -n catalogd-system port-forward svc/catalogd-catalogserver 8080:80 +kubectl -n catalogd-system port-forward svc/catalogd-catalogserver 8080:443 ``` Once the service has been successfully forwarded to a localhost port, issue a HTTP `GET` -request to `http://localhost:8080/catalogs/operatorhubio/all.json` +request to `https://localhost:8080/catalogs/operatorhubio/all.json` An example `curl` request that assumes the port-forwarding is mapped to port 8080 on the local machine: ```sh @@ -126,7 +129,7 @@ This section outlines a way of exposing the `Catalogd` Service's endpoints outsi 1. Run the below example `curl` request to retrieve all of the catalog contents: ```sh - $ curl http://
/catalogs/operatorhubio/all.json + $ curl https://
/catalogs/operatorhubio/all.json ``` To obtain `address` of the ingress object, you can run the below command and look for the value in the `ADDRESS` field from output: diff --git a/internal/serverutil/serverutil.go b/internal/serverutil/serverutil.go new file mode 100644 index 00000000..83f8da64 --- /dev/null +++ b/internal/serverutil/serverutil.go @@ -0,0 +1,70 @@ +package serverutil + +import ( + "crypto/tls" + "fmt" + "net" + "net/http" + "time" + + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/certwatcher" + + "github.com/operator-framework/catalogd/internal/third_party/server" + catalogdmetrics "github.com/operator-framework/catalogd/pkg/metrics" + "github.com/operator-framework/catalogd/pkg/storage" +) + +type CatalogServerConfig struct { + ExternalAddr string + CatalogAddr string + CertFile string + KeyFile string + LocalStorage storage.Instance +} + +func AddCatalogServerToManager(mgr ctrl.Manager, cfg CatalogServerConfig) error { + listener, err := net.Listen("tcp", cfg.CatalogAddr) + if err != nil { + return fmt.Errorf("error creating catalog server listener: %w", err) + } + + if cfg.CertFile != "" && cfg.KeyFile != "" { + tlsFileWatcher, err := certwatcher.New(cfg.CertFile, cfg.KeyFile) + if err != nil { + return fmt.Errorf("error creating TLS certificate watcher: %w", err) + } + config := &tls.Config{ + GetCertificate: tlsFileWatcher.GetCertificate, + MinVersion: tls.VersionTLS12, + } + err = mgr.Add(tlsFileWatcher) + if err != nil { + return fmt.Errorf("error adding TLS certificate watcher to manager: %w", err) + } + listener = tls.NewListener(listener, config) + } + + shutdownTimeout := 30 * time.Second + + catalogServer := server.Server{ + Kind: "catalogs", + Server: &http.Server{ + Addr: cfg.CatalogAddr, + Handler: catalogdmetrics.AddMetricsToHandler(cfg.LocalStorage.StorageServerHandler()), + ReadTimeout: 5 * time.Second, + // TODO: Revert this to 10 seconds if/when the API + // evolves to have significantly smaller responses + WriteTimeout: 5 * time.Minute, + }, + ShutdownTimeout: &shutdownTimeout, + Listener: listener, + } + + err = mgr.Add(&catalogServer) + if err != nil { + return fmt.Errorf("error adding catalog server to manager: %w", err) + } + + return nil +} diff --git a/internal/third_party/server/server.go b/internal/third_party/server/server.go index c8cf442c..cfdec7b3 100644 --- a/internal/third_party/server/server.go +++ b/internal/third_party/server/server.go @@ -38,7 +38,7 @@ var ( _ manager.LeaderElectionRunnable = (*Server)(nil) ) -// Server is a general purpose HTTP server Runnable for a manager. +// Server is a general purpose HTTP(S) server Runnable for a manager. // It is used to serve some internal handlers for health probes and profiling, // but it can also be used to run custom servers. type Server struct { @@ -118,5 +118,6 @@ func (s *Server) serve() error { if s.Listener != nil { return s.Server.Serve(s.Listener) } + return s.Server.ListenAndServe() } diff --git a/test/e2e/unpack_test.go b/test/e2e/unpack_test.go index 87a86c42..8ebbdc6b 100644 --- a/test/e2e/unpack_test.go +++ b/test/e2e/unpack_test.go @@ -92,9 +92,9 @@ var _ = Describe("Catalog Unpacking", func() { name := strings.Split(url.Hostname(), ".")[0] port := url.Port() // the ProxyGet() call below needs an explicit port value, so if - // value from url.Port() is empty, we assume port 80. + // value from url.Port() is empty, we assume port 443. if port == "" { - port = "80" + port = "443" } resp := kubeClient.CoreV1().Services(ns).ProxyGet(url.Scheme, name, port, url.Path, map[string]string{}) rc, err := resp.Stream(ctx)