Skip to content

Commit 81819b8

Browse files
author
Paul Cruickshank
committed
Add support for issuing IPSec tunnel certificates using cert-manager.
When cert-manager certificates are enabled, the controller no longer generates the IPSec CA cert or private key stored in the `ovn-ipsec-ca` secret. The secret should be populated with the same CA as configured with cert-manager. It still enables IPSec in OVN NB. When cert-manager certificates are enabled the CNI daemon creates cert-manager CertificateRequest resources instead of CSRs. A cert-manager ClusterIssuer should be configured to approve and sign these CertificateRequests with a matching CA as configured in `ovn-ipsec-ca` secret. The name of the issuer to use is configurable in the CNI. The CNI daemon now watches the `ovn-ipsec-ca` secret for changes allowing for rollout of a new trust bundle. It verifies the currently configured certificate is signed by the new bundle and if not then triggers a new certificate to be issued. The daemon now splits each certificate in the CA bundle into a separate file as strongswan is unable to parse multiple CAs from a single file. The CNI daemon now requests a new certificate when the current certificate is at least half way to expiry based on the times in the certificate. When generating a new certificate the daemon also generates a new key just in case the previous one was leaked somehow. The certificate lifetime is also now configurable rather than lasting for a year. The CNI no longer restarts the ipsec or ovs-ipsec-monitor services when the certificate changes and just requests ipsec to reread the CA certs if they change. To allow for the CNI daemon to keep track of the versions of its key, certificate, and CA cert files it now stores them with locally unique names on disk. Keys and certs are suffixed with the timestamp they were generated. CA files are suffixed with the k8s revision number of the `ovn-ipsec-ca` secret. The cert manager validation webhook (if used) should be run in the host network to prevent the risk of certificate requests deadlocking in the event of a certificate expiry. The CNI pods and cert manager issuer interact with the API server over the host network to create and approve certificates but the API server calls the webhook of the service network which can be broken in the event of an expired certificate. A new kind deployment is created using cert-manager issued certificates and a new e2e test is created that uses it. The e2e test runs through rotating the the CA. Signed-off-by: Paul Cruickshank <pcruickshank@evroc.com>
1 parent a2db6b1 commit 81819b8

File tree

16 files changed

+1049
-156
lines changed

16 files changed

+1049
-156
lines changed

charts/kube-ovn-v2/templates/agent/agent-clusterrole.yaml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,5 +87,9 @@ rules:
8787
- ""
8888
resources:
8989
- "secrets"
90+
resourceNames:
91+
- "ovn-ipsec-ca"
9092
verbs:
91-
- "get"
93+
- "get"
94+
- "list"
95+
- "watch"

charts/kube-ovn/templates/ovn-CR.yaml

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,6 @@ rules:
270270
verbs:
271271
- get
272272
- list
273-
274273
---
275274
apiVersion: rbac.authorization.k8s.io/v1
276275
kind: ClusterRole
@@ -349,12 +348,23 @@ rules:
349348
- "list"
350349
- "watch"
351350
- "delete"
352-
- apiGroups:
353-
- ""
354-
resources:
355-
- "secrets"
356-
verbs:
357-
- "get"
351+
---
352+
apiVersion: rbac.authorization.k8s.io/v1
353+
kind: Role
354+
metadata:
355+
name: secret-reader-ovn-ipsec
356+
namespace: kube-system
357+
rules:
358+
- apiGroups:
359+
- ""
360+
resources:
361+
- "secrets"
362+
resourceNames:
363+
- "ovn-ipsec-ca"
364+
verbs:
365+
- "get"
366+
- "list"
367+
- "watch"
358368
---
359369
apiVersion: rbac.authorization.k8s.io/v1
360370
kind: ClusterRole

charts/kube-ovn/templates/ovn-CRB.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,20 @@ subjects:
6767
namespace: {{ .Values.namespace }}
6868
---
6969
apiVersion: rbac.authorization.k8s.io/v1
70+
kind: RoleBinding
71+
metadata:
72+
name: kube-ovn-cni-secret-reader
73+
namespace: kube-system
74+
subjects:
75+
- kind: ServiceAccount
76+
name: kube-ovn-cni
77+
namespace: kube-system
78+
roleRef:
79+
kind: Role
80+
name: secret-reader-ovn-ipsec
81+
apiGroup: rbac.authorization.k8s.io
82+
---
83+
apiVersion: rbac.authorization.k8s.io/v1
7084
kind: ClusterRoleBinding
7185
metadata:
7286
name: kube-ovn-app

cmd/daemon/cniserver.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package main
22

