Skip to content

Commit ad27daa

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 56b36b1 commit ad27daa

File tree

16 files changed

+1058
-156
lines changed

16 files changed

+1058
-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
@@ -273,7 +273,6 @@ rules:
273273
verbs:
274274
- get
275275
- list
276-
277276
---
278277
apiVersion: rbac.authorization.k8s.io/v1
279278
kind: ClusterRole
@@ -352,12 +351,23 @@ rules:
352351
- "list"
353352
- "watch"
354353
- "delete"
355-
- apiGroups:
356-
- ""
357-
resources:
358-
- "secrets"
359-
verbs:
360-
- "get"
354+
---
355+
apiVersion: rbac.authorization.k8s.io/v1
356+
kind: Role
357+
metadata:
358+
name: secret-reader-ovn-ipsec
359+
namespace: kube-system
360+
rules:
361+
- apiGroups:
362+
- ""
363+
resources:
364+
- "secrets"
365+
resourceNames:
366+
- "ovn-ipsec-ca"
367+
verbs:
368+
- "get"
369+
- "list"
370+
- "watch"
361371
---
362372
apiVersion: rbac.authorization.k8s.io/v1
363373
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}
@@ -3775,12 +3778,32 @@ rules:
37753778
- "list"
37763779
- "watch"
37773780
- "delete"
3778-
- apiGroups:
3779-
- ""
3780-
resources:
3781-
- "secrets"
3782-
verbs:
3783-
- "get"
3781+
---
3782+
apiVersion: rbac.authorization.k8s.io/v1
3783+
kind: Role
3784+
metadata:
3785+
name: secret-reader-ovn-ipsec
3786+
namespace: kube-system
3787+
rules:
3788+
- apiGroups:
3789+
- ""
3790+
resources:
3791+
- "secrets"
3792+
resourceNames:
3793+
- "ovn-ipsec-ca"
3794+
verbs:
3795+
- "get"
3796+
- "list"
3797+
- "watch"
3798+
- apiGroups:
3799+
- "cert-manager.io"
3800+
resources:
3801+
- "certificaterequests"
3802+
verbs:
3803+
- "get"
3804+
- "list"
3805+
- "create"
3806+
- "delete"
37843807
---
37853808
apiVersion: rbac.authorization.k8s.io/v1
37863809
kind: ClusterRoleBinding
@@ -3808,6 +3831,20 @@ subjects:
38083831
- kind: ServiceAccount
38093832
name: kube-ovn-cni
38103833
namespace: kube-system
3834+
---
3835+
apiVersion: rbac.authorization.k8s.io/v1
3836+
kind: RoleBinding
3837+
metadata:
3838+
name: kube-ovn-cni-secret-reader
3839+
namespace: kube-system
3840+
subjects:
3841+
- kind: ServiceAccount
3842+
name: kube-ovn-cni
3843+
namespace: kube-system
3844+
roleRef:
3845+
kind: Role
3846+
name: secret-reader-ovn-ipsec
3847+
apiGroup: rbac.authorization.k8s.io
38113848
EOF
38123849

38133850
cat <<EOF > kube-ovn-app-sa.yaml
@@ -4610,6 +4647,7 @@ spec:
46104647
- --enable-metrics=$ENABLE_METRICS
46114648
- --node-local-dns-ip=$NODE_LOCAL_DNS_IP
46124649
- --enable-ovn-ipsec=$ENABLE_OVN_IPSEC
4650+
- --cert-manager-ipsec-cert=$CERT_MANAGER_IPSEC_CERT
46134651
- --secure-serving=${SECURE_SERVING}
46144652
- --enable-anp=$ENABLE_ANP
46154653
- --ovsdb-con-timeout=$OVSDB_CON_TIMEOUT
@@ -4805,6 +4843,9 @@ spec:
48054843
- --ovs-vsctl-concurrency=$OVS_VSCTL_CONCURRENCY
48064844
- --secure-serving=${SECURE_SERVING}
48074845
- --enable-ovn-ipsec=$ENABLE_OVN_IPSEC
4846+
- --cert-manager-ipsec-cert=$CERT_MANAGER_IPSEC_CERT
4847+
- --ovn-ipsec-cert-duration=$IPSEC_CERT_DURATION
4848+
- --cert-manager-issuer-name=$CERT_MANAGER_ISSUER_NAME
48084849
- --set-vxlan-tx-off=$SET_VXLAN_TX_OFF
48094850
securityContext:
48104851
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
@@ -657,6 +657,34 @@ kind-install-kwok:
657657
kind-install-ovn-ipsec:
658658
@$(MAKE) ENABLE_OVN_IPSEC=true kind-install
659659

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