diff --git a/cmd/hepto/config.go b/cmd/hepto/config.go
index 3ad960e03c631dcef7d2f1d1d82d46280317a442..93e77476cc81829a87f50a6a68f26fde871230af 100644
--- a/cmd/hepto/config.go
+++ b/cmd/hepto/config.go
@@ -63,6 +63,6 @@ func init() {
 	// Node settings
 	rootCmd.Flags().IntVar(&config.Node.Port, "discovery-port", 7123, "TCP port used for discovering the cluster")
 	rootCmd.Flags().StringVar(&config.Node.Name, "name", "", "Hepto node name")
-	rootCmd.Flags().Var(&config.Node.Anchors, "anchors", "List of cluster anchors")
+	rootCmd.Flags().StringSliceVar(&config.Node.Anchors, "anchors", []string{}, "List of cluster anchors")
 	rootCmd.Flags().StringVar(&config.Node.Role, "role", "node", "Node role inside the cluster")
 }
diff --git a/go.mod b/go.mod
index 5be6b93288ec31779c0c2c63c1252f5129ed834d..fb61918ec2522a1e1b613aa482c1599aabc7fe9c 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
 module forge.tedomum.net/acides/hepto/hepto
 
-go 1.17
+go 1.18
 
 require (
 	github.com/containerd/containerd v1.5.10
@@ -9,15 +9,15 @@ require (
 	github.com/docker/docker v20.10.8+incompatible
 	github.com/google/cadvisor v0.39.3
 	github.com/hashicorp/memberlist v0.3.1
-	github.com/opencontainers/runc v1.0.2
+	github.com/opencontainers/runc v1.1.3
 	github.com/pkg/errors v0.9.1
-	github.com/sirupsen/logrus v1.8.1
+	github.com/sirupsen/logrus v1.9.0
 	github.com/spf13/cobra v1.4.0
 	github.com/spf13/pflag v1.0.5
 	github.com/spf13/viper v1.11.0
-	github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852
+	github.com/vishvananda/netlink v1.2.1-beta.2
 	go.etcd.io/etcd/server/v3 v3.5.0
-	golang.org/x/sys v0.0.0-20220412211240-33da011f77ad
+	golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2
 	golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220504211119-3d4a969bb56b
 	k8s.io/api v0.22.7
 	k8s.io/apiextensions-apiserver v0.0.0
@@ -61,16 +61,16 @@ require (
 	github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a // indirect
 	github.com/aws/aws-sdk-go v1.38.49 // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
-	github.com/bits-and-blooms/bitset v1.2.0 // indirect
+	github.com/bits-and-blooms/bitset v1.3.0 // indirect
 	github.com/blang/semver v3.5.1+incompatible // indirect
 	github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44 // indirect
 	github.com/cespare/xxhash/v2 v2.1.1 // indirect
-	github.com/checkpoint-restore/go-criu/v5 v5.0.0 // indirect
-	github.com/cilium/ebpf v0.6.2 // indirect
+	github.com/checkpoint-restore/go-criu/v5 v5.3.0 // indirect
+	github.com/cilium/ebpf v0.9.1 // indirect
 	github.com/clusterhq/flocker-go v0.0.0-20160920122132-2b8b7259d313 // indirect
 	github.com/container-storage-interface/spec v1.5.0 // indirect
 	github.com/containerd/cgroups v1.0.1 // indirect
-	github.com/containerd/console v1.0.2 // indirect
+	github.com/containerd/console v1.0.3 // indirect
 	github.com/containerd/continuity v0.1.0 // indirect
 	github.com/containerd/fifo v1.0.0 // indirect
 	github.com/containerd/go-cni v1.0.2 // indirect
@@ -84,8 +84,8 @@ require (
 	github.com/coreos/go-iptables v0.5.0 // indirect
 	github.com/coreos/go-oidc v2.1.0+incompatible // indirect
 	github.com/coreos/go-semver v0.3.0 // indirect
-	github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
-	github.com/cyphar/filepath-securejoin v0.2.2 // indirect
+	github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
+	github.com/cyphar/filepath-securejoin v0.2.3 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/docker/distribution v2.7.1+incompatible // indirect
 	github.com/docker/go-connections v0.4.0 // indirect
@@ -104,7 +104,7 @@ require (
 	github.com/go-openapi/jsonreference v0.19.5 // indirect
 	github.com/go-openapi/swag v0.19.14 // indirect
 	github.com/go-ozzo/ozzo-validation v3.5.0+incompatible // indirect
-	github.com/godbus/dbus/v5 v5.0.4 // indirect
+	github.com/godbus/dbus/v5 v5.1.0 // indirect
 	github.com/gofrs/uuid v4.0.0+incompatible // indirect
 	github.com/gogo/googleapis v1.4.0 // indirect
 	github.com/gogo/protobuf v1.3.2 // indirect
@@ -156,7 +156,7 @@ require (
 	github.com/mitchellh/mapstructure v1.4.3 // indirect
 	github.com/moby/locker v1.0.1 // indirect
 	github.com/moby/spdystream v0.2.0 // indirect
-	github.com/moby/sys/mountinfo v0.4.1 // indirect
+	github.com/moby/sys/mountinfo v0.6.2 // indirect
 	github.com/moby/sys/symlink v0.1.0 // indirect
 	github.com/moby/term v0.0.0-20210610120745-9d4ed1856297 // indirect
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
@@ -169,7 +169,7 @@ require (
 	github.com/opencontainers/go-digest v1.0.0 // indirect
 	github.com/opencontainers/image-spec v1.0.2 // indirect
 	github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417 // indirect
-	github.com/opencontainers/selinux v1.8.2 // indirect
+	github.com/opencontainers/selinux v1.10.1 // indirect
 	github.com/pelletier/go-toml v1.9.4 // indirect
 	github.com/pelletier/go-toml/v2 v2.0.0-beta.8 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
@@ -184,7 +184,7 @@ require (
 	github.com/russross/blackfriday/v2 v2.1.0 // indirect
 	github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8 // indirect
 	github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect
-	github.com/seccomp/libseccomp-golang v0.9.1 // indirect
+	github.com/seccomp/libseccomp-golang v0.10.0 // indirect
 	github.com/soheilhy/cmux v0.1.5 // indirect
 	github.com/spf13/afero v1.8.2 // indirect
 	github.com/spf13/cast v1.4.1 // indirect
@@ -197,8 +197,8 @@ require (
 	github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 // indirect
 	github.com/tchap/go-patricia v2.2.6+incompatible // indirect
 	github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 // indirect
-	github.com/urfave/cli v1.22.2 // indirect
-	github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae // indirect
+	github.com/urfave/cli v1.22.9 // indirect
+	github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
 	github.com/vmware/govmomi v0.20.3 // indirect
 	github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect
 	go.etcd.io/bbolt v1.3.6 // indirect
@@ -225,7 +225,7 @@ require (
 	go.uber.org/multierr v1.6.0 // indirect
 	go.uber.org/zap v1.17.0 // indirect
 	golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect
-	golang.org/x/net v0.0.0-20220418201149-a630d4f3e7a2 // indirect
+	golang.org/x/net v0.0.0-20220812174116-3211cb980234 // indirect
 	golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
 	golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
@@ -239,7 +239,7 @@ require (
 	google.golang.org/appengine v1.6.7 // indirect
 	google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac // indirect
 	google.golang.org/grpc v1.45.0 // indirect
-	google.golang.org/protobuf v1.28.0 // indirect
+	google.golang.org/protobuf v1.28.1 // indirect
 	gopkg.in/gcfg.v1 v1.2.0 // indirect
 	gopkg.in/inf.v0 v0.9.1 // indirect
 	gopkg.in/ini.v1 v1.66.4 // indirect
@@ -267,6 +267,7 @@ require (
 replace (
 	// These pinned dependencies are documented in HACKS.md
 	github.com/containernetworking/plugins => github.com/rancher/plugins v0.9.1-k3s1
+	github.com/opencontainers/runc => ./runc
 	google.golang.org/genproto => google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63
 	gopkg.in/square/go-jose.v2 => gopkg.in/square/go-jose.v2 v2.2.2
 	// Kubernetes dependencies are pinned to override v0.0.0 inside the staged kubernetes root
diff --git a/go.sum b/go.sum
index 7db8219251ec7042ce2d0b0a81e984c51148c4f5..56bc28b26145efb7617abfcebc49b73061ed31a2 100644
--- a/go.sum
+++ b/go.sum
@@ -158,6 +158,8 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB
 github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
 github.com/bits-and-blooms/bitset v1.2.0 h1:Kn4yilvwNtMACtf1eYDlG8H77R07mZSPbMjLyS07ChA=
 github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
+github.com/bits-and-blooms/bitset v1.3.0 h1:h7mv5q31cthBTd7V4kLAZaIThj1e8vPGcSqpPue9KVI=
+github.com/bits-and-blooms/bitset v1.3.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
 github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
 github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
 github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
@@ -184,6 +186,8 @@ github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1
 github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw=
 github.com/checkpoint-restore/go-criu/v5 v5.0.0 h1:TW8f/UvntYoVDMN1K2HlT82qH1rb0sOjpGw3m6Ym+i4=
 github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M=
+github.com/checkpoint-restore/go-criu/v5 v5.3.0 h1:wpFFOoomK3389ue2lAb0Boag6XPht5QYpipxmSNL4d8=
+github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E=
 github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
 github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
 github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@@ -194,6 +198,8 @@ github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJ
 github.com/cilium/ebpf v0.5.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
 github.com/cilium/ebpf v0.6.2 h1:iHsfF/t4aW4heW2YKfeHrVPGdtYTL4C4KocpM8KTSnI=
 github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
+github.com/cilium/ebpf v0.9.1 h1:64sn2K3UKw8NbP/blsixRpF3nXuyhz/VjRlRzvlBRu4=
+github.com/cilium/ebpf v0.9.1/go.mod h1:+OhNOIXx/Fnu1IE8bJz2dzOA+VSfyTfdNUVdlQnxUFY=
 github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
 github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
@@ -236,6 +242,8 @@ github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4g
 github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw=
 github.com/containerd/console v1.0.2 h1:Pi6D+aZXM+oUw1czuKgH5IJ+y0jhYcwBJfx5/Ghn9dE=
 github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ=
+github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw=
+github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
 github.com/containerd/containerd v1.2.10/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
 github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
 github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
@@ -334,11 +342,15 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:ma
 github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
 github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU=
 github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
+github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
+github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=
 github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg=
 github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
+github.com/cyphar/filepath-securejoin v0.2.3 h1:YX6ebbZCZP7VkM3scTTokDgBL2TY741X51MTk3ycuNI=
+github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
 github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ=
 github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s=
 github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8=
@@ -444,10 +456,13 @@ github.com/go-ozzo/ozzo-validation v3.5.0+incompatible/go.mod h1:gsEKFIVnabGBt6m
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
 github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
 github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
+github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e h1:BWhy2j3IXJhjCbC68FptL43tDKIq8FladmaTs3Xs7Z8=
 github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
 github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA=
 github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
+github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
+github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
 github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
 github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU=
@@ -744,6 +759,8 @@ github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0Gq
 github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A=
 github.com/moby/sys/mountinfo v0.4.1 h1:1O+1cHA1aujwEwwVMa2Xm2l+gIpUHyd3+D+d7LZh1kM=
 github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A=
+github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78=
+github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI=
 github.com/moby/sys/symlink v0.1.0 h1:MTFZ74KtNI6qQQpuBxU+uKCim4WtOMokr03hCfJcazE=
 github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ=
 github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc=
@@ -821,6 +838,8 @@ github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqi
 github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo=
 github.com/opencontainers/selinux v1.8.2 h1:c4ca10UMgRcvZ6h0K4HtS15UaVSBEaE+iln2LVpAuGc=
 github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8=
+github.com/opencontainers/selinux v1.10.1 h1:09LIPVRP3uuZGQvgR+SgMSNBd1Eb3vlRbGqQpoHsF8w=
+github.com/opencontainers/selinux v1.10.1/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI=
 github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
 github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
 github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
@@ -905,6 +924,8 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUt
 github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
 github.com/seccomp/libseccomp-golang v0.9.1 h1:NJjM5DNFOs0s3kYE1WUOr6G8V97sdt46rlXTMfXGWBo=
 github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo=
+github.com/seccomp/libseccomp-golang v0.10.0 h1:aA4bp+/Zzi0BnWZ2F1wgNBs5gTpm+na2rWM6M9YjLpY=
+github.com/seccomp/libseccomp-golang v0.10.0/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg=
 github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
 github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
@@ -916,6 +937,8 @@ github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrf
 github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
 github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
 github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
+github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
+github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
 github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
 github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
@@ -985,14 +1008,20 @@ github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/
 github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
 github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo=
 github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
+github.com/urfave/cli v1.22.9 h1:cv3/KhXGBGjEXLC4bH0sLuJ9BewaAbpk5oyMOveu4pw=
+github.com/urfave/cli v1.22.9/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
 github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc=
 github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
 github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
 github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852 h1:cPXZWzzG0NllBLdjWoD1nDfaqu98YMv+OneaKc8sPOA=
 github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
+github.com/vishvananda/netlink v1.2.1-beta.2 h1:Llsql0lnQEbHj0I1OuKyp8otXp0r3q0mPkuhwHfStVs=
+github.com/vishvananda/netlink v1.2.1-beta.2/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
 github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
 github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae h1:4hwBBUfQCFe3Cym0ZtKyq7L16eZUtYKs+BaHDN6mAns=
 github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
+github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg=
+github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
 github.com/vmware/govmomi v0.20.3 h1:gpw/0Ku+6RgF3jsi7fnCLmlcikBHfKBCUcu1qgc16OU=
 github.com/vmware/govmomi v0.20.3/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU=
 github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
@@ -1201,6 +1230,8 @@ golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su
 golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/net v0.0.0-20220418201149-a630d4f3e7a2 h1:6mzvA99KwZxbOrxww4EvWVQUnN1+xEu9tafK5ZxkYeA=
 golang.org/x/net v0.0.0-20220418201149-a630d4f3e7a2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220812174116-3211cb980234 h1:RDqmgfe7SvlMWoqC3xwQ2blLO3fcWcxMa3eBLRdRW7E=
+golang.org/x/net v0.0.0-20220812174116-3211cb980234/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1339,6 +1370,10 @@ golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0=
 golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2 h1:fqTvyMIIj+HRzMmnzr9NtpHP6uVpvB5fkHcgPDC4nu8=
+golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@@ -1539,6 +1574,8 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
 google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
 google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
+google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/pkg/cluster/cluster.go b/pkg/cluster/cluster.go
index 6132aefbdce0d1c8219c5e875bc9fabf38aa775c..16c2070f89c16017fcd0ae5f6e1cfb3d741c0087 100644
--- a/pkg/cluster/cluster.go
+++ b/pkg/cluster/cluster.go
@@ -5,6 +5,7 @@ package cluster
 import (
 	"net"
 
+	"forge.tedomum.net/acides/hepto/hepto/pkg/pki"
 	"forge.tedomum.net/acides/hepto/hepto/pkg/sml"
 	"forge.tedomum.net/acides/hepto/hepto/pkg/wg"
 	"github.com/sirupsen/logrus"
@@ -12,10 +13,11 @@ import (
 
 type Cluster struct {
 	settings   *ClusterSettings
-	ml         *sml.Memberlist
+	ml         *sml.Memberlist[HeptoMeta]
 	vpn        *wg.Wireguard
 	networking *ClusterNetworking
 	node       *NodeSettings
+	certs      *NodeCerts
 	pki        *ClusterPKI
 }
 
@@ -26,14 +28,14 @@ func New(settings *ClusterSettings, node *NodeSettings) (*Cluster, error) {
 		networking: NewClusterNetworking(settings.Name, node.Name),
 	}
 	// Prepare memberlist
-	c.ml = sml.New(node.Name, node.IP, node.Port, node.Anchors.IPSlice(), settings.Key, newMeta)
+	c.ml = sml.New(node.Name, node.IP, node.Port, node.Anchors, settings.Key, HeptoMeta{})
 	// Prepare wireguard
 	vpn, err := wg.New("wg0", 7124, c.networking.NodeAddress.IPNet())
 	if err != nil {
 		return nil, err
 	}
 	c.vpn = vpn
-	// Initialize cluster PKI if necessary
+	// Initialize cluster PKI and local keys
 	if node.Role == "master" {
 		pki, err := NewClusterPKI("pki")
 		if err != nil {
@@ -41,9 +43,17 @@ func New(settings *ClusterSettings, node *NodeSettings) (*Cluster, error) {
 		}
 		c.pki = pki
 	}
+	certs, err := NewNodeCerts("certs", node.Name)
+	if err != nil {
+		return nil, err
+	}
+	c.certs = certs
 	// Initialize cluster meta
-	meta := c.ml.Meta.(*HeptoMeta)
+	meta := c.ml.Meta
 	meta.VpnKey = vpn.PubKey.String()
+	meta.Role = node.Role
+	meta.Certs = make(map[string]NodePKIBytes)
+	meta.CSR.APIClient = certs.APIClient.CSR
 	return c, nil
 }
 
@@ -59,6 +69,7 @@ func (c *Cluster) Run() error {
 	for {
 		select {
 		case <-events:
+			c.handlePKI()
 			c.updateVPN()
 		case <-instrUpdates:
 			c.networking.MTU = instr.MinMTU()
@@ -67,13 +78,56 @@ func (c *Cluster) Run() error {
 	}
 }
 
+func (c *Cluster) handlePKI() {
+	logrus.Debug("handling possible PKI messages")
+	for _, node := range c.ml.Nodes() {
+		meta := node.NodeMeta
+		if meta.Role == "master" {
+			logrus.Debug("found master node, checking certs")
+			logrus.Debug(string(node.Meta))
+			if certs, ok := meta.Certs[c.node.Name]; ok {
+				if len(certs.APIClient) > 0 {
+					logrus.Debug("certificate is signed")
+					err := c.certs.APIClient.SetCert(certs.APIClient)
+					if err != nil {
+						logrus.Warn("cannot load certificate")
+					}
+				}
+			}
+		}
+	}
+	if c.pki != nil {
+		logrus.Debug("this is the master, generating required certificates")
+		for _, node := range c.ml.Nodes() {
+			meta := node.NodeMeta
+			if len(meta.CSR.APIClient) > 0 {
+				template := pki.NewClientTemplate(node.Name, "")
+				cert, err := c.pki.APIServer.Sign(meta.CSR.APIClient, template)
+				if err != nil {
+					logrus.Warn("cannot sign certificate")
+					continue
+				}
+				logrus.Debug("signed for ", node.Name, cert)
+				this := c.ml.Meta
+				var certs NodePKIBytes
+				if certs, ok := this.Certs[node.Name]; !ok {
+					this.Certs[node.Name] = certs
+				}
+				certs.APIClient = cert
+				this.Certs[node.Name] = certs
+				c.ml.Update()
+			}
+		}
+	}
+}
+
 func (c *Cluster) updateVPN() {
 	peers := []wg.Peer{}
 	for _, node := range c.ml.Nodes() {
 		if node.Name == c.node.Name {
 			continue
 		}
-		meta := node.NodeMeta.(*HeptoMeta)
+		meta := node.NodeMeta
 		peerAddr := c.networking.NodeNet.DeriveAddress(node.Name).IP
 		peer, err := c.vpn.MakePeer(node.Addr, meta.VpnKey, peerAddr, []net.IPNet{})
 		if err != nil {
diff --git a/pkg/cluster/config.go b/pkg/cluster/config.go
index 3850cad23daaea66415bafefeed649f029365b13..a7e5332731b0baa604bb522b280b2eb2cea2e683 100644
--- a/pkg/cluster/config.go
+++ b/pkg/cluster/config.go
@@ -23,7 +23,7 @@ type NodeSettings struct {
 	// Public IPv6 address for the node
 	IP net.IP
 	// Anchors for this node to join
-	Anchors types.AddressSlice
+	Anchors []string
 }
 
 type ClusterNetworking struct {
diff --git a/pkg/cluster/meta.go b/pkg/cluster/meta.go
index 69b97a947d19af29f5006fc3498254d708c513c2..d12bfa6f9edec15bfc820f3aa8a8f86ee608ddfe 100644
--- a/pkg/cluster/meta.go
+++ b/pkg/cluster/meta.go
@@ -6,18 +6,34 @@ import (
 	"forge.tedomum.net/acides/hepto/hepto/pkg/sml"
 )
 
+// Represents a node CSRs and returned certs
+type NodePKIBytes struct {
+	// APIclient certificate
+	APIClient []byte `json:"apiclient"`
+}
+
 // Represents a node metadata
 type HeptoMeta struct {
 	// Public key for the wireguard mesh VPN
 	VpnKey string `json:"vpnKey"`
+	// Node role inside the cluster
+	Role string `json:"role"`
+	// Certificate CSRs
+	CSR NodePKIBytes `json:"csr"`
+	// Only on master, signed certificates
+	Certs map[string]NodePKIBytes `json:"certs"`
+}
+
+func (m HeptoMeta) Encode() ([]byte, error) {
+	return json.Marshal(&m)
 }
 
-func (m *HeptoMeta) Encode() ([]byte, error) {
-	return json.Marshal(m)
+func (m HeptoMeta) Decode(b []byte) error {
+	return json.Unmarshal(b, &m)
 }
 
-func (m *HeptoMeta) Decode(b []byte) error {
-	return json.Unmarshal(b, m)
+func (m HeptoMeta) String() string {
+	return m.Role
 }
 
 func newMeta() sml.NodeMeta {
diff --git a/pkg/cluster/pki.go b/pkg/cluster/pki.go
index 795d842abbd79b44fd49b593f4fe36cdd152b41a..b4988c8deef6404f2012fd68b0ae1ed6d3e3118b 100644
--- a/pkg/cluster/pki.go
+++ b/pkg/cluster/pki.go
@@ -1,6 +1,7 @@
 package cluster
 
 import (
+	"os"
 	"path/filepath"
 
 	"forge.tedomum.net/acides/hepto/hepto/pkg/pki"
@@ -13,6 +14,10 @@ type ClusterPKI struct {
 }
 
 func NewClusterPKI(path string) (*ClusterPKI, error) {
+	err := os.MkdirAll(path, 0755)
+	if err != nil {
+		return nil, err
+	}
 	servicesCA, err := pki.GetCA(filepath.Join(path, "services"))
 	if err != nil {
 		return nil, err
@@ -27,3 +32,21 @@ func NewClusterPKI(path string) (*ClusterPKI, error) {
 	}
 	return &ClusterPKI{servicesCA, kubeletCA, apiserverCA}, nil
 }
+
+type NodeCerts struct {
+	APIClient *pki.CertKey
+}
+
+func NewNodeCerts(path string, nodeName string) (*NodeCerts, error) {
+	err := os.MkdirAll(path, 0755)
+	if err != nil {
+		return nil, err
+	}
+	apiClientCert, err := pki.LoadWithCSR(filepath.Join(path, "apiclient"), pki.NewClientTemplate(nodeName, ""))
+	if err != nil {
+		return nil, err
+	}
+	return &NodeCerts{
+		APIClient: apiClientCert,
+	}, nil
+}
diff --git a/pkg/pki/cert.go b/pkg/pki/cert.go
new file mode 100644
index 0000000000000000000000000000000000000000..c39f238e3ecd615750cbb06f02323313d409b08f
--- /dev/null
+++ b/pkg/pki/cert.go
@@ -0,0 +1,121 @@
+package pki
+
+import (
+	"crypto"
+	"crypto/ecdsa"
+	"crypto/elliptic"
+	"crypto/rand"
+	"crypto/x509"
+)
+
+const KEY_EXT = ".key"
+const CERT_EXT = ".pem"
+
+// Certificate (request) and associated keypair
+// Only supports ECDSA certificates for sign and auth
+type CertKey struct {
+	Key      *ecdsa.PrivateKey
+	keyPath  string
+	Cert     *x509.Certificate
+	certPath string
+	CSR      []byte
+}
+
+// Get a cert signer
+func (c *CertKey) Signer() crypto.Signer {
+	return c.Key
+}
+
+// Load and generate a CSR if necessary
+func LoadWithCSR(path string, template *x509.Certificate) (*CertKey, error) {
+	ck, err := loadOrGenerateKey(path)
+	if err != nil {
+		return nil, err
+	}
+	// Generate a fresh CSR if required
+	if ck.Cert == nil {
+		csrTemplate := &x509.CertificateRequest{
+			SignatureAlgorithm: template.SignatureAlgorithm,
+			Subject:            template.Subject,
+			DNSNames:           template.DNSNames,
+			EmailAddresses:     template.EmailAddresses,
+			IPAddresses:        template.IPAddresses,
+			URIs:               template.URIs,
+			ExtraExtensions:    template.ExtraExtensions,
+		}
+		csr, err := x509.CreateCertificateRequest(rand.Reader, csrTemplate, ck.Signer())
+		if err != nil {
+			return nil, err
+		}
+		ck.CSR = csr
+	}
+	return ck, nil
+}
+
+// Load and generate a self-signed cert if necessary (for CA mostly)
+func LoadSelfSigned(path string, template *x509.Certificate) (*CertKey, error) {
+	ck, err := loadOrGenerateKey(path)
+	if err != nil {
+		return nil, err
+	}
+	// Self-sign a certificate if required
+	if ck.Cert == nil {
+		signer := ck.Signer()
+		certBytes, err := x509.CreateCertificate(rand.Reader, template, template, signer.Public(), signer)
+		if err != nil {
+			return nil, err
+		}
+		err = ck.SetCert(certBytes)
+		if err != nil {
+			return nil, err
+		}
+	}
+	return ck, nil
+}
+
+// Parse and save a certificate
+func (ck *CertKey) SetCert(certBytes []byte) error {
+	cert, err := x509.ParseCertificate(certBytes)
+	if err != nil {
+		return err
+	}
+	err = SaveCert(ck.certPath, cert)
+	if err != nil {
+		return err
+	}
+	ck.Cert = cert
+	return nil
+}
+
+// Load a CertKey, generate the key if necessary
+func loadOrGenerateKey(path string) (*CertKey, error) {
+	keyPath := path + KEY_EXT
+	certPath := path + CERT_EXT
+	// Try to load the certificate first, so we do not create a key if
+	// this raises an error
+	cert, err := LoadCert(certPath)
+	if err != nil {
+		return nil, err
+	}
+	key, err := LoadKey(keyPath)
+	if err != nil {
+		return nil, err
+	}
+	// Create the key if necessary
+	if key == nil {
+		key, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+		if err != nil {
+			return nil, err
+		}
+		err = SaveKey(keyPath, key)
+		if err != nil {
+			return nil, err
+		}
+	}
+	return &CertKey{
+		Key:      key,
+		keyPath:  keyPath,
+		Cert:     cert,
+		certPath: certPath,
+	}, nil
+}
diff --git a/pkg/pki/pki.go b/pkg/pki/pki.go
index 5a1b00211ebfa2760136e82199beead2012081da..c76aa4cb6092f5c9ff71f2fd8ed91950581cbaf9 100644
--- a/pkg/pki/pki.go
+++ b/pkg/pki/pki.go
@@ -1,45 +1,21 @@
 package pki
 
 import (
-	"crypto"
-	"crypto/ecdsa"
-	"crypto/elliptic"
 	"crypto/rand"
 	"crypto/x509"
-	"os"
-	"path/filepath"
 )
 
-const CA_NAME = "_ca"
-const KEY_EXT = ".key"
-const CERT_EXT = ".pem"
-
 type CA struct {
-	path   string
-	signer crypto.Signer
-	cert   *x509.Certificate
+	certKey *CertKey
 }
 
 func GetCA(path string) (*CA, error) {
-	err := os.MkdirAll(path, 0755)
-	if err != nil {
-		return nil, err
-	}
-
-	signer, err := getSigner(filepath.Join(path, CA_NAME+KEY_EXT))
+	ck, err := LoadSelfSigned(path, NewCATemplate())
 	if err != nil {
 		return nil, err
 	}
-
-	cert, err := getCaCert(signer, filepath.Join(path, CA_NAME+CERT_EXT))
-	if err != nil {
-		return nil, err
-	}
-
 	return &CA{
-		path:   path,
-		signer: signer,
-		cert:   cert,
+		certKey: ck,
 	}, nil
 }
 
@@ -58,48 +34,5 @@ func (c *CA) Sign(csrBytes []byte, template *x509.Certificate) ([]byte, error) {
 	template.PublicKey = csr.PublicKey
 	template.PublicKeyAlgorithm = csr.PublicKeyAlgorithm
 	// Actually sign the certificate
-	return x509.CreateCertificate(rand.Reader, template, c.cert, template.PublicKey, c.signer)
-}
-
-func getSigner(path string) (crypto.Signer, error) {
-	key, err := LoadKey(path)
-	if err != nil {
-		return nil, err
-	}
-	// Create the key if necessary
-	if key == nil {
-		key, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
-		if err != nil {
-			return nil, err
-		}
-		err = SaveKey(path, key)
-		if err != nil {
-			return nil, err
-		}
-	}
-	return key, nil
-}
-
-func getCaCert(signer crypto.Signer, path string) (*x509.Certificate, error) {
-	cert, err := LoadCert(path)
-	if err != nil {
-		return nil, err
-	}
-	// Self-sign a certificate if required
-	if cert == nil {
-		template := NewCATemplate()
-		certBytes, err := x509.CreateCertificate(rand.Reader, template, template, signer.Public(), signer)
-		if err != nil {
-			return nil, err
-		}
-		cert, err = x509.ParseCertificate(certBytes)
-		if err != nil {
-			return nil, err
-		}
-		err = SaveCert(path, cert)
-		if err != nil {
-			return nil, err
-		}
-	}
-	return cert, nil
+	return x509.CreateCertificate(rand.Reader, template, c.certKey.Cert, template.PublicKey, c.certKey.Signer())
 }
diff --git a/pkg/selfcontain/container.go b/pkg/selfcontain/container.go
index a92deb6831047df7387460a53499da38ec7554d9..e4bf4e38340e744277502ed8aa2994c5011bc6c4 100644
--- a/pkg/selfcontain/container.go
+++ b/pkg/selfcontain/container.go
@@ -45,7 +45,7 @@ func New(name string, dataPath string, newRoot string, newArgs []string) (*Conta
 	factoryConfig := func(f *libcontainer.LinuxFactory) error {
 		f.InitPath = self
 		f.InitArgs = []string{os.Args[0], argInit}
-		return libcontainer.Cgroupfs(f)
+		return nil
 	}
 	factory, err := libcontainer.New(dataPath, factoryConfig)
 	if err != nil {
diff --git a/pkg/sml/delegate.go b/pkg/sml/delegate.go
index 78e1c0a01acc70d650b6439ec74743ae8e61567b..6120bbac91f1a75267fb75e5a53c53c5fea1cfee 100644
--- a/pkg/sml/delegate.go
+++ b/pkg/sml/delegate.go
@@ -6,11 +6,11 @@ import (
 )
 
 // Cluster implements the memberlist.Delegate interface
-func (m *Memberlist) NotifyConflict(node, other *memberlist.Node) {
+func (m *Memberlist[M, MP]) NotifyConflict(node, other *memberlist.Node) {
 }
 
 // Cluter implements the memberlist.Delegate interface
-func (m *Memberlist) NodeMeta(limit int) []byte {
+func (m *Memberlist[M, MP]) NodeMeta(limit int) []byte {
 	n, err := m.Meta.Encode()
 	if err != nil {
 		logrus.Warn("could not encode node metadata")
@@ -20,35 +20,35 @@ func (m *Memberlist) NodeMeta(limit int) []byte {
 }
 
 // Clutser implements the memberlist.Delegate interface
-func (m *Memberlist) NotifyMsg([]byte) {
+func (m *Memberlist[M, MP]) NotifyMsg([]byte) {
 }
 
 // Cluster implements the memberlist.Delegate interface
-func (m *Memberlist) GetBroadcasts(overhead, limit int) [][]byte {
+func (m *Memberlist[M, MP]) GetBroadcasts(overhead, limit int) [][]byte {
 	return nil
 }
 
 // Cluster implements the memberlist.Delegate interface
-func (m *Memberlist) LocalState(join bool) []byte {
+func (m *Memberlist[M, MP]) LocalState(join bool) []byte {
 	return nil
 }
 
 // Clutser implements the memberlist.Delegate interface
-func (m *Memberlist) MergeRemoteState(buf []byte, join bool) {
+func (m *Memberlist[M, MP]) MergeRemoteState(buf []byte, join bool) {
 }
 
 // Cluster implements the EventDelegate interface
-func (m *Memberlist) NotifyJoin(n *memberlist.Node) {
+func (m *Memberlist[M, MP]) NotifyJoin(n *memberlist.Node) {
 	logrus.Debug("node joined: ", n.Name)
 	go m.updateCache()
 }
 
 // Node implements the EventDelegate interface
-func (m *Memberlist) NotifyLeave(n *memberlist.Node) {
+func (m *Memberlist[M, MP]) NotifyLeave(n *memberlist.Node) {
 	go m.updateCache()
 }
 
 // Node implements the EventDelegate interface
-func (m *Memberlist) NotifyUpdate(n *memberlist.Node) {
+func (m *Memberlist[M, MP]) NotifyUpdate(n *memberlist.Node) {
 	go m.updateCache()
 }
diff --git a/pkg/sml/memberlist.go b/pkg/sml/memberlist.go
index 7dd5da463679f7fe8dba4275e07e377c6f909e08..6d15580bf953e5128fccde6a740159af9628efd5 100644
--- a/pkg/sml/memberlist.go
+++ b/pkg/sml/memberlist.go
@@ -13,23 +13,21 @@ import (
 	"github.com/sirupsen/logrus"
 )
 
-type Memberlist struct {
-	Meta NodeMeta
-
+type Memberlist[M any, MP MetaPointer[M]] struct {
+	Meta        MP
 	name        string
 	nodeName    string
-	anchors     []net.IP
+	anchors     []string
 	key         []byte
 	config      *memberlist.Config
 	ml          *memberlist.Memberlist
-	newMeta     func() NodeMeta
-	nodeCache   []Node
+	nodeCache   []Node[M, MP]
 	nodeChanges chan struct{}
 	chans       []chan struct{}
 	transport   *instrumentedTransport
 }
 
-func New(nodeName string, nodeIP net.IP, port int, anchors []net.IP, key []byte, newMeta func() NodeMeta) *Memberlist {
+func New[M any, MP MetaPointer[M]](nodeName string, nodeIP net.IP, port int, anchors []string, key []byte, meta MP) *Memberlist[M, MP] {
 	config := memberlist.DefaultLANConfig()
 	config.LogOutput = logrus.StandardLogger().WriterLevel(logrus.DebugLevel)
 	config.SecretKey = key
@@ -40,14 +38,13 @@ func New(nodeName string, nodeIP net.IP, port int, anchors []net.IP, key []byte,
 	config.Name = nodeName
 	config.SecretKey = key
 
-	m := &Memberlist{
+	m := &Memberlist[M, MP]{
 		nodeName:    nodeName,
 		nodeChanges: make(chan struct{}, 100),
 		anchors:     anchors,
 		key:         key,
 		config:      config,
-		newMeta:     newMeta,
-		Meta:        newMeta(),
+		Meta:        meta,
 	}
 
 	// Memberlist is its own memberlist delegate implementation
@@ -59,7 +56,7 @@ func New(nodeName string, nodeIP net.IP, port int, anchors []net.IP, key []byte,
 }
 
 // Start the memberlist cluster by listening on main sockets
-func (m *Memberlist) Start() error {
+func (m *Memberlist[M, MP]) Start() error {
 	logrus.Info("starting the cluster transport")
 	tc := &memberlist.NetTransportConfig{
 		BindAddrs: []string{m.config.BindAddr},
@@ -82,7 +79,7 @@ func (m *Memberlist) Start() error {
 
 // Run the memberlist cluster main loop, that awaits cluster changes, maintains
 // the cluster state and propagates information to channels
-func (m *Memberlist) Run() error {
+func (m *Memberlist[M, MP]) Run() error {
 	ticker := time.Tick(10 * time.Second)
 	for {
 		select {
@@ -91,7 +88,7 @@ func (m *Memberlist) Run() error {
 		case <-m.nodeChanges:
 			logrus.Debug("network topology changed, a node just joined, left or was updated")
 			for _, node := range m.Nodes() {
-				logrus.Debugf("* %s: %s ; %v\n", node.Name, string(node.Meta), node.NodeMeta)
+				logrus.Debugf("* %s [%s]\n", node.Name, node.NodeMeta.String())
 			}
 			for _, c := range m.chans {
 				c <- struct{}{}
@@ -101,7 +98,7 @@ func (m *Memberlist) Run() error {
 }
 
 // Get a channel for notifications of network changes
-func (m *Memberlist) Events() <-chan struct{} {
+func (m *Memberlist[M, MP]) Events() <-chan struct{} {
 	// Buffer to avoid blocking later
 	// TODO: be more organized about channels we deliver
 	c := make(chan struct{}, 100)
@@ -110,24 +107,24 @@ func (m *Memberlist) Events() <-chan struct{} {
 }
 
 // Get the list of current cluster nodes
-func (m *Memberlist) Nodes() []Node {
+func (m *Memberlist[M, MP]) Nodes() []Node[M, MP] {
 	return m.nodeCache
 }
 
 // Get the instrumentation interface
-func (m *Memberlist) Instr() Instrumentation {
+func (m *Memberlist[M, MP]) Instr() Instrumentation {
 	return m.transport
 }
 
-// Build a local node representation from an upstream memberlist node, using
-// empty metadata (before decoding)
-func (m *Memberlist) newNode(mlNode *memberlist.Node) Node {
-	return Node{mlNode, m.newMeta()}
+// Update the current node
+func (m *Memberlist[M, MP]) Update() {
+	logrus.Debug("updating the memberlist cluster")
+	m.ml.UpdateNode(1 * time.Second)
 }
 
 // Update the node cache after a network change, goes through all the
 // nodes and decodes metadata
-func (m *Memberlist) updateCache() {
+func (m *Memberlist[M, MP]) updateCache() {
 	// Do not update the node cache until memberlist is setup
 	if m.ml == nil {
 		logrus.Debug("not updating the node cache before memberlist is ready")
@@ -135,22 +132,29 @@ func (m *Memberlist) updateCache() {
 	}
 	members := m.ml.Members()
 	logrus.Debugf("updating the node cache with %d members", len(members))
-	cache := make([]Node, len(members))
-	for i, mlNode := range members {
-		cache[i] = m.newNode(mlNode)
-		cache[i].Decode()
+	var cache []Node[M, MP]
+	for _, mlNode := range members {
+		meta := new(M)
+		pointer := MP(meta)
+		err := pointer.Decode(mlNode.Meta)
+		if err == nil {
+			cache = append(cache, Node[M, MP]{mlNode, pointer})
+		} else {
+			logrus.Warnf("could not decode meta for node %s", mlNode.Name)
+		}
 	}
 	m.nodeCache = cache
 	m.nodeChanges <- struct{}{}
 }
 
 // Try and join any anchor that is not currently a cluster member
-func (m *Memberlist) join() error {
+func (m *Memberlist[M, MP]) join() error {
 	addrs := []string{}
+	members := m.ml.Members()
 	for _, candidate := range m.anchors {
 		found := false
-		for _, node := range m.ml.Members() {
-			if node.Addr.Equal(candidate) {
+		for _, node := range members {
+			if node.Address() == candidate {
 				found = true
 				break
 			}
@@ -158,7 +162,7 @@ func (m *Memberlist) join() error {
 		if found {
 			continue
 		}
-		addrs = append(addrs, candidate.String())
+		addrs = append(addrs, candidate)
 	}
 	if len(addrs) > 0 {
 		logrus.Info("joining cluster nodes: ", addrs)
diff --git a/pkg/sml/node.go b/pkg/sml/node.go
index defa24378394504cfcb3dff1de2483ab673c4eff..9b5c20c5ecb46b3c0d82946ed0e6121978ca6661 100644
--- a/pkg/sml/node.go
+++ b/pkg/sml/node.go
@@ -4,27 +4,32 @@ import (
 	"github.com/hashicorp/memberlist"
 )
 
-// Node meta can be encoded and decoded to bytes, required
+// Cluster data can be encoded and decoded to bytes, required
 // by Memberlist wire protocol
-type NodeMeta interface {
+type ClusterData interface {
 	Encode() ([]byte, error)
 	Decode([]byte) error
+	String() string
 }
 
-// Represents a full node for easy browsing
-type Node struct {
-	*memberlist.Node
-	NodeMeta NodeMeta
+type NodeMeta interface {
+	ClusterData
 }
 
-func (n *Node) Encode() (*memberlist.Node, error) {
-	node := n.Node
-	b, err := n.NodeMeta.Encode()
-	node.Meta = b
-	return node, err
+// Node meta is plain node data, manipulated as a pointer
+type MetaPointer[M any] interface {
+	NodeMeta
+	*M
 }
 
-// Decode a raw node
-func (n *Node) Decode() error {
-	return n.NodeMeta.Decode(n.Meta)
+// Cluster state is node data that can be merged, crdt-style
+type NodeState interface {
+	ClusterData
+	Merge(*ClusterData) error
+}
+
+// Represents a full node for easy browsing
+type Node[M any, MP MetaPointer[M]] struct {
+	*memberlist.Node
+	NodeMeta MP
 }
diff --git a/test.go b/test.go
new file mode 100644
index 0000000000000000000000000000000000000000..38c12d98be59160fbf2307f79a85cf88d41b4522
--- /dev/null
+++ b/test.go
@@ -0,0 +1,42 @@
+package main
+
+import (
+	"net"
+	"os"
+	"strconv"
+
+	"forge.tedomum.net/acides/hepto/hepto/pkg/sml"
+	"github.com/sirupsen/logrus"
+)
+
+type Meta struct {
+	name string
+}
+
+func (m *Meta) Encode() ([]byte, error) {
+	logrus.Debug("encoding node meta ", m.name)
+	return []byte(m.name), nil
+}
+
+func (m *Meta) Decode(b []byte) error {
+	logrus.Debug("decoding node meta ", b)
+	m.name = string(b)
+	logrus.Debug("decoded node meta ", m.name)
+	return nil
+}
+
+func (m *Meta) String() string {
+	logrus.Debug("debugging node meta ", m.name)
+	return m.name
+}
+
+func main() {
+	logrus.SetLevel(logrus.DebugLevel)
+	ip := net.ParseIP(os.Args[2])
+	logrus.Info(ip)
+	port, _ := strconv.Atoi(os.Args[3])
+	meta := Meta{name: os.Args[1]}
+	m := sml.New(os.Args[1], ip, port, os.Args[4:], []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, &meta)
+	logrus.Error(m.Start())
+	logrus.Error(m.Run())
+}