blob: 790a01edaeb611cd19e49d074e1b98d76d7acb37 [file] [log] [blame]
giolekvab64297c2021-12-13 14:36:32 +04001package main
2
3import (
4 "context"
5 "crypto"
6 "crypto/ed25519"
7 "crypto/rand"
8 "errors"
9 "fmt"
10
11 "inet.af/netaddr"
12
giolekvacc3ebcb2021-12-17 10:52:17 +040013 "github.com/jinzhu/copier"
giolekvab64297c2021-12-13 14:36:32 +040014 "github.com/slackhq/nebula/cert"
15
16 corev1 "k8s.io/api/core/v1"
17 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
18 "k8s.io/client-go/kubernetes"
19
20 nebulav1 "github.com/giolekva/pcloud/core/nebula/controller/apis/nebula/v1"
21 clientset "github.com/giolekva/pcloud/core/nebula/controller/generated/clientset/versioned"
22)
23
24type nebulaCA struct {
25 Name string
26 Namespace string
27 Nodes []nebulaNode
28}
29
30type nebulaNode struct {
31 Name string
32 Namespace string
33 IP string
34}
35
36type Manager struct {
37 kubeClient kubernetes.Interface
38 nebulaClient clientset.Interface
39 namespace string
giolekvaf58a7692021-12-15 18:05:39 +040040 caName string
giolekvacc3ebcb2021-12-17 10:52:17 +040041 cfgTmpl map[string]interface{}
giolekvab64297c2021-12-13 14:36:32 +040042}
43
44func (m *Manager) ListAll() ([]*nebulaCA, error) {
45 ret := make([]*nebulaCA, 0)
46 cas, err := m.nebulaClient.LekvaV1().NebulaCAs("").List(context.TODO(), metav1.ListOptions{})
47 if err != nil {
48 return nil, err
49 }
50 for _, ca := range cas.Items {
51 ret = append(ret, &nebulaCA{
52 Name: ca.Name,
53 Namespace: ca.Namespace,
54 Nodes: make([]nebulaNode, 0),
55 })
56 }
57 nodes, err := m.nebulaClient.LekvaV1().NebulaNodes("").List(context.TODO(), metav1.ListOptions{})
58 if err != nil {
59 return nil, err
60 }
61 for _, node := range nodes.Items {
62 for _, ca := range ret {
63 if ca.Name == node.Spec.CAName {
64 ca.Nodes = append(ca.Nodes, nebulaNode{
65 Name: node.Name,
66 Namespace: node.Namespace,
67 IP: node.Spec.IPCidr,
68 })
69 }
70 }
71 }
72 return ret, nil
73}
74
75func (m *Manager) CreateNode(namespace, name, caNamespace, caName, ipCidr, pubKey string) (string, string, error) {
76 node := &nebulav1.NebulaNode{
77 ObjectMeta: metav1.ObjectMeta{
78 Name: name,
79 Namespace: namespace,
80 },
81 Spec: nebulav1.NebulaNodeSpec{
82 CAName: caName,
83 CANamespace: caNamespace,
84 IPCidr: ipCidr,
85 PubKey: pubKey,
86 SecretName: fmt.Sprintf("%s-cert", name),
87 },
88 }
89 node, err := m.nebulaClient.LekvaV1().NebulaNodes(namespace).Create(context.TODO(), node, metav1.CreateOptions{})
90 if err != nil {
91 return "", "", err
92 }
93 return node.Namespace, node.Name, nil
94}
95
giolekvacc3ebcb2021-12-17 10:52:17 +040096func (m *Manager) GetNodeConfig(namespace, name string) (map[string]interface{}, error) {
97 secret, err := m.getNodeSecret(namespace, name)
98 if err != nil {
99 return nil, err
100 }
101 var c map[string]interface{}
102 if err := copier.CopyWithOption(&c, m.cfgTmpl, copier.Option{DeepCopy: true}); err != nil {
103 return nil, err
104 }
105 var pki map[string]interface{}
106 var ok bool
107 if pki, ok = c["pki"].(map[string]interface{}); !ok {
108 panic("Should not reach")
109 }
110 pki["ca"] = string(secret.Data["ca.crt"])
111 pki["cert"] = string(secret.Data["host.crt"])
112 return c, nil
113}
114
giolekvab64297c2021-12-13 14:36:32 +0400115func (m *Manager) GetNodeCertQR(namespace, name string) ([]byte, error) {
116 secret, err := m.getNodeSecret(namespace, name)
117 if err != nil {
118 return nil, err
119 }
120 return secret.Data["host.png"], nil
121}
122
123func (m *Manager) GetCACertQR(namespace, name string) ([]byte, error) {
giolekvaf58a7692021-12-15 18:05:39 +0400124 secret, err := m.getCASecret(namespace, name)
giolekvab64297c2021-12-13 14:36:32 +0400125 if err != nil {
126 return nil, err
127 }
128 return secret.Data["ca.png"], nil
129}
130
131func (m *Manager) getNextIP() (netaddr.IP, error) {
132 nodes, err := m.nebulaClient.LekvaV1().NebulaNodes(m.namespace).List(context.TODO(), metav1.ListOptions{})
133 if err != nil {
134 return netaddr.IP{}, err
135 }
136 var max netaddr.IP
137 for _, node := range nodes.Items {
138 ip := netaddr.MustParseIPPrefix(node.Spec.IPCidr)
139 if max.Less(ip.IP()) {
140 max = ip.IP()
141 }
142 }
143 n := max.Next()
144 if n.IsZero() {
145 return n, errors.New("IP address range exhausted")
146 }
147 return n, nil
148}
149
150func (m *Manager) Sign(message []byte) ([]byte, error) {
giolekvaf58a7692021-12-15 18:05:39 +0400151 secret, err := m.getCASecret(m.namespace, m.caName)
giolekvab64297c2021-12-13 14:36:32 +0400152 if err != nil {
153 return nil, err
154 }
155 edPriv, _, err := cert.UnmarshalEd25519PrivateKey(secret.Data["ca.key"])
156 if err != nil {
157 return nil, err
158 }
159 return edPriv.Sign(rand.Reader, message, crypto.Hash(0))
160}
161
162func (m *Manager) VerifySignature(message, signature []byte) (bool, error) {
giolekvaf58a7692021-12-15 18:05:39 +0400163 secret, err := m.getCASecret(m.namespace, m.caName)
giolekvab64297c2021-12-13 14:36:32 +0400164 if err != nil {
165 return false, err
166 }
167 edPriv, _, err := cert.UnmarshalEd25519PrivateKey(secret.Data["ca.key"])
168 if err != nil {
169 return false, err
170 }
171 return ed25519.Verify(edPriv.Public().(ed25519.PublicKey), message, signature), nil
172}
173
giolekvaf58a7692021-12-15 18:05:39 +0400174func (m *Manager) getCASecret(namespace, name string) (*corev1.Secret, error) {
175 ca, err := m.nebulaClient.LekvaV1().NebulaCAs(namespace).Get(context.TODO(), name, metav1.GetOptions{})
176 if err != nil {
177 return nil, err
178 }
179 return m.kubeClient.CoreV1().Secrets(namespace).Get(context.TODO(), ca.Spec.SecretName, metav1.GetOptions{})
180}
181
giolekvab64297c2021-12-13 14:36:32 +0400182func (m *Manager) getNodeSecret(namespace, name string) (*corev1.Secret, error) {
183 node, err := m.nebulaClient.LekvaV1().NebulaNodes(namespace).Get(context.TODO(), name, metav1.GetOptions{})
184 if err != nil {
185 return nil, err
186 }
187 return m.kubeClient.CoreV1().Secrets(namespace).Get(context.TODO(), node.Spec.SecretName, metav1.GetOptions{})
188}