Basic implementation of client: render qa code and scan from mobile app
diff --git a/core/nebula/api/Makefile b/core/nebula/api/Makefile
index 3898183..d25b7ed 100644
--- a/core/nebula/api/Makefile
+++ b/core/nebula/api/Makefile
@@ -1,19 +1,22 @@
clean:
rm -f api_*
+tidy:
+ go1.17.1 mod tidy -compat=1.17
+
api_arm64: export CGO_ENABLED=0
api_arm64: export GO111MODULE=on
api_arm64: export GOOS=linux
api_arm64: export GOARCH=arm64
-api_arm64:
- go1.17.1 build -o api_arm64 main.go
+api_arm64: *.go
+ go1.17.1 build -o api_arm64 *.go
api_amd64: export CGO_ENABLED=0
api_amd64: export GO111MODULE=on
api_amd64: export GOOS=linux
api_amd64: export GOARCH=amd64
-api_amd64:
- go1.17.1 build -o api_amd64 main.go
+api_amd64: *.go
+ go1.17.1 build -o api_amd64 *.go
api: clean api_arm64 api_amd64
diff --git a/core/nebula/api/go.mod b/core/nebula/api/go.mod
index e8a515f..3a5bd6d 100644
--- a/core/nebula/api/go.mod
+++ b/core/nebula/api/go.mod
@@ -1,10 +1,48 @@
module github.com/giolekva/pcloud/core/nebula/api
-go 1.16
+go 1.17
require (
github.com/giolekva/pcloud/core/nebula/controller v0.0.0-20211209144208-c054df13a2a1
github.com/gorilla/mux v1.8.0
+ github.com/slackhq/nebula v1.5.0
+ inet.af/netaddr v0.0.0-20211027220019-c74959edd3b6
+ k8s.io/api v0.23.0
k8s.io/apimachinery v0.23.0
k8s.io/client-go v0.23.0
)
+
+require (
+ github.com/davecgh/go-spew v1.1.1 // indirect
+ github.com/go-logr/logr v1.2.0 // indirect
+ github.com/gogo/protobuf v1.3.2 // indirect
+ github.com/golang/protobuf v1.5.2 // indirect
+ github.com/google/go-cmp v0.5.5 // indirect
+ github.com/google/gofuzz v1.1.0 // indirect
+ github.com/googleapis/gnostic v0.5.5 // indirect
+ github.com/imdario/mergo v0.3.8 // indirect
+ github.com/json-iterator/go v1.1.12 // indirect
+ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+ github.com/modern-go/reflect2 v1.0.2 // indirect
+ github.com/spf13/pflag v1.0.5 // indirect
+ go4.org/intern v0.0.0-20211027215823-ae77deb06f29 // indirect
+ go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37 // indirect
+ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect
+ golang.org/x/net v0.0.0-20211101193420-4a448f8816b3 // indirect
+ golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f // indirect
+ golang.org/x/sys v0.0.0-20211103235746-7861aae1554b // indirect
+ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
+ golang.org/x/text v0.3.7 // indirect
+ golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
+ google.golang.org/appengine v1.6.7 // indirect
+ google.golang.org/protobuf v1.27.1 // indirect
+ gopkg.in/inf.v0 v0.9.1 // indirect
+ gopkg.in/yaml.v2 v2.4.0 // indirect
+ gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
+ k8s.io/klog/v2 v2.30.0 // indirect
+ k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect
+ k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b // indirect
+ sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect
+ sigs.k8s.io/structured-merge-diff/v4 v4.1.2 // indirect
+ sigs.k8s.io/yaml v1.2.0 // indirect
+)
diff --git a/core/nebula/api/go.sum b/core/nebula/api/go.sum
index 5c2483f..b00b1a8 100644
--- a/core/nebula/api/go.sum
+++ b/core/nebula/api/go.sum
@@ -62,6 +62,7 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
+github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
@@ -178,8 +179,9 @@
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
-github.com/imdario/mergo v0.3.5 h1:JboBksRwiiAJWvIYJVo46AfV+IAIKZpfrSzVKj42R4Q=
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
+github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ=
+github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
@@ -228,6 +230,9 @@
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
+github.com/slackhq/nebula v1.5.0 h1:EdmvqSRcJrt4/4ki/R6O1NvjcSwdaXf9xpcF1NRavBk=
+github.com/slackhq/nebula v1.5.0/go.mod h1:xTcgNEbS7KLRvEQUKcNXUQIzNNaF+QtwgHxcYCDetBM=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
@@ -252,6 +257,10 @@
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
+go4.org/intern v0.0.0-20211027215823-ae77deb06f29 h1:UXLjNohABv4S58tHmeuIZDO6e3mHpW2Dx33gaNt03LE=
+go4.org/intern v0.0.0-20211027215823-ae77deb06f29/go.mod h1:cS2ma+47FKrLPdXFpr7CuxiTW3eyJbWew4qx0qtQWDA=
+go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37 h1:Tx9kY6yUkLge/pFG7IEMwDZy6CS2ajFc9TvQdPCW0uA=
+go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@@ -260,6 +269,8 @@
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -332,8 +343,9 @@
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20210825183410-e898025ed96a h1:bRuuGXV8wwSdGTB+CtJf+FjgO1APK1CoO39T4BN/XBw=
golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20211101193420-4a448f8816b3 h1:VrJZAjbekhoRn7n5FBujY31gboH+iB3pdLxn3gE9FjU=
+golang.org/x/net v0.0.0-20211101193420-4a448f8816b3/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
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=
@@ -404,13 +416,15 @@
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e h1:XMgFehsDnnLGtjvjOfqWSUzt0alpTR1RSEuznObga2c=
golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211103235746-7861aae1554b h1:1VkfZQv42XQlA/jchYumAnv1UPo6RgF9rJFkTgZIxO4=
+golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/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=
-golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b h1:9zKuko04nR4gjZ4+DNjHqRlAJqbJETHwiNKDqTfOjfE=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -612,6 +626,8 @@
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+inet.af/netaddr v0.0.0-20211027220019-c74959edd3b6 h1:acCzuUSQ79tGsM/O50VRFySfMm19IoMKL+sZztZkCxw=
+inet.af/netaddr v0.0.0-20211027220019-c74959edd3b6/go.mod h1:y3MGhcFMlh0KZPMuXXow8mpjxxAk3yoDNsp4cQz54i8=
k8s.io/api v0.22.2/go.mod h1:y3ydYpLJAaDI+BbSe2xmGcqxiWHmWjkEeIbiwHvnPR8=
k8s.io/api v0.23.0 h1:WrL1gb73VSC8obi8cuYETJGXEoFNEh3LU0Pt+Sokgro=
k8s.io/api v0.23.0/go.mod h1:8wmDdLBHBNxtOIytwLstXt5E9PddnZb0GaMcqsvDBpg=
diff --git a/core/nebula/api/main.go b/core/nebula/api/main.go
index ed09135..c0c4817 100644
--- a/core/nebula/api/main.go
+++ b/core/nebula/api/main.go
@@ -1,8 +1,8 @@
package main
import (
- "context"
"embed"
+ "encoding/json"
"flag"
"fmt"
"html/template"
@@ -11,17 +11,17 @@
"github.com/gorilla/mux"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
- nebulav1 "github.com/giolekva/pcloud/core/nebula/controller/apis/nebula/v1"
clientset "github.com/giolekva/pcloud/core/nebula/controller/generated/clientset/versioned"
)
var port = flag.Int("port", 8080, "Port to listen on.")
var kubeConfig = flag.String("kubeconfig", "", "Path to a kubeconfig. Only required if out-of-cluster.")
var masterURL = flag.String("master", "", "The address of the Kubernetes API server. Overrides any value in kubeconfig. Only required if out-of-cluster.")
+var namespace = flag.String("namespace", "", "Namespace where Nebula CA and Node secrets are stored.")
+var caSecretName = flag.String("ca-secret-name", "", "Name of the Nebula CA secret storing certificate information.")
//go:embed templates/*
var tmpls embed.FS
@@ -38,99 +38,6 @@
return &Templates{index}, nil
}
-type nebulaCA struct {
- Name string
- Namespace string
- Nodes []nebulaNode
-}
-
-type nebulaNode struct {
- Name string
- Namespace string
- IP string
-}
-
-type Manager struct {
- kubeClient kubernetes.Interface
- nebulaClient clientset.Interface
-}
-
-func (m *Manager) ListAll() ([]*nebulaCA, error) {
- ret := make([]*nebulaCA, 0)
- cas, err := m.nebulaClient.LekvaV1().NebulaCAs("").List(context.TODO(), metav1.ListOptions{})
- if err != nil {
- return nil, err
- }
- for _, ca := range cas.Items {
- ret = append(ret, &nebulaCA{
- Name: ca.Name,
- Namespace: ca.Namespace,
- Nodes: make([]nebulaNode, 0),
- })
- }
- nodes, err := m.nebulaClient.LekvaV1().NebulaNodes("").List(context.TODO(), metav1.ListOptions{})
- if err != nil {
- return nil, err
- }
- for _, node := range nodes.Items {
- for _, ca := range ret {
- if ca.Name == node.Spec.CAName {
- ca.Nodes = append(ca.Nodes, nebulaNode{
- Name: node.Name,
- Namespace: node.Namespace,
- IP: node.Spec.IPCidr,
- })
- }
- }
- }
- return ret, nil
-}
-
-func (m *Manager) createNode(namespace, name, caNamespace, caName, ipCidr, pubKey string) (string, string, error) {
- node := &nebulav1.NebulaNode{
- ObjectMeta: metav1.ObjectMeta{
- Name: name,
- Namespace: namespace,
- },
- Spec: nebulav1.NebulaNodeSpec{
- CAName: caName,
- CANamespace: caNamespace,
- IPCidr: ipCidr,
- PubKey: pubKey,
- SecretName: fmt.Sprintf("%s-cert", name),
- },
- }
- node, err := m.nebulaClient.LekvaV1().NebulaNodes(namespace).Create(context.TODO(), node, metav1.CreateOptions{})
- if err != nil {
- return "", "", err
- }
- return node.Namespace, node.Name, nil
-}
-
-func (m *Manager) getNodeCertQR(namespace, name string) ([]byte, error) {
- node, err := m.nebulaClient.LekvaV1().NebulaNodes(namespace).Get(context.TODO(), name, metav1.GetOptions{})
- if err != nil {
- return nil, err
- }
- secret, err := m.kubeClient.CoreV1().Secrets(namespace).Get(context.TODO(), node.Spec.SecretName, metav1.GetOptions{})
- if err != nil {
- return nil, err
- }
- return secret.Data["host.png"], nil
-}
-
-func (m *Manager) getCACertQR(namespace, name string) ([]byte, error) {
- ca, err := m.nebulaClient.LekvaV1().NebulaCAs(namespace).Get(context.TODO(), name, metav1.GetOptions{})
- if err != nil {
- return nil, err
- }
- secret, err := m.kubeClient.CoreV1().Secrets(namespace).Get(context.TODO(), ca.Spec.SecretName, metav1.GetOptions{})
- if err != nil {
- return nil, err
- }
- return secret.Data["ca.png"], nil
-}
-
type Handler struct {
mgr Manager
tmpls *Templates
@@ -151,7 +58,7 @@
vars := mux.Vars(r)
namespace := vars["namespace"]
name := vars["name"]
- qr, err := h.mgr.getNodeCertQR(namespace, name)
+ qr, err := h.mgr.GetNodeCertQR(namespace, name)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
@@ -163,7 +70,7 @@
vars := mux.Vars(r)
namespace := vars["namespace"]
name := vars["name"]
- qr, err := h.mgr.getCACertQR(namespace, name)
+ qr, err := h.mgr.GetCACertQR(namespace, name)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
@@ -176,7 +83,7 @@
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
- _, _, err := h.mgr.createNode(
+ _, _, err := h.mgr.CreateNode(
r.FormValue("node-namespace"),
r.FormValue("node-name"),
r.FormValue("ca-namespace"),
@@ -191,6 +98,66 @@
http.Redirect(w, r, "/", http.StatusSeeOther)
}
+func (h *Handler) getNextIP(w http.ResponseWriter, r *http.Request) {
+ ip, err := h.mgr.getNextIP()
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ fmt.Fprint(w, ip)
+}
+
+type signReq struct {
+ Message []byte `json:"message"`
+}
+
+type signResp struct {
+ Signature []byte `json:"signature"`
+}
+
+func (h *Handler) sign(w http.ResponseWriter, r *http.Request) {
+ var req signReq
+ if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+ signature, err := h.mgr.Sign(req.Message)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ w.Header().Set("Content-Type", "application/json")
+ resp := signResp{
+ signature,
+ }
+ if err := json.NewEncoder(w).Encode(resp); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+}
+
+type verifyReq struct {
+ Message []byte `json:"message"`
+ Signature []byte `json:"signature"`
+}
+
+func (h *Handler) verify(w http.ResponseWriter, r *http.Request) {
+ var req verifyReq
+ if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+ valid, err := h.mgr.VerifySignature(req.Message, req.Signature)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ if !valid {
+ http.Error(w, "Signature could not be verified", http.StatusBadRequest)
+ return
+ }
+}
+
func main() {
flag.Parse()
cfg, err := clientcmd.BuildConfigFromFlags(*masterURL, *kubeConfig)
@@ -209,12 +176,17 @@
mgr := Manager{
kubeClient: kubeClient,
nebulaClient: nebulaClient,
+ namespace: *namespace,
+ caSecretName: *caSecretName,
}
handler := Handler{
mgr: mgr,
tmpls: t,
}
r := mux.NewRouter()
+ r.HandleFunc("/api/ip", handler.getNextIP)
+ r.HandleFunc("/api/sign", handler.sign)
+ r.HandleFunc("/api/verify", handler.verify)
r.HandleFunc("/node/{namespace:[a-zA-z0-9-]+}/{name:[a-zA-z0-9-]+}", handler.handleNode)
r.HandleFunc("/ca/{namespace:[a-zA-z0-9-]+}/{name:[a-zA-z0-9-]+}", handler.handleCA)
r.HandleFunc("/sign-node", handler.handleSignNode)
diff --git a/core/nebula/api/manager.go b/core/nebula/api/manager.go
new file mode 100644
index 0000000..40f6eeb
--- /dev/null
+++ b/core/nebula/api/manager.go
@@ -0,0 +1,163 @@
+package main
+
+import (
+ "context"
+ "crypto"
+ "crypto/ed25519"
+ "crypto/rand"
+ "errors"
+ "fmt"
+
+ "inet.af/netaddr"
+
+ "github.com/slackhq/nebula/cert"
+
+ corev1 "k8s.io/api/core/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/client-go/kubernetes"
+
+ nebulav1 "github.com/giolekva/pcloud/core/nebula/controller/apis/nebula/v1"
+ clientset "github.com/giolekva/pcloud/core/nebula/controller/generated/clientset/versioned"
+)
+
+type nebulaCA struct {
+ Name string
+ Namespace string
+ Nodes []nebulaNode
+}
+
+type nebulaNode struct {
+ Name string
+ Namespace string
+ IP string
+}
+
+type Manager struct {
+ kubeClient kubernetes.Interface
+ nebulaClient clientset.Interface
+ namespace string
+ caSecretName string
+}
+
+func (m *Manager) ListAll() ([]*nebulaCA, error) {
+ ret := make([]*nebulaCA, 0)
+ cas, err := m.nebulaClient.LekvaV1().NebulaCAs("").List(context.TODO(), metav1.ListOptions{})
+ if err != nil {
+ return nil, err
+ }
+ for _, ca := range cas.Items {
+ ret = append(ret, &nebulaCA{
+ Name: ca.Name,
+ Namespace: ca.Namespace,
+ Nodes: make([]nebulaNode, 0),
+ })
+ }
+ nodes, err := m.nebulaClient.LekvaV1().NebulaNodes("").List(context.TODO(), metav1.ListOptions{})
+ if err != nil {
+ return nil, err
+ }
+ for _, node := range nodes.Items {
+ for _, ca := range ret {
+ if ca.Name == node.Spec.CAName {
+ ca.Nodes = append(ca.Nodes, nebulaNode{
+ Name: node.Name,
+ Namespace: node.Namespace,
+ IP: node.Spec.IPCidr,
+ })
+ }
+ }
+ }
+ return ret, nil
+}
+
+func (m *Manager) CreateNode(namespace, name, caNamespace, caName, ipCidr, pubKey string) (string, string, error) {
+ node := &nebulav1.NebulaNode{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: name,
+ Namespace: namespace,
+ },
+ Spec: nebulav1.NebulaNodeSpec{
+ CAName: caName,
+ CANamespace: caNamespace,
+ IPCidr: ipCidr,
+ PubKey: pubKey,
+ SecretName: fmt.Sprintf("%s-cert", name),
+ },
+ }
+ node, err := m.nebulaClient.LekvaV1().NebulaNodes(namespace).Create(context.TODO(), node, metav1.CreateOptions{})
+ if err != nil {
+ return "", "", err
+ }
+ return node.Namespace, node.Name, nil
+}
+
+func (m *Manager) GetNodeCertQR(namespace, name string) ([]byte, error) {
+ secret, err := m.getNodeSecret(namespace, name)
+ if err != nil {
+ return nil, err
+ }
+ return secret.Data["host.png"], nil
+}
+
+func (m *Manager) GetCACertQR(namespace, name string) ([]byte, error) {
+ ca, err := m.nebulaClient.LekvaV1().NebulaCAs(namespace).Get(context.TODO(), name, metav1.GetOptions{})
+ if err != nil {
+ return nil, err
+ }
+ secret, err := m.kubeClient.CoreV1().Secrets(namespace).Get(context.TODO(), ca.Spec.SecretName, metav1.GetOptions{})
+ if err != nil {
+ return nil, err
+ }
+ return secret.Data["ca.png"], nil
+}
+
+func (m *Manager) getNextIP() (netaddr.IP, error) {
+ nodes, err := m.nebulaClient.LekvaV1().NebulaNodes(m.namespace).List(context.TODO(), metav1.ListOptions{})
+ if err != nil {
+ return netaddr.IP{}, err
+ }
+ var max netaddr.IP
+ for _, node := range nodes.Items {
+ ip := netaddr.MustParseIPPrefix(node.Spec.IPCidr)
+ if max.Less(ip.IP()) {
+ max = ip.IP()
+ }
+ }
+ n := max.Next()
+ if n.IsZero() {
+ return n, errors.New("IP address range exhausted")
+ }
+ return n, nil
+}
+
+func (m *Manager) Sign(message []byte) ([]byte, error) {
+ secret, err := m.kubeClient.CoreV1().Secrets(m.namespace).Get(context.TODO(), m.caSecretName, metav1.GetOptions{})
+ if err != nil {
+ return nil, err
+ }
+ edPriv, _, err := cert.UnmarshalEd25519PrivateKey(secret.Data["ca.key"])
+ if err != nil {
+ return nil, err
+ }
+ return edPriv.Sign(rand.Reader, message, crypto.Hash(0))
+}
+
+func (m *Manager) VerifySignature(message, signature []byte) (bool, error) {
+ secret, err := m.kubeClient.CoreV1().Secrets(m.namespace).Get(context.TODO(), m.caSecretName, metav1.GetOptions{})
+ if err != nil {
+ return false, err
+ }
+ edPriv, _, err := cert.UnmarshalEd25519PrivateKey(secret.Data["ca.key"])
+ if err != nil {
+ return false, err
+ }
+ return ed25519.Verify(edPriv.Public().(ed25519.PublicKey), message, signature), nil
+}
+
+func (m *Manager) getNodeSecret(namespace, name string) (*corev1.Secret, error) {
+ node, err := m.nebulaClient.LekvaV1().NebulaNodes(namespace).Get(context.TODO(), name, metav1.GetOptions{})
+ if err != nil {
+ return nil, err
+ }
+ return m.kubeClient.CoreV1().Secrets(namespace).Get(context.TODO(), node.Spec.SecretName, metav1.GetOptions{})
+}