33
import (
44
"errors"
5+
"fmt"
56
"net"
67
"net/http"
78
"net/http/pprof"
@@ -95,7 +96,15 @@ func main() {
9596
kubeovninformer.WithTweakListOptions(func(listOption *v1.ListOptions) {
9697
listOption.AllowWatchBookmarks = true
9798
}))
98-
ctl, err := daemon.NewController(config, stopCh, podInformerFactory, nodeInformerFactory, kubeovnInformerFactory)
99+
100+
caSecretInformerFactory := kubeinformers.NewSharedInformerFactoryWithOptions(config.KubeClient, 0,
101+
kubeinformers.WithTweakListOptions(func(listOption *v1.ListOptions) {
102+
listOption.FieldSelector = fmt.Sprintf("metadata.name=%s", util.DefaultOVNIPSecCA)
103+
}),
104+
kubeinformers.WithNamespace("kube-system"),
105+
)
106+
107+
ctl, err := daemon.NewController(config, stopCh, podInformerFactory, nodeInformerFactory, caSecretInformerFactory, kubeovnInformerFactory)
99108
if err != nil {
100109
util.LogFatalAndExit(err, "failed to create controller")
101110
}

dist/images/install.sh

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ OVS_VSCTL_CONCURRENCY=${OVS_VSCTL_CONCURRENCY:-100}
4141
ENABLE_COMPACT=${ENABLE_COMPACT:-false}
4242
SECURE_SERVING=${SECURE_SERVING:-false}
4343
ENABLE_OVN_IPSEC=${ENABLE_OVN_IPSEC:-false}
44+
CERT_MANAGER_IPSEC_CERT=${CERT_MANAGER_IPSEC_CERT:-false}
45+
IPSEC_CERT_DURATION=${IPSEC_CERT_DURATION:-63072000} # 2 years in seconds
46+
CERT_MANAGER_ISSUER_NAME=${CERT_MANAGER_ISSUER_NAME:-kube-ovn}
4447
ENABLE_ANP=${ENABLE_ANP:-false}
4548
SET_VXLAN_TX_OFF=${SET_VXLAN_TX_OFF:-false}
4649
OVSDB_CON_TIMEOUT=${OVSDB_CON_TIMEOUT:-3}
@@ -3773,12 +3776,32 @@ rules:
37733776
- "list"
37743777
- "watch"
37753778
- "delete"
3776-
- apiGroups:
3777-
- ""
3778-
resources:
3779-
- "secrets"
3780-
verbs:
3781-
- "get"
3779+
---
3780+
apiVersion: rbac.authorization.k8s.io/v1
3781+
kind: Role
3782+
metadata:
3783+
name: secret-reader-ovn-ipsec
3784+
namespace: kube-system
3785+
rules:
3786+
- apiGroups:
3787+
- ""
3788+
resources:
3789+
- "secrets"
3790+
resourceNames:
3791+
- "ovn-ipsec-ca"
3792+
verbs:
3793+
- "get"
3794+
- "list"
3795+
- "watch"
3796+
- apiGroups:
3797+
- "cert-manager.io"
3798+
resources:
3799+
- "certificaterequests"
3800+
verbs:
3801+
- "get"
3802+
- "list"
3803+
- "create"
3804+
- "delete"
37823805
---
37833806
apiVersion: rbac.authorization.k8s.io/v1
37843807
kind: ClusterRoleBinding
@@ -3806,6 +3829,20 @@ subjects:
38063829
- kind: ServiceAccount
38073830
name: kube-ovn-cni
38083831
namespace: kube-system
3832+
---
3833+
apiVersion: rbac.authorization.k8s.io/v1
3834+
kind: RoleBinding
3835+
metadata:
3836+
name: kube-ovn-cni-secret-reader
3837+
namespace: kube-system
3838+
subjects:
3839+
- kind: ServiceAccount
3840+
name: kube-ovn-cni
3841+
namespace: kube-system
3842+
roleRef:
3843+
kind: Role
3844+
name: secret-reader-ovn-ipsec
3845+
apiGroup: rbac.authorization.k8s.io
38093846
EOF
38103847

38113848
cat <<EOF > kube-ovn-app-sa.yaml
@@ -4608,6 +4645,7 @@ spec:
46084645
- --enable-metrics=$ENABLE_METRICS
46094646
- --node-local-dns-ip=$NODE_LOCAL_DNS_IP
46104647
- --enable-ovn-ipsec=$ENABLE_OVN_IPSEC
4648+
- --cert-manager-ipsec-cert=$CERT_MANAGER_IPSEC_CERT
46114649
- --secure-serving=${SECURE_SERVING}
46124650
- --enable-anp=$ENABLE_ANP
46134651
- --ovsdb-con-timeout=$OVSDB_CON_TIMEOUT
@@ -4803,6 +4841,9 @@ spec:
48034841
- --ovs-vsctl-concurrency=$OVS_VSCTL_CONCURRENCY
48044842
- --secure-serving=${SECURE_SERVING}
48054843
- --enable-ovn-ipsec=$ENABLE_OVN_IPSEC
4844+
- --cert-manager-ipsec-cert=$CERT_MANAGER_IPSEC_CERT
4845+
- --ovn-ipsec-cert-duration=$IPSEC_CERT_DURATION
4846+
- --cert-manager-issuer-name=$CERT_MANAGER_ISSUER_NAME
48064847
- --set-vxlan-tx-off=$SET_VXLAN_TX_OFF
48074848
securityContext:
48084849
runAsUser: 0

e2e.mk

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,15 @@ kube-ovn-ipsec-e2e:
248248
ginkgo $(GINKGO_OUTPUT_OPT) $(GINKGO_PARALLEL_OPT) --randomize-all -v \
249249
--focus=CNI:Kube-OVN ./test/e2e/ipsec/ipsec.test -- $(TEST_BIN_ARGS)
250250

251+
.PHONY: kube-ovn-ipsec-e2e-cert-mgr
252+
kube-ovn-ipsec-e2e-cert-mgr:
253+
ginkgo build $(E2E_BUILD_FLAGS) ./test/e2e/ipsec-cert-mgr
254+
E2E_BRANCH=$(E2E_BRANCH) \
255+
E2E_IP_FAMILY=$(E2E_IP_FAMILY) \
256+
E2E_NETWORK_MODE=$(E2E_NETWORK_MODE) \
257+
ginkgo $(GINKGO_OUTPUT_OPT) $(GINKGO_PARALLEL_OPT) --randomize-all -v \
258+
--focus=CNI:Kube-OVN ./test/e2e/ipsec-cert-mgr/ipsec-cert-mgr.test -- $(TEST_BIN_ARGS)
259+
251260
.PHONY: kube-ovn-anp-e2e
252261
kube-ovn-anp-e2e:
253262
KUBECONFIG=$(KUBECONFIG) ./test/anp/conformance.sh

go.mod

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ require (
7575
sigs.k8s.io/network-policy-api v0.1.5
7676
)
7777

78+
require sigs.k8s.io/gateway-api v1.1.0 // indirect
79+
7880
require (
7981
cel.dev/expr v0.24.0 // indirect
8082
cloud.google.com/go/compute/metadata v0.7.0 // indirect
@@ -93,6 +95,7 @@ require (
9395
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
9496
github.com/cenkalti/hub v1.0.2 // indirect
9597
github.com/cenkalti/rpc2 v1.0.4 // indirect
98+
github.com/cert-manager/cert-manager v1.17.1
9699
github.com/cespare/xxhash/v2 v2.3.0 // indirect
97100
github.com/chai2010/gettext-go v1.0.3 // indirect
98101
github.com/container-storage-interface/spec v1.11.0 // indirect
@@ -160,7 +163,7 @@ require (
160163
github.com/httprunner/funplugin v0.5.5 // indirect
161164
github.com/inconshreveable/mousetrap v1.1.0 // indirect
162165
github.com/jinzhu/copier v0.4.0 // indirect
163-
github.com/jmespath/go-jmespath v0.4.0 // indirect
166+
github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24 // indirect
164167
github.com/josharian/intern v1.0.0 // indirect
165168
github.com/josharian/native v1.1.0 // indirect
166169
github.com/json-iterator/go v1.1.12 // indirect

go.sum

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ github.com/cenkalti/hub v1.0.2/go.mod h1:8LAFAZcCasb83vfxatMUnZHRoQcffho2ELpHb+k
5454
github.com/cenkalti/rpc2 v1.0.4 h1:MJWmm7mbt8r/ZkQS+qr/e2KMMrhMLPr/62CYZIHybdI=
5555
github.com/cenkalti/rpc2 v1.0.4/go.mod h1:2yfU5b86vOr16+iY1jN3MvT6Kxc9Nf8j5iZWwUf7iaw=
5656
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
57+
github.com/cert-manager/cert-manager v1.17.1 h1:Aig+lWMoLsmpGd9TOlTvO4t0Ah3D+/vGB37x/f+ZKt0=
58+
github.com/cert-manager/cert-manager v1.17.1/go.mod h1:zeG4D+AdzqA7hFMNpYCJgcQ2VOfFNBa+Jzm3kAwiDU4=
5759
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
5860
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
5961
github.com/chai2010/gettext-go v1.0.3 h1:9liNh8t+u26xl5ddmWLmsOsdNLwkdRTg5AG+JnTiM80=
@@ -336,8 +338,8 @@ github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgf
336338
github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo=
337339
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
338340
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
339-
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
340-
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
341+
github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24 h1:liMMTbpW34dhU4az1GN0pTPADwNmvoRSeoZ6PItiqnY=
342+
github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
341343
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
342344
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
343345
github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4=
@@ -1225,6 +1227,8 @@ sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.32.1 h1:Cf+ed5N8038zb
12251227
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.32.1/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw=
12261228
sigs.k8s.io/controller-runtime v0.20.4 h1:X3c+Odnxz+iPTRobG4tp092+CvBU9UK0t/bRf+n0DGU=
12271229
sigs.k8s.io/controller-runtime v0.20.4/go.mod h1:xg2XB0K5ShQzAgsoujxuKN4LNXR2LfwwHsPj7Iaw+XY=
1230+
sigs.k8s.io/gateway-api v1.1.0 h1:DsLDXCi6jR+Xz8/xd0Z1PYl2Pn0TyaFMOPPZIj4inDM=
1231+
sigs.k8s.io/gateway-api v1.1.0/go.mod h1:ZH4lHrL2sDi0FHZ9jjneb8kKnGzFWyrTya35sWUTrRs=
12281232
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
12291233
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo=
12301234
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=

kind.mk

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -651,6 +651,34 @@ kind-install-kwok:
651651
kind-install-ovn-ipsec:
652652
@$(MAKE) ENABLE_OVN_IPSEC=true kind-install
653653

654+
.PHONY: kind-install-cert-manager
655+
kind-install-cert-manager:
656+
$(call kind_load_image,kube-ovn,$(CERT_MANAGER_CONTROLLER),1)
657+
kubectl apply -f "$(CERT_MANAGER_YAML)"
658+
kubectl rollout status deployment/cert-manager -n cert-manager --timeout 120s
659+
660+
.PHONY: kind-install-ovn-ipsec-cert-manager
661+
kind-install-ovn-ipsec-cert-manager:
662+
@$(MAKE) ENABLE_OVN_IPSEC=true CERT_MANAGER_IPSEC_CERT=true kind-install
663+
@$(MAKE) kind-install-cert-manager
664+
665+
kubectl rollout status deployment/cert-manager-webhook -n cert-manager --timeout 120s
666+
667+
$(eval CA_KEY = $(shell mktemp))
668+
$(shell openssl genrsa -out $(CA_KEY) 2048)
669+
$(eval CA_CERT = $(shell openssl req -x509 -new -nodes \
670+
-key "$(CA_KEY)" \
671+
-days 3650 \
672+
-subj "/C=US/ST=California/L=Palo Alto/O=Open vSwitch/OU=Open vSwitch Certification Authority/CN=OVS CA" | \
673+
base64 -w 0 -))
674+
675+
$(eval CA_KEY64 = $(shell base64 -w 0 "$(CA_KEY)"))
676+
677+
sed -e 's/KUBE_OVN_CA_KEY/$(CA_KEY64)/g' \
678+
-e 's/KUBE_OVN_CA_CERT/$(CA_CERT)/g' yamls/ipsec-certs.yaml | \
679+
kubectl apply -f -
680+
rm $(CA_KEY)
681+
654682
.PHONY: kind-install-anp
655683
kind-install-anp: kind-load-image
656684
$(call kind_load_image,kube-ovn,$(ANP_TEST_IMAGE),1)

pkg/controller/config.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ type Configuration struct {
9797
EnableMetrics bool
9898
EnableANP bool
9999
EnableOVNIPSec bool
100+
CertManagerIPSECCert bool
100101
EnableLiveMigrationOptimize bool
101102

102103
ExternalGatewaySwitch string
@@ -188,6 +189,7 @@ func ParseFlags() (*Configuration, error) {
188189
argEnableMetrics = pflag.Bool("enable-metrics", true, "Whether to support metrics query")
189190
argEnableANP = pflag.Bool("enable-anp", false, "Enable support for admin network policy and baseline admin network policy")
190191
argEnableOVNIPSec = pflag.Bool("enable-ovn-ipsec", false, "Whether to enable ovn ipsec")
192+
argCertManagerIPSECCert = pflag.Bool("cert-manager-ipsec-cert", false, "Whether to use cert-manager for signing IPSec certificates")
191193
argEnableLiveMigrationOptimize = pflag.Bool("enable-live-migration-optimize", true, "Whether to enable kubevirt live migration optimize")
192194

193195
argExternalGatewayConfigNS = pflag.String("external-gateway-config-ns", "kube-system", "The namespace of configmap external-gateway-config, default: kube-system")
@@ -290,6 +292,7 @@ func ParseFlags() (*Configuration, error) {
290292
EnableOVNLBPreferLocal: *argEnableOVNLBPreferLocal,
291293
EnableMetrics: *argEnableMetrics,
292294
EnableOVNIPSec: *argEnableOVNIPSec,
295+
CertManagerIPSECCert: *argCertManagerIPSECCert,
293296
EnableLiveMigrationOptimize: *argEnableLiveMigrationOptimize,
294297
BfdMinTx: *argBfdMinTx,
295298
BfdMinRx: *argBfdMinRx,

0 commit comments

Comments
 (0)