bake ca into image (#626)

* bake ca into image

* Update README.md and values.schema.json with readme-generator-for-helm

* certs/ -> certs/*

* update configmap for forwarder pods

* change when we add cert and remove volumes

* update snapshot

* update tunnel driver with cert utl function

* make path const and re-add volume mount block

* const -> var

* re-add extra volume mounts correctly

ty mason

Co-authored-by: Mason Johnson <mason@ngrok.com>

* remove test

---------

Co-authored-by: Megalonia <Megalonia@users.noreply.github.com>
Co-authored-by: Mason Johnson <mason@ngrok.com>
This commit is contained in:
Mo
2025-04-23 10:40:27 -05:00
committed by GitHub
parent 30385ae651
commit 1d21084d54
15 changed files with 210 additions and 183 deletions
+1
View File
@@ -15,6 +15,7 @@
!internal/
!pkg/
!scripts/
!certs/*
!**/*.go
!**/*.mod
!**/*.sum
+2 -1
View File
@@ -18,12 +18,13 @@ ARG TARGETOS TARGETARCH
# Build
RUN --mount=type=cache,target=/go \
CGO_ENABLED=0 GOOS="${TARGETOS}" GOARCH="${TARGETARCH}" make _build
# Use distroless as minimal base image to package the manager binary
# Refer to https://github.com/GoogleContainerTools/distroless for more details
FROM gcr.io/distroless/static:nonroot
COPY certs /etc/ssl/certs/ngrok
WORKDIR /
COPY --from=builder /workspace/bin/api-manager /workspace/bin/agent-manager /workspace/bin/bindings-forwarder-manager ./
USER 65532:65532
ENTRYPOINT ["/api-manager"]
+23
View File
@@ -0,0 +1,23 @@
-----BEGIN CERTIFICATE-----
MIIDwjCCAqqgAwIBAgIUZqF2AkB17pISojTndgc2U5BDt7wwDQYJKoZIhvcNAQEL
BQAwbzEQMA4GA1UEAwwHUm9vdCBDQTENMAsGA1UECwwEcHJvZDESMBAGA1UECgwJ
bmdyb2sgSW5jMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMRMwEQYDVQQIDApDYWxp
Zm9ybmlhMQswCQYDVQQGEwJVUzAeFw0yMjA4MzExNDU5NDhaFw0zMjA4MjgxNDU5
NDhaMF8xCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQK
DAluZ3JvayBJbmMxDTALBgNVBAsMBHByb2QxGDAWBgNVBAMMD0ludGVybWVkaWF0
ZSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK+t8q9Ost9BxCWX
fyGG0mVQOpIiyrzzZWyqT6CZpMY2fpOadLuZeBP7ti2Iw4FgCpfLntL0RldvMMNY
4qq61dVrCwhL/v2ldsaHUdzjtFj1i+ZNGUtV4E9korHxm2YdsD91w6WIjF/J0lvo
X2koLwFlGc/CkhT8z2VWebY8a6mYNyz5S7yPTQh2/mQ14lx/QPJgZSFEE/EEkMDC
bs4BoMuqKMhCpqEP8m4+CxPQ5/V6POSqUIxT4A7eWWj2MRpnmirmVbXOc24Aznqk
bdQUP4qagiR/i7qPsRx+f4mFfDninPsXp/djjByo0xzdh+i1HFyOR/7nyNDKlJ+e
rymRgnUCAwEAAaNmMGQwHQYDVR0OBBYEFJ47nRzHaOT+vY44N3TCMYtGlBjIMB8G
A1UdIwQYMBaAFNxeUxPIM8G7cX0DhFc81pLD4W+HMBIGA1UdEwEB/wQIMAYBAf8C
AQAwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4IBAQBRmnMoOtQbYL7P
Co1B5Chslb86HP2WI1jGRXhbfwAF2ySDFnX2ZbRPVtoQ+IuqXWxyXAeicYjXR6kz
xX8hLWfD14kWUIz6ZgT3uZrDSIzmQ+tz8ztbT6mTI1ECWdjLV/i58f6vKzgLD8Vp
3VdVns8NA9ee6a65QNjZEnwBVeccysoWkOwM/KzuazhSGcGu44y/S4ny9pAg7Pol
2kV4NicDKD6tSAdXmPmjFalYUfnMmyhurZIPrS2dgYgpOrGVMwronTOZ3BUf4DL4
zkkmcLXss1KztQnLd23nuNiIscwMcGM58a3O5zUp7aorfrm7cdRgkFmcYVNO/6uG
Q5iJ+Ppk
-----END CERTIFICATE-----
+7
View File
@@ -42,6 +42,7 @@ import (
bindingsv1alpha1 "github.com/ngrok/ngrok-operator/api/bindings/v1alpha1"
ngrokv1alpha1 "github.com/ngrok/ngrok-operator/api/ngrok/v1alpha1"
bindingscontroller "github.com/ngrok/ngrok-operator/internal/controller/bindings"
"github.com/ngrok/ngrok-operator/internal/util"
"github.com/ngrok/ngrok-operator/internal/version"
"github.com/ngrok/ngrok-operator/pkg/bindingsdriver"
// +kubebuilder:scaffold:imports
@@ -137,6 +138,11 @@ func runController(_ context.Context, opts managerOpts) error {
bd := bindingsdriver.New()
certPool, err := util.LoadCerts()
if err != nil {
return err
}
if err = (&bindingscontroller.ForwarderReconciler{
Client: mgr.GetClient(),
Log: ctrl.Log.WithName("controllers").WithName("bindings-forwarder"),
@@ -144,6 +150,7 @@ func runController(_ context.Context, opts managerOpts) error {
Recorder: mgr.GetEventRecorderFor("bindings-forwarder-controller"),
BindingsDriver: bd,
KubernetesOperatorName: opts.releaseName,
RootCAs: certPool,
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "BindingsForwarder")
os.Exit(1)
+16 -40
View File
@@ -153,43 +153,19 @@ To uninstall the chart:
### Kubernetes Bindings feature configuration
| Name | Description | Value |
| ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `bindings.enabled` | Whether to enable the Endpoint Bindings feature | `false` |
| `bindings.endpointSelectors` | List of cel expressions used to filter which kubernetes-bound endpoints should be projected into this cluster | `["true"]` |
| `bindings.serviceAnnotations` | Annotations to add to projected services bound to an endpoint | `{}` |
| `bindings.serviceLabels` | Labels to add to projected services bound to an endpoint | `{}` |
| `bindings.ingressEndpoint` | The hostname of the ingress endpoint for the bindings | `kubernetes-binding-ingress.ngrok.io:443` |
| `bindings.forwarder.replicaCount` | The number of bindings forwarders to run. | `1` |
| `bindings.forwarder.resources.limits` | The resources limits for the container | `{}` |
| `bindings.forwarder.resources.requests` | The requested resources for the container | `{}` |
| `bindings.forwarder.serviceAccount.create` | Specifies whether a ServiceAccount should be created for the bindings forwarder pod(s). | `true` |
| `bindings.forwarder.serviceAccount.name` | The name of the ServiceAccount to use for the bindings forwarder pod(s). | `""` |
| `bindings.forwarder.serviceAccount.annotations` | Additional annotations to add to the bindings-forwarder ServiceAccount | `{}` |
| `bindings.forwarder.tolerations` | Tolerations for the bindings forwarder pod(s) | `[]` |
| `bindings.forwarder.nodeSelector` | Node labels for the bindings forwarder pod(s) | `{}` |
| `bindings.forwarder.topologySpreadConstraints` | Topology Spread Constraints for the bindings forwarder pod(s) | `[]` |
| `bindings.ngrokCA` | The ngrok intermediate CA certificate to use for verifyng self-signed TLS certs from ngrok | `-----BEGIN CERTIFICATE-----
MIIDwjCCAqqgAwIBAgIUZqF2AkB17pISojTndgc2U5BDt7wwDQYJKoZIhvcNAQEL
BQAwbzEQMA4GA1UEAwwHUm9vdCBDQTENMAsGA1UECwwEcHJvZDESMBAGA1UECgwJ
bmdyb2sgSW5jMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMRMwEQYDVQQIDApDYWxp
Zm9ybmlhMQswCQYDVQQGEwJVUzAeFw0yMjA4MzExNDU5NDhaFw0zMjA4MjgxNDU5
NDhaMF8xCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQK
DAluZ3JvayBJbmMxDTALBgNVBAsMBHByb2QxGDAWBgNVBAMMD0ludGVybWVkaWF0
ZSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK+t8q9Ost9BxCWX
fyGG0mVQOpIiyrzzZWyqT6CZpMY2fpOadLuZeBP7ti2Iw4FgCpfLntL0RldvMMNY
4qq61dVrCwhL/v2ldsaHUdzjtFj1i+ZNGUtV4E9korHxm2YdsD91w6WIjF/J0lvo
X2koLwFlGc/CkhT8z2VWebY8a6mYNyz5S7yPTQh2/mQ14lx/QPJgZSFEE/EEkMDC
bs4BoMuqKMhCpqEP8m4+CxPQ5/V6POSqUIxT4A7eWWj2MRpnmirmVbXOc24Aznqk
bdQUP4qagiR/i7qPsRx+f4mFfDninPsXp/djjByo0xzdh+i1HFyOR/7nyNDKlJ+e
rymRgnUCAwEAAaNmMGQwHQYDVR0OBBYEFJ47nRzHaOT+vY44N3TCMYtGlBjIMB8G
A1UdIwQYMBaAFNxeUxPIM8G7cX0DhFc81pLD4W+HMBIGA1UdEwEB/wQIMAYBAf8C
AQAwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4IBAQBRmnMoOtQbYL7P
Co1B5Chslb86HP2WI1jGRXhbfwAF2ySDFnX2ZbRPVtoQ+IuqXWxyXAeicYjXR6kz
xX8hLWfD14kWUIz6ZgT3uZrDSIzmQ+tz8ztbT6mTI1ECWdjLV/i58f6vKzgLD8Vp
3VdVns8NA9ee6a65QNjZEnwBVeccysoWkOwM/KzuazhSGcGu44y/S4ny9pAg7Pol
2kV4NicDKD6tSAdXmPmjFalYUfnMmyhurZIPrS2dgYgpOrGVMwronTOZ3BUf4DL4
zkkmcLXss1KztQnLd23nuNiIscwMcGM58a3O5zUp7aorfrm7cdRgkFmcYVNO/6uG
Q5iJ+Ppk
-----END CERTIFICATE-----
` |
| Name | Description | Value |
| ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | ----------------------------------------- |
| `bindings.enabled` | Whether to enable the Endpoint Bindings feature | `false` |
| `bindings.endpointSelectors` | List of cel expressions used to filter which kubernetes-bound endpoints should be projected into this cluster | `["true"]` |
| `bindings.serviceAnnotations` | Annotations to add to projected services bound to an endpoint | `{}` |
| `bindings.serviceLabels` | Labels to add to projected services bound to an endpoint | `{}` |
| `bindings.ingressEndpoint` | The hostname of the ingress endpoint for the bindings | `kubernetes-binding-ingress.ngrok.io:443` |
| `bindings.forwarder.replicaCount` | The number of bindings forwarders to run. | `1` |
| `bindings.forwarder.resources.limits` | The resources limits for the container | `{}` |
| `bindings.forwarder.resources.requests` | The requested resources for the container | `{}` |
| `bindings.forwarder.serviceAccount.create` | Specifies whether a ServiceAccount should be created for the bindings forwarder pod(s). | `true` |
| `bindings.forwarder.serviceAccount.name` | The name of the ServiceAccount to use for the bindings forwarder pod(s). | `""` |
| `bindings.forwarder.serviceAccount.annotations` | Additional annotations to add to the bindings-forwarder ServiceAccount | `{}` |
| `bindings.forwarder.tolerations` | Tolerations for the bindings forwarder pod(s) | `[]` |
| `bindings.forwarder.nodeSelector` | Node labels for the bindings forwarder pod(s) | `{}` |
| `bindings.forwarder.topologySpreadConstraints` | Topology Spread Constraints for the bindings forwarder pod(s) | `[]` |
@@ -1,13 +0,0 @@
{{- if .Values.bindings.enabled }}
---
kind: ConfigMap
apiVersion: v1
metadata:
labels:
{{- include "ngrok-operator.labels" . | nindent 4 }}
name: ngrok-intermediate-ca
namespace: {{ .Release.Namespace }}
data:
root.crt: |
{{- .Values.bindings.ngrokCA | nindent 4 }}
{{- end }}
@@ -95,23 +95,13 @@ spec:
fieldPath: metadata.namespace
- name: HELM_RELEASE_NAME
value: {{ .Release.Name | quote }}
- name: SSL_CERT_DIR
value: /etc/ssl/certs/ngrok
{{- range $key, $value := .Values.extraEnv }}
- name: {{ $key }}
value: {{- toYaml $value | nindent 12 }}
{{- end }}
{{- if .Values.extraVolumeMounts }}
{{- if .Values.extraVolumeMounts }}
volumeMounts:
{{- toYaml .Values.extraVolumeMounts | nindent 8 }}
- name: ngrok-ca
mountPath: /etc/ssl/certs/ngrok
readOnly: true
{{- else }}
volumeMounts:
- name: ngrok-ca
mountPath: /etc/ssl/certs/ngrok
readOnly: true
{{- end }}
{{- if .Values.lifecycle }}
lifecycle:
@@ -134,13 +124,5 @@ spec:
{{- if .Values.extraVolumes }}
volumes:
{{- toYaml .Values.extraVolumes | nindent 8 }}
- name: ngrok-ca
configMap:
name: ngrok-intermediate-ca
{{- else }}
volumes:
- name: ngrok-ca
configMap:
name: ngrok-intermediate-ca
{{- end }}
{{- end }}
@@ -71,8 +71,6 @@ Should match snapshot:
fieldPath: metadata.namespace
- name: HELM_RELEASE_NAME
value: RELEASE-NAME
- name: SSL_CERT_DIR
value: /etc/ssl/certs/ngrok
image: docker.io/ngrok/ngrok-operator:0.16.1
imagePullPolicy: IfNotPresent
livenessProbe:
@@ -93,12 +91,4 @@ Should match snapshot:
requests: {}
securityContext:
allowPrivilegeEscalation: false
volumeMounts:
- mountPath: /etc/ssl/certs/ngrok
name: ngrok-ca
readOnly: true
serviceAccountName: RELEASE-NAME-ngrok-operator-bindings-forwarder
volumes:
- configMap:
name: ngrok-intermediate-ca
name: ngrok-ca
-5
View File
@@ -504,11 +504,6 @@
"items": {}
}
}
},
"ngrokCA": {
"type": "string",
"description": "The ngrok intermediate CA certificate to use for verifyng self-signed TLS certs from ngrok",
"default": "-----BEGIN CERTIFICATE-----\nMIIDwjCCAqqgAwIBAgIUZqF2AkB17pISojTndgc2U5BDt7wwDQYJKoZIhvcNAQEL\nBQAwbzEQMA4GA1UEAwwHUm9vdCBDQTENMAsGA1UECwwEcHJvZDESMBAGA1UECgwJ\nbmdyb2sgSW5jMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMRMwEQYDVQQIDApDYWxp\nZm9ybmlhMQswCQYDVQQGEwJVUzAeFw0yMjA4MzExNDU5NDhaFw0zMjA4MjgxNDU5\nNDhaMF8xCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQK\nDAluZ3JvayBJbmMxDTALBgNVBAsMBHByb2QxGDAWBgNVBAMMD0ludGVybWVkaWF0\nZSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK+t8q9Ost9BxCWX\nfyGG0mVQOpIiyrzzZWyqT6CZpMY2fpOadLuZeBP7ti2Iw4FgCpfLntL0RldvMMNY\n4qq61dVrCwhL/v2ldsaHUdzjtFj1i+ZNGUtV4E9korHxm2YdsD91w6WIjF/J0lvo\nX2koLwFlGc/CkhT8z2VWebY8a6mYNyz5S7yPTQh2/mQ14lx/QPJgZSFEE/EEkMDC\nbs4BoMuqKMhCpqEP8m4+CxPQ5/V6POSqUIxT4A7eWWj2MRpnmirmVbXOc24Aznqk\nbdQUP4qagiR/i7qPsRx+f4mFfDninPsXp/djjByo0xzdh+i1HFyOR/7nyNDKlJ+e\nrymRgnUCAwEAAaNmMGQwHQYDVR0OBBYEFJ47nRzHaOT+vY44N3TCMYtGlBjIMB8G\nA1UdIwQYMBaAFNxeUxPIM8G7cX0DhFc81pLD4W+HMBIGA1UdEwEB/wQIMAYBAf8C\nAQAwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4IBAQBRmnMoOtQbYL7P\nCo1B5Chslb86HP2WI1jGRXhbfwAF2ySDFnX2ZbRPVtoQ+IuqXWxyXAeicYjXR6kz\nxX8hLWfD14kWUIz6ZgT3uZrDSIzmQ+tz8ztbT6mTI1ECWdjLV/i58f6vKzgLD8Vp\n3VdVns8NA9ee6a65QNjZEnwBVeccysoWkOwM/KzuazhSGcGu44y/S4ny9pAg7Pol\n2kV4NicDKD6tSAdXmPmjFalYUfnMmyhurZIPrS2dgYgpOrGVMwronTOZ3BUf4DL4\nzkkmcLXss1KztQnLd23nuNiIscwMcGM58a3O5zUp7aorfrm7cdRgkFmcYVNO/6uG\nQ5iJ+Ppk\n-----END CERTIFICATE-----\n"
}
}
}
-27
View File
@@ -371,30 +371,3 @@ bindings:
## @param bindings.forwarder.topologySpreadConstraints Topology Spread Constraints for the bindings forwarder pod(s)
topologySpreadConstraints: []
## @param bindings.ngrokCA The ngrok intermediate CA certificate to use for verifyng self-signed TLS certs from ngrok
## Note: This is temporarily vendored and will be removed in a future release.
ngrokCA: | # ngrok intermediate CA
-----BEGIN CERTIFICATE-----
MIIDwjCCAqqgAwIBAgIUZqF2AkB17pISojTndgc2U5BDt7wwDQYJKoZIhvcNAQEL
BQAwbzEQMA4GA1UEAwwHUm9vdCBDQTENMAsGA1UECwwEcHJvZDESMBAGA1UECgwJ
bmdyb2sgSW5jMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMRMwEQYDVQQIDApDYWxp
Zm9ybmlhMQswCQYDVQQGEwJVUzAeFw0yMjA4MzExNDU5NDhaFw0zMjA4MjgxNDU5
NDhaMF8xCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQK
DAluZ3JvayBJbmMxDTALBgNVBAsMBHByb2QxGDAWBgNVBAMMD0ludGVybWVkaWF0
ZSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK+t8q9Ost9BxCWX
fyGG0mVQOpIiyrzzZWyqT6CZpMY2fpOadLuZeBP7ti2Iw4FgCpfLntL0RldvMMNY
4qq61dVrCwhL/v2ldsaHUdzjtFj1i+ZNGUtV4E9korHxm2YdsD91w6WIjF/J0lvo
X2koLwFlGc/CkhT8z2VWebY8a6mYNyz5S7yPTQh2/mQ14lx/QPJgZSFEE/EEkMDC
bs4BoMuqKMhCpqEP8m4+CxPQ5/V6POSqUIxT4A7eWWj2MRpnmirmVbXOc24Aznqk
bdQUP4qagiR/i7qPsRx+f4mFfDninPsXp/djjByo0xzdh+i1HFyOR/7nyNDKlJ+e
rymRgnUCAwEAAaNmMGQwHQYDVR0OBBYEFJ47nRzHaOT+vY44N3TCMYtGlBjIMB8G
A1UdIwQYMBaAFNxeUxPIM8G7cX0DhFc81pLD4W+HMBIGA1UdEwEB/wQIMAYBAf8C
AQAwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4IBAQBRmnMoOtQbYL7P
Co1B5Chslb86HP2WI1jGRXhbfwAF2ySDFnX2ZbRPVtoQ+IuqXWxyXAeicYjXR6kz
xX8hLWfD14kWUIz6ZgT3uZrDSIzmQ+tz8ztbT6mTI1ECWdjLV/i58f6vKzgLD8Vp
3VdVns8NA9ee6a65QNjZEnwBVeccysoWkOwM/KzuazhSGcGu44y/S4ny9pAg7Pol
2kV4NicDKD6tSAdXmPmjFalYUfnMmyhurZIPrS2dgYgpOrGVMwronTOZ3BUf4DL4
zkkmcLXss1KztQnLd23nuNiIscwMcGM58a3O5zUp7aorfrm7cdRgkFmcYVNO/6uG
Q5iJ+Ppk
-----END CERTIFICATE-----
@@ -27,6 +27,7 @@ package bindings
import (
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"io"
"net"
@@ -65,6 +66,7 @@ type ForwarderReconciler struct {
BindingsDriver *bindingsdriver.BindingsDriver
KubernetesOperatorName string
RootCAs *x509.CertPool
}
func (r *ForwarderReconciler) SetupWithManager(mgr ctrl.Manager) (err error) {
@@ -170,6 +172,10 @@ func (r *ForwarderReconciler) update(ctx context.Context, epb *bindingsv1alpha1.
},
}
if r.RootCAs != nil {
tlsDialer.Config.RootCAs = r.RootCAs
}
endpointURI, err := url.Parse(epb.Spec.EndpointURI)
if err != nil {
return err
+63
View File
@@ -0,0 +1,63 @@
package util
import (
"crypto/x509"
"fmt"
"io/fs"
"os"
"path/filepath"
"sync"
)
var (
// TODO: Make this configurable via helm and document it so users can
// use it for things like proxies
customCertsPath = "/etc/ssl/certs/ngrok/"
ngrokCertPool *x509.CertPool
loadCertsOnce sync.Once
loadCertsOnceErr error
)
func LoadCerts() (*x509.CertPool, error) {
// Load all certificates from the well known ngrok certs directory,
// combine them with the default certs, and save the cert pool once.
// If we've already done this, just return the cert pool.
loadCertsOnce.Do(func() {
var err error
// Load the system cert pool
ngrokCertPool, err = x509.SystemCertPool()
if err != nil {
loadCertsOnceErr = err
return
}
// Now, walk the ngrok certs dir and add all the certs to the cert pool
loadCertsOnceErr = filepath.WalkDir(customCertsPath, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
// Skip directories
if d.IsDir() {
return nil
}
// Open the file
content, err := os.ReadFile(path)
if err != nil {
return err
}
if ok := ngrokCertPool.AppendCertsFromPEM(content); !ok {
return fmt.Errorf("failed to append certs from %s", path)
}
return nil
})
// if WalkDir or cert appending fails, clear the pool
if loadCertsOnceErr != nil {
ngrokCertPool = nil
}
})
return ngrokCertPool, loadCertsOnceErr
}
+85
View File
@@ -0,0 +1,85 @@
package util
import (
"os"
"path/filepath"
"sync"
"testing"
"github.com/stretchr/testify/require"
)
func resetCertsState() {
ngrokCertPool = nil
loadCertsOnce = sync.Once{}
loadCertsOnceErr = nil
}
func TestLoadCerts_ValidPEM(t *testing.T) {
resetCertsState()
tmpDir := t.TempDir()
customCertsPath = tmpDir
err := os.WriteFile(filepath.Join(tmpDir, "valid.pem"), []byte(validPEM), 0644)
require.NoError(t, err)
pool, err := LoadCerts()
require.NoError(t, err)
require.NotNil(t, pool)
}
func TestLoadCerts_InvalidPEM(t *testing.T) {
resetCertsState()
tmpDir := t.TempDir()
customCertsPath = tmpDir
err := os.WriteFile(filepath.Join(tmpDir, "invalid.pem"), []byte("not a cert"), 0644)
require.NoError(t, err)
pool, err := LoadCerts()
require.Error(t, err, "expected error, got nil")
t.Logf("err: %v", err)
require.Nil(t, pool, "expected nil pool when certs path is missing")
require.Error(t, err)
}
func TestLoadCerts_MissingPath(t *testing.T) {
resetCertsState()
tmpDir := filepath.Join(t.TempDir(), "nonexistent")
customCertsPath = tmpDir
pool, err := LoadCerts()
require.Error(t, err, "expected error, got nil")
t.Logf("err: %v", err)
// pool will be nil because cert loading failed completely
require.Nil(t, pool, "expected nil pool when certs path is missing")
}
const validPEM = `-----BEGIN CERTIFICATE-----
MIIDwjCCAqqgAwIBAgIUZqF2AkB17pISojTndgc2U5BDt7wwDQYJKoZIhvcNAQEL
BQAwbzEQMA4GA1UEAwwHUm9vdCBDQTENMAsGA1UECwwEcHJvZDESMBAGA1UECgwJ
bmdyb2sgSW5jMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMRMwEQYDVQQIDApDYWxp
Zm9ybmlhMQswCQYDVQQGEwJVUzAeFw0yMjA4MzExNDU5NDhaFw0zMjA4MjgxNDU5
NDhaMF8xCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQK
DAluZ3JvayBJbmMxDTALBgNVBAsMBHByb2QxGDAWBgNVBAMMD0ludGVybWVkaWF0
ZSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK+t8q9Ost9BxCWX
fyGG0mVQOpIiyrzzZWyqT6CZpMY2fpOadLuZeBP7ti2Iw4FgCpfLntL0RldvMMNY
4qq61dVrCwhL/v2ldsaHUdzjtFj1i+ZNGUtV4E9korHxm2YdsD91w6WIjF/J0lvo
X2koLwFlGc/CkhT8z2VWebY8a6mYNyz5S7yPTQh2/mQ14lx/QPJgZSFEE/EEkMDC
bs4BoMuqKMhCpqEP8m4+CxPQ5/V6POSqUIxT4A7eWWj2MRpnmirmVbXOc24Aznqk
bdQUP4qagiR/i7qPsRx+f4mFfDninPsXp/djjByo0xzdh+i1HFyOR/7nyNDKlJ+e
rymRgnUCAwEAAaNmMGQwHQYDVR0OBBYEFJ47nRzHaOT+vY44N3TCMYtGlBjIMB8G
A1UdIwQYMBaAFNxeUxPIM8G7cX0DhFc81pLD4W+HMBIGA1UdEwEB/wQIMAYBAf8C
AQAwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4IBAQBRmnMoOtQbYL7P
Co1B5Chslb86HP2WI1jGRXhbfwAF2ySDFnX2ZbRPVtoQ+IuqXWxyXAeicYjXR6kz
xX8hLWfD14kWUIz6ZgT3uZrDSIzmQ+tz8ztbT6mTI1ECWdjLV/i58f6vKzgLD8Vp
3VdVns8NA9ee6a65QNjZEnwBVeccysoWkOwM/KzuazhSGcGu44y/S4ny9pAg7Pol
2kV4NicDKD6tSAdXmPmjFalYUfnMmyhurZIPrS2dgYgpOrGVMwronTOZ3BUf4DL4
zkkmcLXss1KztQnLd23nuNiIscwMcGM58a3O5zUp7aorfrm7cdRgkFmcYVNO/6uG
Q5iJ+Ppk
-----END CERTIFICATE-----`
+6 -56
View File
@@ -3,15 +3,12 @@ package tunneldriver
import (
"context"
"crypto/tls"
"crypto/x509"
"encoding/json"
"errors"
"fmt"
"io"
"net"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
@@ -21,6 +18,7 @@ import (
commonv1alpha1 "github.com/ngrok/ngrok-operator/api/common/v1alpha1"
ingressv1alpha1 "github.com/ngrok/ngrok-operator/api/ingress/v1alpha1"
ngrokv1alpha1 "github.com/ngrok/ngrok-operator/api/ngrok/v1alpha1"
"github.com/ngrok/ngrok-operator/internal/util"
"github.com/ngrok/ngrok-operator/internal/version"
"golang.org/x/exp/maps"
"golang.org/x/sync/errgroup"
@@ -43,12 +41,6 @@ func (l k8sLogger) Log(_ context.Context, level logrok.LogLevel, msg string, kvs
l.logger.V(level-4).Info(msg, keysAndValues...)
}
const (
// TODO: Make this configurable via helm and document it so users can
// use it for things like proxies
customCertsPath = "/etc/ssl/certs/ngrok/"
)
type commonEndpointOption interface {
config.HTTPEndpointOption
config.TLSEndpointOption
@@ -163,14 +155,11 @@ func New(ctx context.Context, logger logr.Logger, opts TunnelDriverOpts) (*Tunne
return nil, fmt.Errorf("invalid value for RootCAs: %s", opts.RootCAs)
}
// Configure certs if the custom cert directory exists or host if set
if _, err := os.Stat(customCertsPath); !os.IsNotExist(err) || isHostCA {
caCerts, err := caCerts(isHostCA)
if err != nil {
return nil, err
}
connOpts = append(connOpts, ngrok.WithCA(caCerts))
certPool, err := util.LoadCerts()
if err != nil {
return nil, err
}
connOpts = append(connOpts, ngrok.WithCA(certPool))
if isHostCA {
connOpts = append(connOpts, ngrok.WithTLSConfig(func(c *tls.Config) {
@@ -231,7 +220,7 @@ func New(ctx context.Context, logger logr.Logger, opts TunnelDriverOpts) (*Tunne
}
}),
)
_, err := ngrok.Connect(ctx, connOpts...)
_, err = ngrok.Connect(ctx, connOpts...)
return td, err
}
@@ -262,45 +251,6 @@ func (td *TunnelDriver) getSession() (ngrok.Session, error) {
}
}
// caCerts combines the system ca certs with a directory of custom ca certs
func caCerts(hostCA bool) (*x509.CertPool, error) {
systemCertPool, err := x509.SystemCertPool()
if err != nil {
return nil, err
}
// we're all set if we're using the host CA
if hostCA {
return systemCertPool, nil
}
// Clone the system cert pool
customCertPool := systemCertPool.Clone()
// Read each .crt file in the custom cert directory
files, err := os.ReadDir(customCertsPath)
if err != nil {
return nil, err
}
for _, file := range files {
if filepath.Ext(file.Name()) != ".crt" {
continue
}
// Read the contents of the .crt file
certBytes, err := os.ReadFile(filepath.Join(customCertsPath, file.Name()))
if err != nil {
return nil, err
}
// Append the cert to the custom cert pool
customCertPool.AppendCertsFromPEM(certBytes)
}
return customCertPool, nil
}
// CreateTunnel creates and starts a new tunnel in a goroutine. If a tunnel with the same name already exists,
// it will be stopped and replaced with a new tunnel unless the labels match.
func (td *TunnelDriver) CreateTunnel(ctx context.Context, name string, spec ingressv1alpha1.TunnelSpec) error {
@@ -42,15 +42,3 @@ spec:
("tls.crt" != null && "tls.crt" != ""): true
("tls.csr" != null && "tls.csr" != ""): true
("tls.key" != null && "tls.key" != ""): true
- name: assert Configmap/ngrok-intermediate-ca exists (tunnels/forwarders will work)
try:
- assert:
resource:
apiVersion: v1
kind: ConfigMap
metadata:
name: ngrok-intermediate-ca
namespace: ngrok-operator
data:
("root.crt" != null): true